MVVM源码解析之数据代理篇

Posted 李耀书

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MVVM源码解析之数据代理篇相关的知识,希望对你有一定的参考价值。

源码解析之数据代理

学习准备

了解以下几点

  1. [].slice.call():将arguments转为数组
  2. node.nodeType:判断节点类型
  3. 了解Object.defineProperty是什么?
  4. 理解:Object.hasOwnProperty
  5. DocumentFragment文档碎片
<ul id="liBox">
    <li>[].slice.call(childNodes)</li>
    <li>Object.defineProperty</li>
    <li>Object.keys</li>
    <li>Object.hasOwnProperty</li>
    <li>node.nodeType</li>
    <li>createDocumentFragment</li>
</ul>

1、[].slice.call()

    //    1.   [].slice.call(childNodes) 伪数组转换为数组
    //    伪数组 ==> 不能直接使用数组方法 但是内部有下标 长度

    //伪数组__proto__ 不指向Array 指向的是 对象object
    let lis = document.getElementsByTagName("li");
    console.log(lis);
    let arr = [].slice.call(lis);
    console.log(arr);

2、node.nodeType

    <div id="app" class="app">
         node.nodeType
    </div>
 let elementNode = document.getElementById("app");
    //let attrNode = elementNode.getAttribute("id");//获取属性值
    let attrNode = elementNode.getAttributeNode("id"); //获取属性节点
    let textNode = elementNode.firstChild; //获取文本节点
    console.log(elementNode, attrNode, textNode);
    console.log(elementNode.nodeType, attrNode.nodeType, textNode.nodeType)
    //textNode.getAttribute("v-model")  -->name
    console.dir(textNode);
    //所有的节点都继承自Node
    // Node上拥有一个nodeType

3、Object.defineProperty()

 //Object上常用的API defineProperty keys hasOwnProperty assign
    //    2.Object.defineProperty 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
    /**
     * 定义属性
     * obj: 要增加或者修改属性的对象
     * prop: 属性名
     * descriptor: 属性描述
     * return  obj
     */
    //Object.defineProperty(obj, prop, descriptor)

    //属性描述符
    //        数据描述符
    //configurable: 布尔 --> 是否可配置
    //enumerable: 布尔 --> 是否可枚举
    //value: 默认值
    //writable: 布尔 --> 是否可重写

    //    访问(存取)描述符
    //get //回调函数 根据其他属性,动态计算当前属性的值
    //set //回调函数 监听当前属性值是否发生改变 然后更新其他相关属性

    let obj = {
        firstName: "A",
        lastName: "B"
    };

    Object.defineProperty(obj, "fullName", {
        enumerable: true,
        get() { //回调函数 根据其他属性,动态计算当前属性的值
            console.log("读取fullName");
            return this.firstName + "-" + this.lastName
        },
        set(newValue) { //回调函数 监听当前属性值是否发生改变 然后更新其他相关属性
            console.log("set方法--->", newValue); //"C-D"
            let fullName = newValue.split("-"); //["C","D"]
            this.firstName = fullName[0];
            this.lastName = fullName[1];
        }
    });
    Object.defineProperty(obj, "fullName1", {
        configurable: true,
        enumerable: false,
    });
    Object.defineProperty(obj, "fullName1", {
        enumerable: true, // Cannot redefine property: fullName1
        writable: false, // --> 是否可写
        value: "1234" //默认值
    });
    console.log(obj.fullName); //A-B
    obj.firstName = "C";
    console.log(obj.fullName); //C-B
    obj.fullName = "E-F";
    console.log(obj.firstName, obj.lastName); //E ,F

4、Object.hasOwnProperty()

  • **hasOwnProperty()** 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)。
  • 所有继承了 Object 的对象都会继承到 hasOwnProperty 方法。这个方法可以用来检测一个对象是否含有特定的自身属性;和 in 运算符不同,该方法会忽略掉那些从原型链上继承到的属性。
 console.log("hasOwnProperty-->",  obj.hasOwnProperty("fullName")); //true

