美文网首页web 前端Vuejs Javascript 知识点逐步分析与解剖
vue.js原理和实现双向绑定MVVM(1)--Observer

vue.js原理和实现双向绑定MVVM(1)--Observer

作者: Ziksang | 来源:发表于2017-01-06 17:54 被阅读1545次

    今天是周五,今天一行代码也没有写,心血来潮想看看vue.js的实现原理和双向绑定MVVM。来吧BB那么多费话真没用,讲一点实现基础理论是必要的

    我相信大家对Angular.JS的成为下一代最主流的MVC架构还记忆在心,但好景不长,angular2.0 vue.js react 纷纷居上。为什么我把vue.js夹在中间
    因为1.0抄angular的,2.0抄react的,这我也能理解尤大神,没办法,一个想搞过一个团队,只有抄,说错了,(借鉴)我只能说真心好,快,轻。

    数据劫持:vue.js 则是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()
    来劫持各个属性的setter
    ,getter
    ,在数据变动时发布消息给订阅者,触发相应的监听回调。

    已经了解到vue是通过数据劫持的方式来做数据绑定的,其中最核心的方法便是通过Object.defineProperty()
    来实现对属性的劫持,达到监听数据变动的目的,无疑这个方法是本文中最重要、最基础的内容之一,要实现mvvm的双向绑定,就必须要实现以下几点:
    1、实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者
    2、实现一个指令解析器Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
    3、实现一个Watcher,作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图4、mvvm入口函数,整合以上三者

    我写的文章永远是详解,不然就不是我的个性了,什么github,这种里面的东西是治标不治本,拿来用用可以,换种自己想要的东西,就是一头苦恼,所以这次分享内面内容分n次说,我也不确定几次!反正就是详细说!我也不知道最后分几部!就当看抗日剧吧

    我做解析还是喜欢先把要用到的知识点先拿出来解析一下,结合一下之后你就会发现,看代码一点都不难

    第一章Object.keys()和 Object.defineProperty用法

    Object.keys()
    --引用MDN
    Object.keys()方法会返回一个由给定对象的所有可枚举自身属性的属性名组成的数组,数组中属性名的排列顺序和使用for-in
    循环遍历该对象时返回的顺序一致 (顺序一致不包括数字属性)(两者的主要区别是 for-in 还会遍历出一个对象从其原型链上继承到的可枚举属性)。

    返回参数
    返回可枚举的自身属性的属性名

    描述
    Object.keys
    返回一个所有元素为字符串的数组,其元素来自于从给定的对象上面可直接枚举的属性。这些属性的顺序与手动遍历该对象属性时的一致。

    var obj = {
        name : "ziksang",
        age : 20
    }//声明一个对象
    console.log(Object.keys(obj))
    //["name", "age"]打印出来的是对象的属性名
    //>>>>-----------------------------
    //以下所有返回的都是字符串数组
    var arr = ["a", "b", "c"];
    console.log(Object.keys(arr)); 
    //["0", "1", "2"] 返回的是合并合后数组的下标
    
    // 类数组对象,简称类似数组的对象
    var obj2 = { 0 : "a", 1 : "b", 2 : "c"};
    console.log(Object.keys(obj2));
    //["0", "1", "2"]打印出来健值,简称属性名
    function demo(){
        console.log(arguments)
        //这里的arguments也是一个类数组对象
        return Object.keys(arguments)
    }
    console.log(demo(1,2,3))
    ////["0", "1", "2"]打印出来是下标,怎么说呢也可以叫为下标,也可以叫我属性名
    //>>>>------------------------------------
    //具有随机键排序的数组类对象
    var an_obj = { 100: 'a', 2: 'b', 7: 'c' };
    //果如遇到排序不正常的类数组对象
    //最后的返回值会给你从小到大排序
    console.log(Object.keys(an_obj)); // console: ['2', '7', '100']
    

    以上有那些用法,我已经给大家很祥细的讲出来了

    细节注意点

    var obj = {
        name : "ziksang",
    }
    //声明一个obj对象
    obj.__proto__.a = 1
    //在obj的原型上加一个a的属性
    for( prop in obj){
        console.log(prop)
        //name,a //for in 是可以枚举
    }
    console.log(Object.keys(obj))
    //["name"]不可以枚举对象上原型的属性名
    
    
    //>>>>-----------------------------------------------------
    console.log(Object.keys("foo"));
    // TypeError: "foo" is not an object (ES5 code)
    
    console.log(Object.keys("foo"));
    //因为在ES6里,字符串也是一个Iterator(可以遍历的对象)
    //分把字符串当作数组分隔,然后取下标
    // ["0", "1", "2"]                   (ES6 code)
    

    Object.defineProperty()
    方法会直接在一个对象上定义一个新属性,或者修改一个已经存在的属性, 并返回这个对象。

    语法
    Object.defineProperty(obj, prop, descriptor)

    参数
    obj
    需要定义属性的对象。
    prop
    需定义或修改的属性的名字。
    descriptor
    将被定义或修改的属性的描述符。
    返回值
    返回传入函数的对象,即第一个参数obj

    描述

    虽然以下这句话不是我写的,但是一定要细读,这才是Object.defineProperty精髓

    该方法允许精确添加或修改对象的属性。一般情况下,我们为对象添加属性是通过赋值来创建并显示在属性枚举中(for...in
    Object.keys
    方法),但这种方式添加的属性值可以被改变,也可以被删除。而使用Object.defineProperty()则允许改变这些额外细节的默认设置。例如,默认情况下,使用 Object.defineProperty()增加的属性值是不可改变的。

    对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。数据描述符是一个拥有可写或不可写值的属性。存取描述符是由一对 getter-setter 函数功能来描述的属性。描述符必须是两种形式之一;不能同时是两者

    数据描述符和存取描述符均具有以下可选键值:
    configurable

    当且仅当该属性的 configurable 为 true 时,该属性描述符
    才能够被改变,也能够被删除。**默认为false
    **。
    enumerable

    当且仅当该属性的 enumerable 为 true 时,该属性才能够出现在对象的枚举属性中。
    **默认为false。
    **

    数据描述符同时具有以下可选键值:
    value

    该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为undefined

    writable

    当且仅当该属性的 writable 为 true 时,该属性才能被
    赋值运算符改变。
    **默认为false
    **。

    存取描述符同时具有以下可选键值:
    get

    一个给属性提供 getter 的方法,如果没有 getter 则为undefined
    。该方法返回值被用作属性值。默认为undefined

    set

    一个给属性提供 setter 的方法,如果没有 setter 则为undefined
    。该方法将接受唯一参数,并将该参数的新值分配给该属性。默认为undefined

    记住,这些选项不一定是自身属性,如果是继承来的也要考虑。为了确认保留这些默认值,你可能要在这之前冻结Object.prototype
    ,明确指定所有的选项,或者将proto
    属性指向null

    //这上面的是一种隐式写法
    //所有数据描述符都是默认为false的
    Object.defineProperty(obj, "key", {
      __proto__: null, // 没有继承的属性
                       // 不可 enumerable(枚举)
                       // 不可 configurable(配置)
                       // 不可 writable(从写)
      value: "static"  // 作为默认值
    });
    
    // 显式写法
    Object.defineProperty(obj, "key", {
      __proto__ : null
      enumerable: false,
      configurable: false,
      writable: false,
      value: "static"
    });
    

    如果对象中不存在指定的属性,Object.defineProperty()
    就创建这个属性。当描述符中省略某些字段时,这些字段将使用它们的默认值。拥有布尔值的字段的默认值都是false。value,get和set字段的默认值为undefined。定义属性时如果没有get/set/value/writable,那它被归类为数据描述符。

    var o = {};
    //声明一个对象
    var a;//声明一个变量a
    Object.defineProperty(o, "b", {
        get : function(){ return a },
        //调用o.b返回的是一个Undefined
        set : function(newValue){ a = newValue },
        //调用o.b = 3  然后b 的 属性值就为3
        enumerable : true,//可枚举,为数据描述符
        configurable : true//可配置 ,为数据描述符
    });
    
    //>>>>-----------------------------------------------------
    var o = {};
    //声明一个对象
    Object.defineProperty(o, "a", {
        value : 37,  //存储描述 ,设置a的值为37
        writable : false,  //数据描述,不可修改
        enumerable : true,  //可枚举
        configurable : true //可配置
    });
    console.log(o.a) //=>37
    o.a = 4
    //因为是不可修改的,所以改了没用
    console.log(o.a) //=>4
    //>>>---------------------------------------------------
    var o ={}
    Object.defineProperty(o, "conflict", { 
        value: "0x9f91102",
        get: function() { return "0xdeadbeef" }  //存储描述符不能同样 
    });
    //底下报错
    // throws a TypeError: value appears only in data descriptors, get appears only in accessor descriptors
    

    举两个例子
    1.例子1

    function Archiver() {
      var count = 1;
      //声明一个变量count =1 
      var archive = [];
      //声明空数组
      Object.defineProperty(this, 'temperature', {
      //在构造函数定义一个属性,this指向这个构造函数
      //temperature是定义的属性名
        get: function() {
          console.log('get!');
          return count;
          //返回count变量,就是给他初时值义定
        },
        set: function(value) {
          count = value;
          //当给属性从新定义值时把新值给初始值
          archive.push({ val: count });
          //把每次设置的值扔进一个数组里
        }
      });
      this.getArchive = function() { return archive; };
      //在构造函数里的方法,返回数组值
    }
    
    var arc = new Archiver();
    console.log(arc.temperature); // 'get!'
    arc.temperature = 11;
    arc.temperature = 13;
    console.log(arc.getArchive()); // [{ val: 11 }, { val: 13 }]
    

    2.例子2

        //定义一个对象,这个对象用于定义的描述
    var pattern = {
        get: function () {
            return 'I alway return this string,whatever you have assigned';
        },
        //get方法返回一个字符串
        set: function () {
            this.myname = 'this is my name string';
        }
        //设置myname的值,这里的this是运行时执行的,所以这里不指向pattern对象
    };
    
    
    function TestDefineSetAndGet() {
        Object.defineProperty(this, 'myproperty', pattern);
    }
    //声明一个构造函数
    //1.this.指向这个构造函数
    //2.myproperty是指定构造函数的一个属性
    //parttern是里面的描述
    
    
    var instance = new TestDefineSetAndGet();
    instance.myproperty = 'test';
    //设置了定义属性的值,这里有什么用呢?改变myproperty的值
    //非也,本质上是出发set方法让this.myname有值
    //如果去掉的话,你会发现console.log(instance.myname);//undefined
    console.log(instance.myproperty);
    // 'I alway return this string,whatever you have assigned'
    console.log(instance.myname);
    // 'this is my name string'
    

    就光这些知识点,有没有发觉已经离数据驱动的原理很相近了,就是通过get 和 set的方式来劫持这些属性,所以造房子,必须把根基打好,所以下一章我就要开始讲 Observe如何监听,观察这些属性

    相关文章

      网友评论

      • 时光经年:感谢 终于有点明白了点 vue 里面的 wath 原理
      • 金承灏:可以,写的非常详细,虽然我啥都看不懂

      本文标题:vue.js原理和实现双向绑定MVVM(1)--Observer

      本文链接:https://www.haomeiwen.com/subject/rgatbttx.html