最新公告
  • 欢迎您光临奇兔源码社区,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入我们
  • 双向数据绑定无非就是,视图 => 数据,数据 => 视图的更新过程

    以下的方案中的实现思路:

    定义一个Vue的构造函数并初始化这个函数(myVue.prototype._init)

    实现数据层的更新:数据劫持,定义一个 obverse 函数重写data的set和get(myVue.prototype._obsever)

    实现视图层的更新:订阅者模式,定义个 Watcher 函数实现对DOM的更新(Watcher)

    将数据和视图层进行绑定,解析指令v-bind、v-model、v-click(myVue.prototype._compile)

    创建Vue实例(new myVue)

    1.object.defineproperty方式实现双向数据绑定

    <!DOCTYPE html>

    <html>

     

    <head>

    <title>myVue</title>

    <style>

    #app{

    text-align: center;

    }

    </style>

    </head>

     

    <body>

    <div id="app">

    <form>

    <input type="text" v-model="number" />

    <button type="button" v-click="increment">增加</button>

    </form>

    <h3 v-bind="number"></h3>

    </div>

    </body>

    <script>

     

    // 定义一个myVue构造函数

    function myVue(option) {

    this._init(option)

    }

     

    myVue.prototype._init = function (options) { // 传了一个配置对象

    this.$options = options // options 为上面使用时传入的结构体,包括el,data,methods

    this.$el = document.querySelector(options.el) // el是 #app, this.$el是id为app的Element元素

    this.$data = options.data // this.$data = {number: 0}

    this.$methods = options.methods // this.$methods = {increment: function(){}}

     

     

    // _binding保存着model与view的映射关系,也就是我们前面定义的Watcher的实例。当model改变时,我们会触发其中的指令类更新,保证view也能实时更新

    this._binding = {}

     

    this._obsever(this.$data)

    this._compile(this.$el)

    }

     

    // 数据劫持:更新数据

    myVue.prototype._obsever = function (obj) {

    let _this = this

    Object.keys(obj).forEach((key) => { // 遍历obj对象

    if (obj.hasOwnProperty(key)) { // 判断 obj 对象是否包含 key属性

    _this._binding[key] = [] // 按照前面的数据,_binding = {number: []} 存储 每一个 new Watcher

    }

    let value = obj[key]

    if (typeof value === 'object') { //如果值还是对象,则遍历处理

    _this._obsever(value)

    }

    Object.defineProperty(_this.$data, key, {

    enumerable: true,

    configurable: true,

    get: () => { // 获取 value 值

    return value

    },

    set: (newVal) => { // 更新 value 值

    if (value !== newVal) {

    value = newVal

    _this._binding[key].forEach((item) => { // 当number改变时,触发_binding[number] 中的绑定的Watcher类的更新

    item.update() // 调 Watcher 实例的 update 方法更新 DOM

    })

    }

    }

    })

    })

    }

     

    // 订阅者模式: 绑定更新函数,实现对 DOM 元素的更新

    function Watcher(el, data, key, attr) {

    this.el = el // 指令对应的DOM元素

    this.data = data // this.$data 数据: {number: 0, count: 0}

    this.key = key // 指令绑定的值,本例如"number"

    this.attr = attr // 绑定的属性值,本例为"innerHTML","value"

     

    this.update()

    }

    // 比如 H3.innerHTML = this.data.number; 当number改变时,会触发这个update函数,保证对应的DOM内容进行了更新

    Watcher.prototype.update = function () {

    this.el[this.attr] = this.data[this.key]

    }

     

    // 将view与model进行绑定,解析指令(v-bind,v-model,v-clickde)等

    myVue.prototype._compile = function (el) { // root 为id为app的Element元素,也就是我们的根元素

    let _this = this

    let nodes = Array.prototype.slice.call(el.children) // 将为数组转化为真正的数组

    nodes.map(node => {

    if (node.children.length && node.children.length > 0) { // 对所有元素进行遍历,并进行处理

    _this._compile(node)

    }

    if (node.hasAttribute('v-click')) { // 如果有v-click属性,我们监听它的onclick事件,触发increment事件,即number++

    let attrVal = node.getAttribute('v-click')

    node.onclick = _this.$methods[attrVal].bind(_this.$data) // bind是使data的作用域与method函数的作用域保持一致

    }

     

    // 如果有v-model属性,并且元素是INPUT或者TEXTAREA,我们监听它的input事件

    if (node.hasAttribute('v-model') && (node.tagName === 'INPUT' || node.tagName === 'TEXTAREA')) {

    let attrVal = node.getAttribute('v-model')

     

    _this._binding[attrVal].push(new Watcher(

    node, // 对应的 DOM 节点

    _this.$data,

    attrVal, // v-model 绑定的值

    'value'

    ))

    node.addEventListener('input', () => {

    _this.$data[attrVal] = node.value // 使number 的值与 node的value保持一致,已经实现了双向绑定

    })

    }

    if (node.hasAttribute('v-bind')) {

    let attrVal = node.getAttribute('v-bind')

    _this._binding[attrVal].push(new Watcher(

    node,

    _this.$data,

    attrVal, // v-bind 绑定的值

    'innerHTML'

    ))

    }

    })

    }

     

     

    window.onload = () => { // 当文档内容完全加载完成会触发该事件,避免获取不到对象的情况

    new myVue({

    el: '#app',

    data: {

    number: 0,

    count: 0

    },

    methods: {

    increment() {

    this.number++

    },

    incre() {

    this.count++

    }

    }

    })

    }

    </script>

     

    </html>

    2.Proxy 实现双向数据绑定

    <!DOCTYPE html>

    <html>

     

    <head>

    <title>myVue</title>

    <style>

    #app{

    text-align: center;

    }

    </style>

    </head>

     

    <body>

    <div id="app">

    <form>

    <input type="text" v-model="number" />

    <button type="button" v-click="increment">增加</button>

    </form>

    <h3 v-bind="number"></h3>

    </div>

    </body>

    <script>

     

    // 定义一个myVue构造函数

    function myVue(option) {

    this._init(option)

    }

     

    myVue.prototype._init = function (options) { // 传了一个配置对象

    this.$options = options // options 为上面使用时传入的结构体,包括el,data,methods

    this.$el = document.querySelector(options.el) // el是 #app, this.$el是id为app的Element元素

    this.$data = options.data // this.$data = {number: 0}

    this.$methods = options.methods // this.$methods = {increment: function(){}}

     

    this._binding = {}

    this._obsever(this.$data)

    this._complie(this.$el)

     

    }

     

    // 数据劫持:更新数据

    myVue.prototype._obsever = function (data) {

    let _this = this

    let handler = {

    get(target, key) {

    return target[key]; // 获取该对象上key的值

    },

    set(target, key, newValue) {

    let res = Reflect.set(target, key, newValue); // 将新值分配给属性的函数

    _this._binding[key].map(item => {

    item.update();

    });

    return res;

    }

    };

    // 把代理器返回的对象代理到this.$data,即this.$data是代理后的对象,外部每次对this.$data进行操作时,实际上执行的是这段代码里handler对象上的方法

    this.$data = new Proxy(data, handler);

    }

     

    // 将view与model进行绑定,解析指令(v-bind,v-model,v-clickde)等

    myVue.prototype._complie = function (el) { // el 为id为app的Element元素,也就是我们的根元素

    let _this = this

    let nodes = Array.prototype.slice.call(el.children) // 将为数组转化为真正的数组

     

    nodes.map(node => {

    if (node.children.length && node.children.length > 0) this._complie(node)

    if (node.hasAttribute('v-click')) { // 如果有v-click属性,我们监听它的onclick事件,触发increment事件,即number++

    let attrVal = node.getAttribute('v-click')

    node.onclick = _this.$methods[attrVal].bind(_this.$data) // bind是使data的作用域与method函数的作用域保持一致

    }

     

    // 如果有v-model属性,并且元素是INPUT或者TEXTAREA,我们监听它的input事件

    if (node.hasAttribute('v-model') && (node.tagName === 'INPUT' || node.tagName === 'TEXTAREA')) {

    let attrVal = node.getAttribute('v-model')

     

    console.log(_this._binding)

    if (!_this._binding[attrVal]) _this._binding[attrVal] = []

    _this._binding[attrVal].push(new Watcher(

    node, // 对应的 DOM 节点

    _this.$data,

    attrVal, // v-model 绑定的值

    'value',

    ))

    node.addEventListener('input', () => {

    _this.$data[attrVal] = node.value // 使number 的值与 node的value保持一致,已经实现了双向绑定

    })

    }

    if (node.hasAttribute('v-bind')) {

    let attrVal = node.getAttribute('v-bind')

    if (!_this._binding[attrVal]) _this._binding[attrVal] = []

    _this._binding[attrVal].push(new Watcher(

    node,

    _this.$data,

    attrVal, // v-bind 绑定的值

    'innerHTML',

    ))

    }

     

    })

    }

    // 绑定更新函数,实现对 DOM 元素的更新

    function Watcher(el, data, key, attr) {

    this.el = el // 指令对应的DOM元素

    this.data = data // 代理的对象 this.$data 数据: {number: 0, count: 0}

    this.key = key // 指令绑定的值,本例如"num"

    this.attr = attr // 绑定的属性值,本例为"innerHTML","value"

     

    this.update()

    }

    // 比如 H3.innerHTML = this.data.number; 当number改变时,会触发这个update函数,保证对应的DOM内容进行了更新

    Watcher.prototype.update = function () {

    this.el[this.attr] = this.data[this.key]

    }

     

    window.onload = () => { // 当文档内容完全加载完成会触发该事件,避免获取不到对象的情况

    new myVue({

    el: '#app',

    data: {

    number: 0,

    count: 0

    },

    methods: {

    increment() {

    this.number++

    },

    incre() {

    this.count++

    }

    }

    })

    }

    </script>

     

    </html>

    3.将上面代码改成class的写法

    <!DOCTYPE html>

    <html>

     

    <head>

    <title>myVue</title>

    <style>

    #app{

    text-align: center;

    }

    </style>

    </head>

     

    <body>

    <div id="app">

    <form>

    <input type="text" v-model="number" />

    <button type="button" v-click="increment">增加</button>

    </form>

    <h3 v-bind="number"></h3>

    </div>

    </body>

    <script>

     

    class MyVue {

    constructor(options) { // 接收了一个配置对象

    this.$options = options // options 为上面使用时传入的结构体,包括el,data,methods

    this.$el = document.querySelector(options.el) // el是 #app, this.$el是id为app的Element元素

    this.$data = options.data // this.$data = {number: 0}

    this.$methods = options.methods // this.$methods = {increment: function(){}}

     

    this._binding = {}

    this._obsever(this.$data)

    this._complie(this.$el)

    }

    _obsever (data) { // 数据劫持:更新数据

    let _this = this

    let handler = {

    get(target, key) {

    return target[key]; // 获取该对象上key的值

    },

    set(target, key, newValue) {

    let res = Reflect.set(target, key, newValue); // 将新值分配给属性的函数

    _this._binding[key].map(item => {

    item.update();

    });

    return res;

    }

    };

    // 把代理器返回的对象代理到this.$data,即this.$data是代理后的对象,外部每次对this.$data进行操作时,实际上执行的是这段代码里handler对象上的方法

    this.$data = new Proxy(data, handler);

    }

    _complie(el) { // el 为id为app的Element元素,也就是我们的根元素

    let _this = this

    let nodes = Array.prototype.slice.call(el.children) // 将为数组转化为真正的数组

     

    nodes.map(node => {

    if (node.children.length && node.children.length > 0) this._complie(node)

    if (node.hasAttribute('v-click')) { // 如果有v-click属性,我们监听它的onclick事件,触发increment事件,即number++

    let attrVal = node.getAttribute('v-click')

    node.onclick = _this.$methods[attrVal].bind(_this.$data) // bind是使data的作用域与method函数的作用域保持一致

    }

     

    // 如果有v-model属性,并且元素是INPUT或者TEXTAREA,我们监听它的input事件

    if (node.hasAttribute('v-model') && (node.tagName === 'INPUT' || node.tagName === 'TEXTAREA')) {

    let attrVal = node.getAttribute('v-model')

    if (!_this._binding[attrVal]) _this._binding[attrVal] = []

    _this._binding[attrVal].push(new Watcher(

    node, // 对应的 DOM 节点

    _this.$data,

    attrVal, // v-model 绑定的值

    'value',

    ))

    node.addEventListener('input', () => {

    _this.$data[attrVal] = node.value // 使number 的值与 node的value保持一致,已经实现了双向绑定

    })

    }

    if (node.hasAttribute('v-bind')) {

    let attrVal = node.getAttribute('v-bind')

    if (!_this._binding[attrVal]) _this._binding[attrVal] = []

    _this._binding[attrVal].push(new Watcher(

    node,

    _this.$data,

    attrVal, // v-bind 绑定的值

    'innerHTML',

    ))

    }

     

    })

    }

    }

     

    class Watcher {

    constructor (el, data, key, attr) {

    this.el = el // 指令对应的DOM元素

    this.data = data // 代理的对象 this.$data 数据: {number: 0, count: 0}

    this.key = key // 指令绑定的值,本例如"num"

    this.attr = attr // 绑定的属性值,本例为"innerHTML","value"

    this.update()

    }

     

    update () {

    this.el[this.attr] = this.data[this.key]

    }

    }

     

     

     

    window.onload = () => { // 当文档内容完全加载完成会触发该事件,避免获取不到对象的情况

    new MyVue({

    el: '#app',

    data: {

    number: 0,

    count: 0

    },

    methods: {

    increment() {

    this.number++

    },

    incre() {

    this.count++

    }

    }

    })

    }

    </script>

     

    </html>

    以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

    1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
    2. 分享目的仅供大家学习和交流,请不要用于商业用途!
    3. 如果你也有好源码或者教程,可以到审核区发布,分享有金币奖励和额外收入!
    4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
    5. 如有链接无法下载、失效或广告,请联系管理员处理!
    6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需!
    7. 如遇到加密压缩包,默认解压密码为"www.qitupic.com",如遇到无法解压的请联系管理员!

    成华区美乐滋网络工作室|奇兔源码网 » Vue Object.defineProperty及ProxyVue实现双向数据绑定

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    站壳网
    一个高级程序员模板开发平台