5、DocumentFragment

它被作为一个轻量版的 Document 使用,就像标准的document一样,存储由节点(nodes)组成的文档结构。

document相比,最大的区别是DocumentFragment 不是真实 DOM 树的一部分,它的变化不会触发 DOM 树的重新渲染,且不会导致性能等问题。

<body>
    <ul id="liBox">
        <li>这是一个文档碎片</li>
        <li>这是一个文档碎片</li>
        <li>这是一个文档碎片</li>
        <li>这是一个文档碎片</li>
        <li>这是一个文档碎片</li>
    </ul>
</body>

</html>
<script>
    /*  
      利用类似于虚拟DOM思想
      1、首先在内存中创建一个空的文档
      2、获取出要更改的节点
      3、查看节点状态,判断这个节点是否可更改
      4、对符合条件的节点进行修改,
      5、成功修改后将其一次行的更新到页面视图上
    */
    // 1、创建一个文档
    var frag = document.createDocumentFragment()
    console.dir(frag);
    // 2、获取ul标签
    var li = document.getElementById('liBox')
    console.log(li);
    while (li.firstChild) {
        frag.appendChild(li.firstChild)
    }

    let child = frag
    // console.log(child);
    // // 3、修改
    Array.from(child).forEach(item => {
        /*
          #text==>nodeType:3    
          li===>nodeType:1
        */
        // console.log(child); 
        if (item.nodeType === 1) {
            item.innerHTML = '这是修改后的文档碎片'
        }
    })
    // console.log(frag);
    // // 4、将frag进行添加
    li.appendChild(frag)
</script>

数据代理

什么是数据代理?

  1. 通过一个对象代理另一个对象中属性的操作
  2. 通过vm对象代理vm.data对象中所有的属性的操作
  3. 方便操作data中的所有的操作。

基本流程:

  1. 通过Object.definePropertyvm添加与data对象的属性对应的属性描述符。
  2. 给所有的属性添加getter/setter
  3. 通过getter/setter操作data中对应的属性数据
function MVVM(options) {
    //给实例新增一个$options属性,.并且把传递过来的配置进行暂存
    this.$options = options;
    //在实例上新增一个_data 保存传递过来的data数据
    var data = this._data = this.$options.data;
    //保存this 为了之后使用this的时候保证this指向的正确性
    var me = this;
    //通过Object.keys取出data中每一项数据的属性名,然后遍历调用_proxy方法
    Object.keys(data).forEach(function(key) {
        // 数据代理
        me._proxy(key);
    });
    //为data所有数据进行劫持 结合订阅发布模式
    observe(data, this);
    //增加模版解析
    this.$compile = new Compile(options.el || document.body, this)
}
MVVM.prototype = {
    $watch: function(key, cb, options) {
        new Watcher(this, key, cb);
    },
    _proxy: function(key) {//实现数据代理
        var me = this;//暂存this 保证this的指向正确  这里的this还是实例vm
        //通过defineProperty方法在实例(vm)上新增所有与data中属性所对应属性,并且为该属性添加get和set方法
        Object.defineProperty(me, key, {//vm.name
            configurable: false,
            enumerable: true,
            get: function proxyGetter() {
                //实现了vm代理data中数据的读操作
                return me._data[key];
            },
            set: function proxySetter(newVal) {//vm.name = "bb"
                //实现了vm代理data中数据的写操作
                me._data[key] = newVal;
            }
        });
    }
};

以上是关于MVVM源码解析之数据代理篇的主要内容,如果未能解决你的问题,请参考以下文章

MVVM源码解析之Observer()篇

MVVM源码解析之Observer()篇

MVVM源码解析之模板解析篇

MVVM源码解析之模板解析篇

MVVM源码解析之Watcher监听

MVVM源码解析之Watcher监听