内容简介:数据劫持是双向绑定各种方案中比较流行的一种,最著名的实现就是Vue。基于数据劫持的双向绑定离不开Proxy与Object.defineProperty等方法对对象/对象属性的”劫持”,我们要实现一个完整的双向绑定需要以下几个要点。本文主要总结了VueJS利用
数据劫持是双向绑定各种方案中比较流行的一种,最著名的实现就是Vue。
基于数据劫持的双向绑定离不开Proxy与Object.defineProperty等方法对对象/对象属性的”劫持”,我们要实现一个完整的双向绑定需要以下几个要点。
- 利用 Proxy 或 Object.defineProperty 生成的 Observer 针对对象/对象的属性进行”劫持”,在属性发生变化后通知 订阅者 。
- 解析器 Compile 解析模板中的 Directive (指令),收集指令所依赖的方法和数据,等待数据变化然后进行渲染。
- Watcher 属于 Observer 和 Compile 桥梁,它将接收到的 Observer 产生的数据变化,并根据 Compile 提供的指令进行视图渲染,使得数据变化促使视图变化。
本文主要总结了VueJS利用 Object.defineProperty()
和 Proxy
,结合发布者-订阅者模式实现双向数据绑定的基本原理。
Object.defineProperty()
Vue 内部使用了 Object.defineProperty()
来实现双向绑定,当你把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter,监听到 set 和 get 的事件。
Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
Object.defineProperty(obj, prop, descriptor)
该函数接受三个参数:
- obj:要在其上定义属性的对象。
- prop:要定义或修改的属性的名称。
- descriptor:将被定义或修改的 属性描述符 。
详细的文档可以参阅 MDN
Observer对象
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;
// 通知所有的 viewModel 更新
dep.notify();
}
}
})
}
}
module.exports = Observer;
为每个属性添加了 getter 和 setter ,当属性是一个对象,那么就递归添加。
一旦获取属性值或者为属性赋值就会触发 get 或 set ,当触发了 set ,即 model 变化,就可以发布消息, dep.notify();
通知所有 viewModel 更新。
Dep 对象就是一个闭包。
class Dep {
constructor() {
// 依赖列表
this.dependences = [];
}
// 添加依赖
addDep(watcher) {
if (watcher) {
this.dependences.push(watcher);
}
}
depend() {
// Dep.target是一个实例化的全局 watcher 对象
if (Dep.target) {
// 传入闭包中的 dep 对象
Dep.target.addDep(this)
}
}
// 通知所有依赖更新
notify() {
this.dependences.forEach((watcher) => {
watcher.update();
})
}
}
Dep.target = null
function update(value) {
document.querySelector('div').innerText = value
}
module.exports = Dep;
这里的每个依赖就是一个 Watcher 。每一个 Watcher 都会有一个唯一的 id 号,它拥有一个表达式和一个回调函数 。
var uid = 0;
class Watcher {
constructor(viewModel, exp, callback) {
// viewModel 就是 obj
this.viewModel = viewModel;
this.id = uid++;
this.exp = exp;
this.callback = callback;
this.oldValue = "";
this.update();
}
get() {
// 将 Dep.target 指向自己
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。
通过 get
添加依赖, 修改Object.defineProperty为以下代码
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;
// 执行 watcher 的 update 方法
dep.notify();
}
}
})
以上实现了一个简易的双向绑定,核心思路就是手动触发一次属性的 getter 来实现发布订阅的添加。
Proxy
Object.defineProperty存在两个缺陷:
- 无法监听数组变化。
然而Vue的文档提到了Vue是可以检测到数组变化的,但是只有以下八种方法, vm.items[indexOfItem] = newValue
这种是无法检测的。
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()
其实作者在这里用了一些奇技淫巧,把无法监听数组的情况hack掉了。由于只针对了八种方法进行了hack,所以其他数组的属性也是检测不到的。
- 只能对对象的属性进行数据劫持,所以需要深度遍历整个对象。
所以利用 Proxy,就可以很好地避免上述两个缺陷。
let onWatch = (obj, setBind, getLogger) => {
let handler = {
get(target, property, receiver) {
getLogger(target, property)
return Reflect.get(target, property, receiver);
},
set(target, property, value, receiver) {
setBind(value);
return Reflect.set(target, property, value);
}
};
return new Proxy(obj, handler);
};
可以看到,Proxy直接可以劫持整个对象,并返回一个新对象,不管是操作便利程度还是底层功能上都远强于Object.defineProperty。
Proxy的其他优势
Proxy有多达13种拦截方法,不限于apply、ownKeys、deleteProperty、has等等是Object.defineProperty不具备的。
Proxy作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利。
当然,Proxy的劣势就是兼容性问题,而且无法用polyfill磨平,因此Vue的作者才声明需要等到下个大版本(3.0)才能用Proxy重写
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 3分钟了解vue数据劫持的原理
- 深入浅出 Vue 系列 -- 数据劫持实现原理
- ES6-Proxy与数据劫持(12)
- 可导致数百万玩家帐户被劫持:EA游戏帐户劫持漏洞分析
- 浅谈jsonp劫持
- 内网会话劫持
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
算法竞赛入门经典(第2版)
刘汝佳 / 清华大学出版社 / 2014-6-1 / CNY 49.80
《算法竞赛入门经典(第2版)》是一本算法竞赛的入门与提高教材,把C/C++语言、算法和解题有机地结合在一起,淡化理论,注重学习方法和实践技巧。全书内容分为12 章,包括程序设计入门、循环结构程序设计、数组和字符串、函数和递归、C++与STL入门、数据结构基础、暴力求解法、高效算法设计、动态规划初步、数学概念与方法、图论模型与算法、高级专题等内容,覆盖了算法竞赛入门和提高所需的主要知识点,并含有大量......一起来看看 《算法竞赛入门经典(第2版)》 这本书的介绍吧!