// 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>