Vue的一大核心是双向绑定,在2.0中采用数据劫持,用Object.defineProperty实现,但作者已声明在3.0中会采用proxy实现
Object.defineProperty是什么?proxy是什么?为什么要换呢?我们来探讨下
Object.defineProperty
是js中一个高级方法,理解它有助于我们更好地理解面向对象及理解vue运行原理
定义:
语法:
参数:
- obj 要在其上定义属性的对象。
- prop 要定义或修改的属性的名称。
- descriptor 将被定义或修改的属性描述符。
返回值:
被传递给函数的对象
详细语法介绍请参考MDN:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
示例:
自己简单写了个示例,方便理解Object.defineProperty
var data = {
name: "jade",
age: 18,
}
Object.keys(data).forEach(key=>{
var vl = data[key];
Object.defineProperty(data,key,{
get(){
console.log(key + "...get...");
return vl;
},
set(value){
console.log(key + "...set... value is " + value);
vl = value;
}
});
}) console.log("age:"+data.age);
data.age = 20;
data.name = "jake";
console.log("age:"+data.age);
在读取属性或者给属性赋值时,会进入get和set方法,进而可以派发出事件,通知监听者。
缺点:
- 无法监听数组变化
- 只能劫持对象的属性(所以需要深度遍历)
Proxy
Proxy在ES2015规范中被正式发布,它在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写,我们可以这样认为,Proxy是Object.defineProperty
的全方位加强版
- 可以直接监听对象而非属性
- 可以直接监听数组的变化
- 有多达13种拦截方法
- 返回的是一个新对象,我们可以只操作新的对象达到目的,而
Object.defineProperty
只能遍历对象属性直接修改。 - 劣势就是兼容性问题,而且无法用polyfill磨平,因此Vue的作者才声明需要等到下个大版本(3.0)才能用Proxy重写。
示例:
拦截对象:
<input id="input" type="text"/>
<p id="p"></p> <script>
const input = document.getElementById('input');
const p = document.getElementById('p');
const obj = {}; const newObj = new Proxy(obj, {
get: function(target, key, receiver) {
console.log(`getting ${key}!`);
return Reflect.get(target, key, receiver);
},
set: function(target, key, value, receiver) {
console.log(target, key, value, receiver);
if (key === 'text') {
input.value = value;
p.innerHTML = value;
}
return Reflect.set(target, key, value, receiver);
},
}); input.addEventListener('keyup', function(e) {
newObj.text = e.target.value;
});
</script>
拦截数组:
<ul id="list"> </ul>
<input id="btn" value="test" type="button"/> <script>
const list = document.getElementById('list');
const btn = document.getElementById('btn'); // 渲染列表
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);
},
// 我们只考虑了增加的情况,仅作为示例
change: function(val) {
const li = document.createElement('li');
li.textContent = val;
list.appendChild(li);
},
}; // 初始数组
const arr = [1, 2, 3, 4]; // 监听数组
const newArr = new Proxy(arr, {
get: function(target, key, receiver) {
console.log(key);
return Reflect.get(target, key, receiver);
},
set: function(target, key, value, receiver) {
console.log(target, key, value, receiver);
if (key !== 'length') {
Render.change(value);
}
return Reflect.set(target, key, value, receiver);
},
}); // 初始化
window.onload = function() {
Render.init(arr);
} // push数字
btn.addEventListener('click', function() {
newArr.push(6);
}); </script>