ES6 之 Object.defineProperty && Object.Proxy区别

发布于 2021-08-02  400 次阅读


1.defineProperty介绍

1.基本用法

Object.defineProperty()的作用就是直接在一个对象上定义一个新属性,或者修改一个已经存在的属性。

// obj  需要定义属性的当前对象
// prop 当前需要定义的属性名
// desc 属性描述符
Object.defineProperty(obj, prop, desc);

通过Object.defineProperty()为对象定义属性,有两种形式,且不能混合使用,
分别为数据描述符存取描述符,下面分别描述两者的区别:

1)数据描述符——特有的两个属性(value, writable);

const obj = {};
Object.defineProperty(obj, "age", {
    value       : 18,      // 默认undefind
    writable    : false,   // 是否可以改变,默认false不可以改变
    enumerable  : true,    // 是否可以枚举
    configurable: true,    // 是否可以配置
});

obj.age = 20;
console.log(obj.age); // undefined,因为writable不能改变

2)存取描述符——是由gettersetter函数功能来描述的属性;

Object.defineProperty(data, key, {
    enumerable: true,
    get() {
        console.log("劫持了");
        return value;
    },
    set(v) {
        if (value === v) {
            return;
        }
        value = v;
    },
});

2.在vue2.x中的应用

有很多人说Object.defineProperty不能监听数组的变化,这种说法是不严谨的,
准确的说是不能监听到数组某些方法的变化

function observer(data) {
    for (let prop in data) {
        let value = data[prop];
        Object.defineProperty(data, prop, {
            enumerable: true,
            get() {
                console.log("数组劫持了");
                return value;
            },
            set(v) {
                if (value === v) {
                    return;
                }
                value = v;
            },
        });
    }
}
let arr = ["张", "王", "李", "赵"];
observer(arr);
arr.pop();      //会触发get函数,控制台会打印「数组劫持了」
arr.push("周"); //不会触发,劫持失败

总结:

数据的["push", "unshift", "splice", "reverse", "sort", "pop", "shift"]
这些方法中
["push", "unshift", "splice"]
劫持不到的,在vue2.0源码中,对数组的这三个方法进行了重写,具体如下:

let   arrayProto = Array.prototype;
let   newProto   = Object.create(arrayProto);
const arrMethods = ["push", "unshift", "splice", "reverse", "sort", "pop", "shift"];
arrMethods.forEach((method) => {
    newProto[method] = function (...args) {
        let inserted = null;
        switch (method) {
            // args 是一个数组
            case "push":
                inserted = args;
                break;
            case "unshift":
                inserted = args;
                break;
            case "splice":   //splice方法有三个参数,第三个参数才是被新增的元素
                inserted = args.slice(2);  //slice返回一个新的数组
                break;
        }
        //因为inserted是一个新的数组项,所以要对数组的新增项重新进行劫持
        if (inserted) ArrayObserver(inserted);
        //调用数组本身对应的方法
        arrayProto[method].call(this, ...args);
    };
});
function observer(obj) {
    ...
    //通过Object.defineProperty进行循环递归绑定
}
function ArrayObserver(obj) {
    //对数组的新增项进行监控
    obj.forEach((item) => {
        observer(item);
    });
}

2.Proxy介绍

1.基本用法

语法:

let p = new Proxy(target, handler);
  • target:需要使用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理);
  • handler:一个对象,其属性是当执行一个操作时定义代理的行为的函数(可以理解为某种触发器),具体的handler相关函数请查阅官网;
let arr = ["张", "王", "李", "赵"];
let testArr = new Proxy(arr, {
    get(target, key) {
        console.log("获取了getter属性");
        return target[key];
    },
});
testArr.push("周"); //获取了getter属性

3.defineProperty和Proxy对比

  • Proxy性能优于Object.defineProperty。Proxy代理的是整个对象,Object.defineProperty只代理对象上的某个属性,如果是多层嵌套的数据需要循环递归绑定;
  • 对象上定义新属性时,Proxy可以监听到,Object.defineProperty监听不到,需要借助$set方法;
  • 数组的某些方法 ["push", "unshift", "splice"] 中,Object.defineProperty监听不到,Proxy可以监听到;
  • Proxy在IE浏览器中存在兼容性问题。

博主好穷啊,快点支助一下吧 ε = = (づ′▽`)づ