精华内容
下载资源
问答
  • Vue.js 最核心的功能有两个,一是响应式的数据绑定系统,二是组件系统。本文仅探究双向绑定是怎样实现的。...一、双向绑定的实现原理 访问器属性是对象中的一种特殊属性,它不能直接在对象中设置,而必须通过 d...

     Vue.js 最核心的功能有两个,一是响应式的数据绑定系统,二是组件系统。本文仅探究双向绑定是怎样实现的。先讲涉及的知识点,再用简化得不能再简化的代码实现一个简单的 hello world 示例。

    一、双向绑定的实现原理

           访问器属性是对象中的一种特殊属性,它不能直接在对象中设置,而必须通过 defineProperty() 方法单独定义。

           var obj = { };

           // 为obj定义一个名为 hello 的访问器属性

           Object.defineProperty(obj, "hello", {

             get: function () {return sth},

             set: function (val) {/* do sth */}

           })

           obj.hello // 可以像普通属性一样读取访问器属性

           访问器属性的"值"比较特殊,读取或设置访问器属性的值,实际上是调用其内部特性:get和set函数。

           obj.hello // 读取属性,就是调用get函数并返回get函数的返回值

           obj.hello = "abc" // 为属性赋值,就是调用set函数,赋值其实是传参

          get 和 set 方法内部的 this 都指向 obj,这意味着 get 和 set 函数可以操作对象内部的值。另外,访问器属性的会"覆盖"同名的普通属性,因为访问器属性会被优先访问,与其同名的普通属性则会被忽略。

    二、极简双向绑定的实现

      此例实现的效果是:随文本框输入文字的变化,span 中会同步显示相同的文字内容;在js或控制台显式的修改 obj.hello 的值,视图会相应更新。这样就实现了 model => view 以及 view => model 的双向绑定。

    img_161cf4bab06c9e097998f72e098f6555.png
    img_00b68a21f4e461603ab9a66e20655076.png

      以上就是 Vue 实现双向绑定的基本原理。

    二、双向绑定的实现 

    回顾一下,每当 new 一个 Vue,主要做了两件事:第一个是监听数据:observe(data),第二个是编译 HTML:nodeToFragement(id)。

           在监听数据的过程中,会为 data 中的每一个属性生成一个主题对象 dep。

           在编译 HTML 的过程中,会为每个与数据绑定相关的节点生成一个订阅者 watcher,watcher 会将自己添加到相应属性的 dep 中。

           我们已经实现:修改输入框内容 => 在事件回调函数中修改属性值 => 触发属性的 set 方法。

           接下来我们要实现的是:发出通知 dep.notify() => 触发订阅者的 update 方法 => 更新视图。

           这里的关键逻辑是:如何将 watcher 添加到关联属性的 dep 中。

    参考文件:https://www.cnblogs.com/kidney/p/6052935.html?utm_source=gold_browser_extension

    展开全文
  • 转自:https://www.w3cplus.com/vue/vue-two-way-binding-object-defineproperty.html2016年,Vue.js可谓是大放异彩,以迅雷不及掩耳之势赶React超Angular,用惯jQuery我一下子被Vue开篇介绍的双向绑定给惊着了!...

    转自:https://www.w3cplus.com/vue/vue-two-way-binding-object-defineproperty.html

    2016年,Vue.js可谓是大放异彩,以迅雷不及掩耳之势赶React超Angular,用惯jQuery的我一下子被Vue开篇介绍的双向绑定给惊着了!一下子按捺不住好奇心,打算刨根究底,看看双向绑定到底是怎样实现的?

    目标

    • 第二个版本:更新AngularJS双向绑定的实现原理
    • 第三个版本:更新BackboneJS双向绑定的实现原理
    • 第四个版本:更新ReactJS双向绑定的实现原理

    前言

    • 发布者-订阅者模式(Backbone.js): 一般通过subpub的方式实现数据和视图的绑定监听,更新数据方式通常做法是 vm.set('property', value)
    • 数据劫持(Vue.js): 通过Object.defineProperty()来劫持各个属性的settergetter,在数据变动时发布消息给订阅者,触发相应的监听回调。(采用数据劫持结合发布者-订阅者模式的方式)

    关于数据绑定

    单向数据绑定

    目前前端框架大都采用MV*的模式,其中M(model)指的是模型,也就是数据;Vview)指的是视图,也就是页面展现的部分。通常,我们需要编写代码,将从服务器获取的数据进行“渲染”并展现到视图上。每当数据有变更时,我们会再次进行渲染,从而更新视图,使得视图与数据保持一致。也就是:

    Vue双向绑定的实现原理Object.defineproperty

    而另一方面,页面也会通过用户的交互,产生状态、数据的变化,这个时候,我们则编写代码,将视图对数据的更新同步到数据,以致于同步到后台服务器。也就是:

    Vue双向绑定的实现原理Object.defineproperty

    双向数据绑定

    Vue双向绑定的实现原理Object.defineproperty

    Backbonejs:Model 到 View 的数据传递,可以在 View 中监听 Model 的 change 事件,每当 Model 更新,View 中重新执行 render。而 View 到 Model 的数据传递,可以监听 View 对应的 DOM 元素的各种事件,在检测到 View 状态变更后,将变更的数据发送到 Model。

    AngularJS:采用“脏值检测”的方式,数据发生变更后,对于所有的数据和视图的绑定关系进行一次检测,识别是否有数据发生了改变,有变化进行处理,可能进一步引发其他数据的改变,所以这个过程可能会循环几次,一直到不再有数据变化发生后,将变更的数据发送到视图,更新页面展现。如果是手动对 ViewModel 的数据进行变更,为确保变更同步到视图,需要手动触发一次“脏值检测”。

    VueJS:采用 ES5 提供的 Object.defineProperty() 方法,监控对数据的操作,从而可以自动触发数据同步。并且,由于是在不同的数据上触发同步,可以精确的将变更发送给绑定的视图,而不是对所有的数据都执行一次检测。

    VueJS双向数据绑定实现

    Object.defineProperty简单应用

    var obj = {};
    Object.defineProperty(obj, 'hello', {
        get: function() {
            console.log('get方法获取值');
        },
        set: function(val) {
            console.log('set方法设置的值为:' + val);
        }
    });
    obj.hello; // get方法获取值
    obj.hello = 'Hello World';
    

    实现数据和视图的联动,即实现双向绑定,听起来是不是很牛叉?并且Vue.js和Avalon.js 都是通过它实现双向绑定的。是不是有点小激动呢?所以更有必要了解一下了几行代码看它怎么用?

    基本用法

    var a = {};
    Object.defineProperty(a, 'b', {
        value: 123
    });
    console.log(a.b); // 123
    

    很简单,它接受三个参数,而且都是必填的。

    参数介绍:

    • 第一个参数:目标对象
    • 第二个参数:需要定义的属性或方法的名字。
    • 第三个参数:目标属性所拥有的特性。

    前两个参数不多说了,一看代码就懂,主要看第三个参数,看看有哪些取值。

    • value:属性的值。
    • writable:如果为false,属性的值就不能被重写,只能为只读了。
    • configurable:总开关,一旦为false,就不能再设置他的(valuewritableconfigurable)。
    • enumerable:是否能在for...in循环中遍历出来或在Object.keys中列举出来。
    • get:见下面例子。
    • set:见下面例子。

    接下来,该是到了用实例说话的时候了。

    var a = {};
    Object.defineProperty(a, 'b', {
        value: 123
    });
    console.log(a.b); // 123
    

    我们只设置了 value,别的并没有设置,但是 第一次的时候 可以简单的理解为(暂时这样理解)它会默认帮我们把writableconfigurableenumerable、都设上值,而且值还都是false。也就是说,上面代码和下面是等价的的( 仅限于第一次设置的时候):

    var a = {};
    Object.defineProperty(a, 'b', {
        value: 123,
        writable: false,
        enumerable: false,
        configurable: false
    });
    console.log(a.b); //123
    
    configurable

    总开关,第一次设置 false 之后,,第二次什么设置也不行了,比如说:

    var a = {};
    Object.defineProperty(a, 'b', {
        configurable: false
    });
    Object.defineProperty(a, 'b', { // Uncaught TypeError: Cannot redefine property: b(…)
        configurable: true
    });
    

    如果第一次不设置,第二次再设置同样会报错。

    writable

    如果设置为fasle,就变成只读了。。

    var a = {};
    Object.defineProperty(a, 'b', {
        value: 123,
        writable: false
    });
    console.log(a.b); // 打印 123
    a.b = 124; // 没有错误抛出(在严格模式下会抛出,即使之前已经有相同的值)
    console.log(a.b); // 打印 123, 赋值不起作用。
    
    enumerable

    属性特性 enumerable 定义了对象的属性是否可以在 for...in 循环和 Object.keys() 中被枚举。

    var a = {};
    Object.defineProperty(a, "b", {
        value: 3445,
        enumerable: true
    });
    console.log(Object.keys(a)); // 打印["b"]
    

    改为false

    var a = {};
    Object.defineProperty(a, "b", {
        value: 3445,
        enumerable: false //注意咯这里改了
    });
    console.log(Object.keys(a)); // 打印[]
    

    for...in 类似,不赘述了。

    set 和 get

    在参数中不能 同时 设置访问器 (get 和 set) 和 wriable 或 value,否则会错,就是说想用(get 和 set),就不能用(wriable 或 value中的任何一个)

    set 和 get ,他俩干啥用的的?

    var a = {};
    Object.defineProperty(a, 'b', {
        set: function(newValue) {
            console.log('你要赋值给我,我的新值是:'+ newValue);
        },
        get: function() {
            console.log("你取我的值");
            return 2; //注意这里,我硬编码返回2
        }
    });
    a.b = 1; //打印 你要赋值给我,我的新值是1
    console.log(a.b); //打印 你取我的值 2
    

    简单来说,这个 b 赋值或者取值的时候会分别触发 set 和 get 对应的函数。

    现在开始使用Object.defineProperty实现数据和视图的联动。

    <!-- HTML -->
    <div> 
        你好,<span id='nickName'></span>
        <div id="introduce"></div>
    </div>
    
    // JavaScript
    // 视图控制器
    var userInfo = {};
    Object.defineProperty(userInfo, "nickName", {
        get: function() {
            return document.getElementById('nickName').innerHTML;
        },
        set: function(nick) {
            document.getElementById('nickName').innerHTML = nick;
        }
    });
    Object.defineProperty(userInfo, "introduce", {
        get: function() {
            return document.getElementById('introduce').innerHTML;
        },
        set: function(introduce) {
            document.getElementById('introduce').innerHTML = introduce;
        }
    });
    userInfo.nickName = "xxx";
    userInfo.introduce = "我是xxx,我来自云南,..."
    

    设置userInfonickName属性时会调用set方法,更新DOM节点的HTML。

    关于 Object.defineProperty() 小结

    首先我们得先知道,ECMAScript中有两种属性:数据属性访问器属性

    数据属性

    • [[Configurable]]:表示能否修改属性。默认值为true
    • [[Enumerable]]:表示属性是否可枚举,也就是是否可以通过for-in循环返回属性。默认值为true
    • [[Writable]]:表示能否修改属性的值。默认值为true
    • [[value]]:包含这个属性的值.读取属性的时候就是通过这里开始读。默认值为undefined

    接下来我们看看例子

    var person = {
    
    }
    

    我们要是想修改默认属性的值该怎么做呢?这时候就要用到标题上所说的方法了Object.defineProperty(obj,prop,descriptor):

    • obj:需要定义的属性的对象
    • prop:需要定义(创建)或修改的属性的名字
    • descriptor:需要定义或修改的属性的描述符,可以是一个对象

    具体内容可以参考MDN

    var person = {
    
    }
    // 这里我们把这些数据属性显示的写了出来
    Object.defineProperty(person,'a',{
        configurable:true, //可以修改默认属性
        enumerable:true,   //可以被枚举
        writable:true,     //可以修改这个属性的值
        value:1            //定义一个初始的值为1
    })
    console.log(person)    //Object {a: 1}
    person.a=2
    console.log(person)    //Object {a: 2}
    for(var k in person){
        console.log(k)     //a,可以被枚举
    }
    

    现在我们来修改一下默认的值

    Object.defineProperty(person,'a',{
        configurable:true,
        enumerable:false,
        writable:false,
        value:1
    })
    console.log(person)  //Object {a: 1}
    person.a=2
    console.log(person)  //Object {a: 1} 因为writable值被设置为false了,所以不可以写,严格模式下会报错
    for(var k in person){
        console.log(k)   //不起作用,因为enumerable的值被设置为false了
    }
    

    我们试试吧configurable的值改为false

    Object.defineProperty(person,'a',{
        configurable:false,    //为false的时候不允许修改默认属性了
    })
    // ===============================
    // 改为false之后再试试修改其他属性
    Object.defineProperty(person,'a',{
        configurable:true,
        enumerable:true,
        writable:true,
        value:1
    })
    //woa,控制台直接报错了!连想把false值改回true都不行!也就是说,这个改动是一次性了!
    //也就是说,你可以使用Object.defineProperty()方法无限修改同一个属性,但是当把configurable改为false之后就有限制了
    

    接下来我们看看访问器属性。

    访问器属性

    • [[Configurable]]:表示能否修改属性。默认值为true
    • [[Enumerable]]:表示属性是否可枚举,也就是是否可以通过for-in循环返回属性。默认值为true
    • [[Get]]:在读取属性时调用的函数,默认值为undefined
    • [[Set]]:在设置属性的时候调用的函数,默认值为undefined

    访问器属性不能直接定义!只能通过Object.defineProperty()来定义。我们看看例子

    var person = {
        a:1
    }
    Object.defineProperty(person,'a',{
        get(){
            return 3          //当访问这个属性的时候返回3
        },
        set(val){
            console.log(val)  //当设置这个属性的时候执行,val是设置的值
        }
    })
    
    person.a                  // 3,我们明明写的是a:1,怎么返回的3呢?这就是get()的威力了
    person.a = 5              // 5,相应的设置的时候执行了set()函数
    

    我们来模拟一个访问和设置的默认行为

    var person = {
        a:1
    }
    // 注:里面的this指向ogj(person)
    Object.defineProperty(person,'a',{
        get(){
            return this.a 
        },
        set(val){
            this.a = val 
        }
    })
    //我们想当然的这么写.
    person.a       //Uncaught RangeError: Maximum call stack size exceeded
    // 什么,溢出了?这是为什么?
    // 哦~原来是这么写的话会造成循环引用,狂call不止
    // 我们看下流程:
    // person.a → get.call(person) → this.a → person.a  → get.call(person) → this.a......
    

    我们得改一下

    var person = {
        a:1
    }
    Object.defineProperty(person,'a',{
        get(){
            return this._a || 1 //定义一个新的属性和一个默认值
        },
        set(val){
            this._a = val 
        }
    })
    person.a    // 1
    person.a=2  // 2
    person.a    // 2
    

    这样就好了。

    小结

    • 当把configurable值设置为false后,就不能修改任何属性了,包括自己本身这个属性
    • 想用访问器属性模拟默认行为的话,必须得在里面新顶一个属性,不然的话会造成循环引用
    • 这对我们了解对象的工作机制很有作用,虽然可能很少会用到

    Vue 事件驱动和依赖追踪

    之前关于 Vue 数据绑定原理的一点分析,最近需要回顾,就顺便发到随笔上了。

    在之前实现一个自己的MVVM中,用 setter 来观测model,将界面上所有的 viewModel 绑定到 model 上。 当model改变,更新所有的viewModel,将新值渲染到界面上 。同时监听界面上通过v-model 绑定的所有 input,并通过 addEventListener事件将新值更新到 model 上,以此来完成双向绑定 。

    但是那段程序除了用来理解 defineProperty,其它一文不值。

    • 没有编译节点 。
    • 没有处理表达式依赖 。

    这里我将解决表达式依赖这个问题,Vue 模板的编译我会在下一节介绍 。

    为数据定义 getter & setter

    class Observer {
        constructor(data) {
            this._data = data;
            this.walk(this._data);
        }
    
        walk(data) {
            Object.keys(data).forEach((key) => { 
                this.defineRective(data, key, data[key]) 
            })
        };
        defineRective(vm, key, value) {
            var self = this;
            if (value && typeof value === "object") {
                this.walk(value);
            }
            Object.defineProperty(vm, key, {
                get: function() {
                    return value;
                },
                set: function(newVal) {
                    if (value != newVal) {
                        if (newVal && typeof newVal === "object") {
                            self.walk(newVal);
                        }
                        value = newVal;
                    }
                }
            })
        }
    }
    
    module.exports = Observer;
    

    这样,就为每个属性添加了 getter 和 setter ,当属性是一个对象,那么就递归添加。

    一旦获取属性值或者为属性赋值就会触发 get 或 set ,当触发了 set,即model变化,就可以发布一个消息,通知所有viewModel 更新。

    defineRective(vm, key, value) {
        // 将这个属性的依赖表达式存储在闭包中。
        var dep = new Dep();
        var self = this;
        if (value && typeof value === "object") {
            this.walk(value);
        }
        Object.defineProperty(vm, key, {
            get: function() {
                return value;
            },
            set: function(newVal) {
                if (value != newVal) {
                    if (newVal && typeof newVal === "object") {
                        self.walk(newVal);
                    }
                    value = newVal;
                    // 通知所有的 viewModel 更新
                    dep.notify();
                }
            }
        })
    }
    

    那么怎么定义 Dep 呢??

    class Dep {
        constructor() {
            // 依赖列表
            this.dependences = [];
        }
        // 添加依赖
        addDep(watcher) {
            if (watcher) {
                this.dependences.push(watcher);
            }
        }
        // 通知所有依赖更新
        notify() {
            this.dependences.forEach((watcher) => {
                watcher.update();
            })
        }
    }
    
    module.exports = Dep;
    

    这里的每个依赖就是一个Watcher 。看看如何定义 Watcher。这里每一个 Watcher 都会有一个唯一的id号,它拥有一个表达式和一个回调函数 。

    比如 表达式 a +b ; 会在get 计算时 访问 a 与 b , 由于 JavaScript是单线程,任一时刻只有一处JavaScript代码在执行, 用Dep.target 作为一个全局变量来表示当前 Watcher 的表达式,然后通过 compute 访问 a ,b ,触发 a 与b 的getter,在 getter 里面将 Dep.target 添加为依赖 。

    一旦 a 与 b 的set 触发,调用 update 函数,更新依赖的值 。

    var uid = 0;
    class Watcher {
        constructor(viewModel, exp, callback) {
            this.viewModel = viewModel;
            this.id = uid++;
            this.exp = exp;
            this.callback = callback;
            this.oldValue = "";
            this.update();
        }
    
        get() {
            Dep.target = this;
            var res = this.compute(this.viewModel, this.exp);
            Dep.target = null;
            return res;
        }
    
        update() {
            var newValue = this.get();
            if (this.oldValue === newValue) {
                return;
            }
            // callback 里进行Dom 的更新操作
            this.callback(newValue, this.oldValue);
            this.oldValue = newValue;
        }
    
        compute(viewModel, exp) {
            var res = replaceWith(viewModel, exp);
            return res;
        }
    }
    
    module.exports = Watcher;
    

    由于当前表达式需要在 当前的model下面执行,所以 采用replaceWith 函数来代替 with ,具体可以查看另一篇随笔 JavaScript 中 with 的替代语法

    通过get 添加依赖:

    Object.defineProperty(vm, key, {
        get: function() {
            var watcher = Dep.target;
            if (watcher && !dep.dependences[watcher.id]) {
                dep.addDep(watcher);
            }
            return value;
        },
        set: function(newVal) {
            if (value != newVal) {
                if (newVal && typeof newVal === "object") {
                    self.walk(newVal);
                }
                value = newVal;
                dep.notify();
            }
        }
    })
    

    这种添加依赖的方式实在太巧妙了 。这里我画了一个图来描述

    Vue双向绑定的实现原理Object.defineproperty

    最后通过一段代码简单测试一下

    const Observer = require('./Observer.js');
    const Watcher = require('./watcher.js');
    var data = {
        a: 10,
        b: {
            c: 5,
            d: {
                e: 20,
            }
        }
    }
    
    var observe = new Observer(data);
    
    var watcher = new Watcher(data, "a+b.c", function(newValue, oldValue) {
        console.log("new value is  " + newValue);
        console.log("oldValue is  " + oldValue);
    });
    console.log("\r\n");
    console.log("a has changed to 50,then the expr should has value 55");
    data.a = 50;
    
    console.log("\r\n");
    console.log("b.c has changed to 50,then the expr should has value 122");
    data.b.c = 72;;
    
    console.log("\r\n");
    console.log("b.c has reseted an object,then the expr should has value 80");
    data.b = { c: 30 }
    

    Vue双向绑定的实现原理Object.defineproperty

    OK 大功告成。

    著作权归作者所有。
    商业转载请联系作者获得授权,非商业转载请注明出处。
    原文: https://www.w3cplus.com/vue/vue-two-way-binding-object-defineproperty.html © w3cplus.com
    展开全文
  •  vue数据双向绑定是通过(数据劫持)+(发布者-订阅者模式)方式来实现的,而所谓数据劫持就是通过Object.defineProperty() 来实现的,所谓Object.defineProperty( )是用来做什么?简单点来说就是给一个...

    vue数据双向绑定原理

       vue数据双向绑定是通过(数据劫持)+(发布者-订阅者模式)的方式来实现的,而所谓的数据劫持就是通过Object.defineProperty()

    来实现的,所谓的Object.defineProperty( )是用来做什么的?简单点来说就是给一个对象添加get和set方法,在我们通过类似于obj.

    attribute获取属性的时候会调用get方法,通过obj.attribute = ***设置属性的时候会调用set方法,我们通过重写这个get,set方法就可以达到我们

    不可告人的目的-实现数据劫持。

      举个栗子:

    var Person= {}
    var name = '';
    Object.defineProperty(Person, 'name', {
      set: function (value) {
        name = value;
        console.log('这个人的名字叫:' + value);
      },
      get: function () {
        return '<' + name + '>'
      }
    })
     
    Person.name = 'vue';  //访问属性调用set方法输出:这个人的名字叫vue
    console.log(Person.name);  //设置属性调用set方法得到:<vue>
    

      接下来我们来简单分析实现一个vue的双向绑定:

    实现分析

    我们已经知道实现数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。如果属性发上变化了,就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。接着,我们还需要有一个指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令对应初始化成一个订阅者Watcher,并替换模板数据或者绑定相应的函数,此时当订阅者Watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。因此接下去我们执行以下3个步骤,实现数据的双向绑定:

    1.实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。

    2.实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。

    3.实现一个消息订阅器Dep,专门统计管理订阅者,并且每一个监听的属性持有一个dep的引用,在属性变化后通过dep去更新Watcher,从而更新师徒。

    4.实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。

    流程图如下:

    具体实现

          github链接:vue数据双向绑定原理分析和简单实现

          参考博客:http://www.cnblogs.com/libin-1/p/6893712.html

     

    转载于:https://www.cnblogs.com/barryzhang/p/10396372.html

    展开全文
  • Vue 采用数据劫持结合发布者-订阅者模式...要实现mvvm的双向绑定,就必须要实现以下几点: Compile—指令解析系统,对每个元素节点指令进行扫描和解析,根据指令模板替换数据,以及绑定相应更新函数 ...

    Vue 采用数据劫持结合发布者-订阅者模式的方式来实现数据的响应式,通过Object.defineProperty来劫持数据的setter,getter,在数据变动时发布消息给订阅者,订阅者收到消息后进行相应的处理。

     

    要实现mvvm的双向绑定,就必须要实现以下几点:

    1. Compile—指令解析系统,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数

    2. Observer—数据监听系统,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者

    3. Dep+Watcher—发布订阅模型,作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图。 Dep是发布订阅者模型中的发布者:get数据的时候,收集订阅者,触发Watcher的依赖收集;set数据时发布更新,通知Watcher 。一个Dep实例对应一个对象属性或一个被观察的对象,用来收集订阅者和在数据改变时,发布更新。 Watcher是发布订阅者模型中的订阅者:订阅的数据改变时执行相应的回调函数(更新视图或表达式的值)。一个Watcher可以更新视图,如html模板中用到的{{test}},也可以执行一个$watch监督的表达式的回调函数(Vue实例中的watch项底层是调用的$watch实现的),还可以更新一个计算属性(即Vue实例中的computed项)。

    mvvm入口函数,整合以上三者,具体如图所示:

     

     

     

    compire可以参看《双向绑定的实现原理》,这里不做过多解读。

    Observer,Dep和Watcher类的实现及原理,推荐阅读《Vue源码解读一:Vue数据响应式原理》,一般开发者需要关注:

     

    收集依赖指的是谁收集依赖,依赖又是指的什么?

    Watcher,作用是分割表达式,收集依赖并且在值变化的时候调用回调函数。

    我们上面说过一个Dep对应着一个数据(这个数据可能是:对象的属性、一个对象、一个数组);一个Watcher对应可以是一个模板也可以是一个$watch对应的表达式、函数等,无论那种情况,他们都依赖于data里面的数据,所以这里说的依赖其实就是模板或表达式所依赖的数据,对应着相关数据的Dep。

     

    Watcher的四个使用场景

    • 第一种:观察模板中的数据

    • 第二种:观察创建Vue实例时watch选项里的数据

    • 第三种:观察创建Vue实例时computed选项里的数据所依赖的数据

    • 第四种:调用$watch api观察的数据或表达式

    Watcher只有在这四种场景中,Watcher才会收集依赖,更新模板或表达式,否则,数据改变后,无法通知依赖这个数据的模板或表达式:

    所以在解决数据改变,模板或表达式没有改变的问题时,可以这么做:

    首先仔细看一看数据是否在上述四种应用场景中,以便确认数据已经收集依赖;其次查看改变数据的方式,确定这种方式会使数据的改变被拦截(关于这一点,上面Obsever相关内容中说的比较多)。

     

    对于Observer需要注意的是:

     

     

     

    getter/setter方法拦截数据的不足

    1. 当对象增删的时候,是监控不到的。比如:data={a:"a"},这个时候如果我们设置data.test="test",这个时候是监控不到的。因为在observe data的时候,会遍历已有的每个属性(比如a),添加getter/setter,而后面设置的test属性并没有机会设置getter/setter,所以检测不到变化。同样的,删除对象属性的时候,getter/setter会跟着属性一起被删除掉,拦截不到变化。 vm.$set/Vue.set和vm.$delete/Vue.delete这样的api来解决这个问题

    2. getter/setter是针对对象的对于数组的修改(push(),pop(),shift(),unshift(),splice(),sort(),reverse())等方法,arr发生了改变,此时是需要更新视图的,但是arr的getter/setter拦截不到变化(只有在赋值的时候才会调用setter,比如:arr=[6,7,8])。 对于这种情况,vue通过改写Array的默认方法,在调用这些方法的时候发布更新消息。一般无需关注,但是对于如下两种情况:

    3. 当你利用索引直接设置一个项时,例如:vm.items[indexOfItem] = newValue

    4. 当你修改数组的长度时,例如:vm.items.length = newLength

    需要vm.$set/Vue.set和vm.items.splice(newLength)解决,具体参看官方说明

    1. 每次给数据设置值得时候,都会调用setter函数,这个时候就会发布属性更新消息,即使数据的值没有变。从性能方便考虑我们肯定希望值没有变化的时候,不更新模板。(像Angular这样把批量操作延时到一次更新,一次做完所有数据变更,然后整体应用到界面上)

     

     

    整体感知virtual DOM

    virtual DOM分为三个步骤:

    1.createElement(): 用 JavaScript对象(虚拟树) 描述 真实DOM对象(真实树)

    2.diff(oldNode, newNode) : 对比新旧两个虚拟树的区别,收集差异

    3.patch() : 将差异应用到真实DOM树

     

    有的时候 第二步 可能与 第三步 合并成一步(Vue 中的patch就是这样)

     

    Vue的实现原理总结

    1. 首先,在实例化的过程中,把一个普通 JavaScript 对象传给 Vue 实例的 data选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。

    2. Dep 是一个依赖收集器。data 下的每一个属性都有一个唯一的 Dep 对象,在 get 中收集仅针对该属性的依赖,然后在 set 方法中触发所有收集的依赖。

    3. 在Watcher中对表达式求值,从而触发数据的get。在求值之前将当前Watch实例设置到全局,使用pushTarget(this)方法。

    4. 在get()中收集依赖,this.subs.push(sub),set的时候触发回调Dep.notify()。

    5. Compile中首先将template或el编译成render函数,render函数返回一个虚拟DOM对象(将模板转为 render 函数的时候,实际是先生成的抽象语法树(AST),再将抽象语法树转成的 render 函数)

    6. 当 vm._render 执行的时候,所依赖的变量就会被求值,并被收集为依赖。按照Vue中 watcher.js 的逻辑,当依赖的变量有变化时不仅仅回调函数被执行,实际上还要重新求值,即还要执行一遍

    7. 如果还没有 prevVnode 说明是首次渲染,直接创建真实DOM。如果已经有了 prevVnode 说明不是首次渲染,那么就采用 patch 算法进行必要的DOM操作。这就是Vue更新DOM的逻辑。

    最后,安利下:《Vue.js 技术揭秘

    参考文章

    梳理Vue2.0双向绑定的实现原理

    文自《梳理vue双向绑定的实现原理 - vue入坑总结 - 周陆军的个人网站》,如有不妥之前,请源站留言告知。

    展开全文
  •     实现MVVM通用方式要实现mvvm的双向绑定,就必须要实现以下几点:1、实现一个数据监听器Observer,能够对数据对象所有属性进行监听,如有     Vue 采用数据劫持结合发布者-订阅者模式方式来实现...
  • 设计模式 ...在不改变对象自身基础上,在程序运行期间给对象动态添加职责 //看一个简单例子: Function.prototype.fn = function(fn){ var self = this; return function(){ self.apply(t...
  • 监听器Observer和订阅者Watcher 实现简单版Vue的过程,主要实现{{}}、v-model和事件指令功能 ... 实现数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性...
  • 了解Object.defineProperty() github源码 Object.defineProperty()方法直接在一个对象上定义一个新属性...vueJS采用 ES5 提供 Object.defineProperty() 方法,监控对数据操作,从而可以自动触发数据同步。并且...
  • 补充指令解析器compile github源码 补充下HTML节点类型知识: 元素节点 Node.ELEMENT_NODE(1) 属性节点 Node.ATTRIBUTE_NODE(2) 文本节点 Node.TEXT_NODE(3) ...
  • 主要介绍了Angular和Vue双向数据绑定的实现原理(重点是vue的双向绑定),非常不错,具有参考借鉴价值,感兴趣的朋友一起看看吧
  • 主要介绍了Vue双向绑定实现原理与方法,结合实例形式详细分析了发布者-订阅者模式、脏值检查、数据劫持与双向绑定相关实现技巧,需要朋友可以参考下
  • 主要为大家详细介绍了Vue数据双向绑定底层实现原理,文中示例代码介绍非常详细,具有一定参考价值,感兴趣小伙伴们可以参考一下
  • 关于Vue双向绑定的实现原理,我认为这位前辈写的这篇文章真的太好了,在这里分享一下(顺便希望一下之后我也能将自己对这块知识的理解写成一篇博客) https://www.cnblogs.com/canfoo/p/6891868.html ...
  • Vue 双向绑定的原理实现Demo

    千次阅读 2017-03-01 23:41:39
    Vue双向绑定的原理Vue用了一段时间了,一直没有纠结过它的原理,今天看了一篇很不错的文章:Vue.js双向绑定的实现原理,跟着敲了一遍,发现其中有意思的地方还是很多的,一些知识我之前都没有接触过,这里要好好整理...
  • 1.首先我们先了解下实现Vue双向绑定的核心方法Object.defineProperty(obj, prop, descriptor) obj: 目标对象; prop: 目标对象属性的名称; descriptor: 目标对象属性的描述符; 该方法的详细解释请移步这里。 2....
  • VUE的双向绑定实现原理MVVM vue.js是JavaScript MVVM (Model-View-ViewModel)库。 vue数据双向绑定实现原理就是MVVM模式。 在讲MVVM模式之前,可以先了解下Mqtt协议设计思路,通过各个终端与Mqtt...
  • 本篇文章主要介绍了解析Vue2.0双向绑定实现原理,小编觉得挺不错,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • vue数据双向绑定的实现原理是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。懒加载原理?一般来讲,...
  • 随着自己在前端越来越深入,也越来越喜欢前端。... 这一次自己摸索出vue双向数据绑定的原理,不足的地方希望大家给予指正 class Mvvm { constructor({el,data}){ this.$el=document.querySelector(el...
  • 目录vue双向绑定原理实现面试解释 vue双向绑定原理实现 数据双向绑定原理简单概括话就是: View层影响Model层是通过对 ‘keyup’ 等事件监听。 Model层影响View层是通过 Object.defineProperty( ) 方法劫持...
  • 使用vue也好有一段时间了,虽然对其双向绑定原理也有了解个大概,但也没好好探究下其原理实现,所以这次特意花了几晚时间查阅资料和阅读相关源码,自己也实现一个简单版vue的双向绑定版本,先上个成果图来吸引各位:...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,755
精华内容 702
关键字:

vue双向绑定的实现原理

vue 订阅