Object.defineProperty和Proxy的区别和优势
// Object.defineProperty的第一个缺陷,无法监听数组变化 如这种数组改变方式 list[0] = xx; 对象的话是 var obj = {a: 1} obj.b = 2; Vue.set() / this.$set()
/* 以下八种方法Vue是可以检测到数组变化的进行了数组方法重载
push()
pop() // 删除并返回数组的最后一个元素
shift() // 把数组的第一个元素从其中删除,并返回第一个元素的值
unshift() // 向数组的开头添加一个或更多元素,并返回新的长度
splice()
sort()
reverse()
*/
// 这是将要被劫持的对象
const obj = {
name: 'bob',
age: 25
};
function test(age) {
if (age === 25) {
console.log('这是我的年龄');
} else if (age > 25) {
console.log('油腻大叔');
} else {
console.log('风花雪月的日子一去不复返了');
}
}
// 遍历对象,对其属性值进行劫持
Object.keys(obj).forEach(function(key) {
// 语法 Object.defineProperty(obj, prop, descriptor) obj 要定义属性的对象。prop 要定义或修改的属性的名称或 Symbol, descriptor要定义或修改的属性描述符。
/*描述符默认值汇总
拥有布尔值的键 configurable、enumerable 和 writable 的默认值都是 false。
属性值和函数的键 value、get 和 set 字段的默认值为 undefined
*/
Object.defineProperty(obj, key, {
enumerable: true, // 当且仅当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中。默认为 false
configurable: true, // 当且仅当该属性的 configurable 键值为 true 时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除,默认false。
get: function() {
console.log('get');
},
set: function(newVal) {
// 当属性值发生变化时我们可以进行额外操作
console.log(`新值${newVal}`);
// document.getElementById('input').value = newVal; // 例如双向绑定原理 输入框的 e.target.value;
test(newVal)
}
})
})
obj.age = 26;
console.log(obj.age, 'obj.age')
/* 输出结果
新值26
油腻大叔
get
undefined "obj.age"
*/
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div>es6</div>
<div id="app">
<input type="text" id="input" />
<div>输入框的值: <span id="title"></span></div>
<button type="button" name="button" id="btn">添加到list</button>
<ul id="list"></ul>
</div>
<script type="text/javascript">
/*
Object.defineProperty() 的问题主要有三个
不能监听数组的变化
必须遍历对象的每个属性
必须深层遍历嵌套的对象
*/
// Proxy语法糖 优势 解决了vue2.0 Object.defineProperty 没解决的几个问题 Proxy 的第二个参数可以有 13 种拦截方法
// target:[目标值], key:[目标的key值], value:[要改变的值], receiver:[改版前的原始值]
/*
针对对象:针对整个对象,而不是对象的某个属性
支持数组:不需要对数组的方法进行重载,省去了众多 hack
嵌套支持(本质也是不支持嵌套的): get 里面递归调用 Proxy 并返回
*/
// 劣势:Proxy 的兼容性不如 Object.defineProperty() 不能使用 polyfill 来处理兼容性
// var proxy = new Proxy(target, handler);
const obj = {};
const inp = document.getElementById("input");
const title = document.getElementById("title");
// 监听对象
// Reflect.get():获取对象身上某个属性的值,类似于 target[name]。
// Reflect.set():将值分配给属性的函数,返回一个Boolean,如果更新成功,则返回true。
const newObj = new Proxy(obj, {
get: function(target, key, receiver) {
console.log(`getting ${key}!`);
return Reflect.get(target, key, receiver); // Reflect.get 和 Reflect.set 可以理解为类继承里的 super,即调用原来的方法
},
set: function(target, key, value, receiver) {
console.log(target, key, value, receiver, '对象劫持');
if (key === "text") {
inp.value = value;
title.innerHTML = value;
}
return Reflect.set(target, key, value, receiver);
}
});
inp.addEventListener("keyup", function(e) {
newObj.text = e.target.value;
});
// 渲染list列表
const render = {
// 初始化
init: function(arr) {
// document.createDocumentFragment 创建一个新的空白的文档片段因为文档片段存在于内存中,并不在DOM树中,所以将子元素插入到文档片段时不会引起页面回流(对元素位置和几何上的计算)。
// 因此,使用文档片段通常会带来更好的性能
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);
},
addItem: function(val) {
const li = document.createElement("li");
li.textContent = val;
list.appendChild(li);
}
};
// 监听数组
const arr = [];
const newArr = new Proxy(arr, {
get: function(target, key, receiver) {
return Reflect.get(target, key, receiver);
},
set: function(target, key, value, receiver) {
console.log(target, key, value, receiver,'数组的劫持响应');
if (key !== "length") {
render.addItem(value);
}
return Reflect.set(target, key, value, receiver);
}
});
// 初始化
window.onload = function() {
render.init(arr);
};
btn.addEventListener("click", function() {
newArr.push(parseInt(newObj.text));
});
</script>
</body>
</html>