Vue.js 原理

vue原理

必看:https://segmentfault.com/a/1190000006599500#articleHeader3

  vue采用数据劫持并结合发布者-订阅者模式的方式实现双向数据绑定的。

数据劫持

  通过Object.defineProperty()来劫持各个属性的settergetter

Object.defineProperty方法会=能够在一个对象上直接定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。

Object.defineProperty语法

参考文章:http://www.cnblogs.com/kidney/p/6052935.html#!comments

Object.defineProperty(obj, prop, descriptor)
// obj 要在其上定义属性的对象
// prop 要定义或修改的属性名称
// descriptor 将被定义或者修改的属性描述
// 返回值为被传递给函数的对象

访问器属性

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

var obj = {}
Object.defineProperty(obj,'red',{
    get: function () {
        console.log('get方法被调用了');
    },
    set: function(value) {
        console.log('set方法被调用,参数为' + value);
    }
});
console.log(obj); // {}
obj.red; // get方法被调用了
obj.red = 'green'; // set方法被调用,参数为green

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

实现一个简单的双向绑定

<input type="text" id="inp">
<p id="par"></p>
<script>
    var obj = {}
    Object.defineProperty(obj, 'red', {
      set: function (v) {
        document.getElementById('inp').value = v
        document.getElementById('par').innerHTML = v
      }
    });
    document.addEventListener('keyup',function(ev){
      obj.red = ev.target.value // 获取事件目标里最先触发的元素
    })
    console.log(obj); // {}
  </script>

简单模拟vue双向数据绑定

思路分析:

  1. 实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,如果变动,可以拿到最新值并通知订阅者。

  2. 实现一个指令解析器Compile,对每个元素节点的志林进行扫描和解析,根据志林模板替代数据,以及绑定的相应的更新函数。

  3. 实现一个Watcher,作为连接Observer和Compile的桥梁,能够订阅并受到每个属性变动的通知,执行志林绑定的相应回调函数,从而更新视图。

  4. mvvm入口函数,整合以上三者。

1、实现Observer

认识Object.keys()

获取对象的键名,返回数组

var data = {
    name : 'king',
    age: 20,
    gender: 'boy'
}
console.log(Object.keys(data)) // ["name", "age", "gender"]

实现监听者Observer

我们可以利用Object.defineProperty()来监听属性变化,那么将需要observe的数据进行递归遍历,包括他子属性对象的属性,都加上settergetter

那么,如果给对象的某个属性赋值时,就会触发setter,那么就能监听到数据变化。

var obj = {name: 'houfee'}

// 监听器监听数据变化
function observe(data) {
    // 判断是否为对象
    if (!data || typeof data !== 'object') {
        return;
    }
    // 遍历对象的键名,每次遍历键名调用dafineReactive方法
    Object.keys(data).forEach(function (key) {
        dafineReactive(data, key, data[key]);
    })
}

// defineProperty() 监听数据变化
function dafineReactive(data, key, val) {
    observe(val); // 监听子属性
    Object.defineProperty(data, key, {
        enumerable: true,
        configurable: false,
        get: function () {
            return val;
        },
        set: function (newVal) {
            console.log('监听到属性变化了', val, '---->', newVal);
            val = newVal;
        }
    });
}

console.log(obj); // {name: "houfee"}
// 调用Observer监听方法
observe(obj)
// 使用定时器验证监听
setTimeout(function () {
    console.log('定时器监听变化');
    obj.name = '一直走'
    console.log(obj);
}, 2000)

  以上我们就监听到了每一个数据的变化,那么监听到变化之后就该通知订阅者了,所以接下来我们需要实现一个消息订阅器:需要维护一个数组,用来收集订阅器,数据变动就触发notify,再调用订阅者的update方法:

// defineProperty() 监听数据变化
function dafineReactive(data, key, val) {
    var dep = new Dep(); // 订阅者
    observe(val); // 监听子属性
    Object.defineProperty(data, key, {
        enumerable: true,
        configurable: false,
        get: function () {
            return val;
        },
        set: function (newVal) {
            if (val === newVal) return;
            console.log('监听到属性变化了', val, '---->', newVal);
            val = newVal;
            dep.notify(); // 每次数据变动通知订阅者
        }
    });
}

function Dep() {
    // 维护一个数组,收集订阅者
    this.subs = [];
}

Dep.prototype = {
    addSub: function (sub) {
        this.subs.push(sub);
    },
    // 数据变动触发notify,调用订阅者的update方法
    notify: function () {
        this.subs.forEach(function (sub) {
            sub.update();
        })
    }
}

  添加订阅者:上面的思路整理中我们已经明确订阅者应该是Watcher, 而且var dep = new Dep();是在 defineReactive方法内部定义的,所以想通过dep添加订阅者,就必须要在闭包内操作,所以我们可以在 getter里面动手脚:

Object.defineProperty(data, key, {
    enumerable: true,
    configurable: false,
    get: function () {
        // 由于需要在闭包内添加watcher,所以
        //通过Dep定义一个全局target属性,暂存watcher, 添加完移除
        Dep.target && dep.addDep(Dep.target)
        return val;
    },
    set: function (newVal) {
        if (val === newVal) return;
        console.log('监听到属性变化了', val, '---->', newVal);
        val = newVal;
        dep.notify(); // 每次数据变动通知订阅者
    }
});


// Watcher.js
watcher.prototype = {
    get: function (key) {
        Dep.target = this;
        // 这里会触发属性的getter,从而添加订阅者
        this.value = data[key];
        Dep.target = null;
    }
}

  

这里已经实现了一个Observer了,已经具备了监听数据和数据变化通知订阅者的功能。

 

2、实现Compile

  Compile主要实现解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面,并肩每个指令对饮的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图:

 

因为遍历解析过程中有多次操作DOM节点,为了提高性能和效率,会先将根节点el转换成文档碎片fragment进行解析编译操作,解析完成,在将fragment添加会原来的真实DOM节点中:

// 未完待续中......参考文章

猜你喜欢

转载自www.cnblogs.com/houfee/p/9975652.html