vue原理
必看:https://segmentfault.com/a/1190000006599500#articleHeader3
vue采用数据劫持并结合发布者-订阅者模式的方式实现双向数据绑定的。
数据劫持
通过Object.defineProperty()
来劫持各个属性的setter
,getter
Object.defineProperty
方法会=能够在一个对象上直接定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。
Object.defineProperty
语法
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双向数据绑定
思路分析:
-
实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,如果变动,可以拿到最新值并通知订阅者。
-
实现一个指令解析器Compile,对每个元素节点的志林进行扫描和解析,根据志林模板替代数据,以及绑定的相应的更新函数。
-
实现一个Watcher,作为连接Observer和Compile的桥梁,能够订阅并受到每个属性变动的通知,执行志林绑定的相应回调函数,从而更新视图。
-
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的数据进行递归遍历,包括他子属性对象的属性,都加上setter
和getter
那么,如果给对象的某个属性赋值时,就会触发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)
// 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(); }) } }
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主要实现解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面,并肩每个指令对饮的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图: