vueConf大会,尤小右实锤vue3.0将改definePrototype为proxy。
却之为何
1.在Vue中,Object.defineProperty无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实时响应。 为了解决这个问题,经过vue内部处理后可以使用以下几种方法来监听数组。(事实上,Object.defineProperty本身是可以监控到数组下标的变化的,参Vue为什么不能检测数组变动)
1 2 3 4 5 6 7 |
push() pop() shift() unshift() splice() sort() reverse() |
由于只针对了以上八种方法进行了hack处理,所以其他数组的属性也是检测不到的,还是具有一定的局限性。
Object.defineProperty只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。Vue里,是通过递归以及遍历data 对象来实现对数据的监控的,如果属性值也是对象那么需要深度遍历,显然如果能劫持一个完整的对象,不管是对操作性还是性能都会有一个很大的提升。
而要取代它的Proxy有以下两个优点;
- 可以劫持整个对象,并返回一个新对象
- 有13种劫持操作
vue2.x之前之所以不用Proxy,主要Proxy是es6提供的新特性,兼容性不好,最主要的是这个属性无法用polyfill来兼容。
基于proxy的vue数据双向绑定实现
■ Observer: 基于proxy处理代理,当属性修改时通知Dep。
■ compile: 解析指令,订阅数据变化,绑定更新函数。 根据对应指令绑定相应watcher。
■ Dep: 为全局对象subscribe添加对应属性,当数据变化,通知watcher,调用相关update方法
■ Watcher: 主要update方法,修改相关node节点数据。
1. 页面结构
1 2 3 4 5 6 7 8 9 10 |
<div id="app"> <!-- input框, 包含v-model指令 --> <input type="text" v-model="num" /> <!-- input框, 包含v-model指令 --> <input id="btn" type="button" value="添加到Todolist" v-click="addList" /><br /> <span>您输入的是:</span> <!-- span, 包含v-bind指令 --> <span v-bind="num"></span> <ul id="list"></ul> </div> |
2. 参照vue调用
1 2 3 4 5 6 7 8 9 10 11 12 13 |
new proxyVue({
el: "#app",
data: {
num: 0,
arr: []
},
methods: {
addList() {
this.arr.push(this.num);
Render.addList(this.num);
}
}
});
|
proxyVue实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
// 渲染todolist列表
const Render = {
// 初始化
init: function (arr) {
const fragment = document.createDocumentFragment();
for (let i = 0; i < arr.length; i++) {
const li = document.createElement("li");
li.textContent = arr[i];
fragment.appendChild(li);
}
list.appendChild(fragment);
},
addList: function (val) {
const li = document.createElement("li");
li.textContent = val;
list.appendChild(li);
}
};
class ProxyVue {
constructor(options) {
this.$options = options || {},
this.$methods = this.$options.methods;
const data = (this._data = this.$options.data);
// subscribe对象结构:{
// num: [watcher, watcher]
// }
this.subscribe = {};
this.observe(data);
this.compile(options.el);
}
observe(data) {
let handel = {
get: (target, key) => Reflect.get(target, key),
set: (target, key, value) => {
// 需要将Reflect赋值并return
let res = Reflect.set(target, key, value);
// 当对象改变,遍历执行其属性对应的watcher数组,并调用自身update方法
this.subscribe[key].map(item => {
item.update();
})
return res;
}
}
this.$data = new Proxy(data, handel);
}
compile(el){
// 将nodes类数组转换成数组,以供遍历
let nodes = Array.from(document.querySelector(el).children);
let data = this.$data;
nodes.map(node => {
if (node.hasAttribute('v-model')) {
let property = node.getAttribute('v-model');
this.publish(new Watcher(node, "value", data, property));
node.addEventListener("input", () => {
data[property] = node.value;
});
}
if (node.hasAttribute('v-bind')) {
let property = node.getAttribute('v-bind');
this.publish(new Watcher(node, "innerHTML", data, property));
}
if (node.hasAttribute('v-click')) {
let methodName = node.getAttribute('v-click');
let method = this.$methods[methodName].bind(data);
node.addEventListener("click", method);
}
})
}
// 订阅消息
publish(watcher) {
if (!this.subscribe[watcher.property]) {
this.subscribe[watcher.property] = []
}
this.subscribe[watcher.property].push(watcher);
}
}
class Watcher{
constructor(node, attr, data, property) {
this.node = node;
this.attr = attr;
this.data = data;
this.property = property;
}
update(){
this.node[this.attr] = this.data[this.property];
}
|