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>
原文地址:https://www.cnblogs.com/lhl66/p/13636108.html