使用 deleteProperty 陷阱函数避免属性被删除

delete 运算符能够从指定对象上删除一个属性,在删除成功时返回 true,否则返回 false。如果试图用 delete 运算符去删除一个不可配置的属性,在严格模式下将会抛出错误;而非严格模式下只是单纯返回 false。这里有个例子:

let target = {
    name: "target",
    value: 42
};

Object.defineProperty(target, "name", { configurable: false });

console.log("value" in target);     // true

let result1 = delete target.value;
console.log(result1);               // true

console.log("value" in target);     // false

// Note: The following line throws an error in strict mode
let result2 = delete target.name;
console.log(result2);               // false

console.log("name" in target);      // true

这里使用了 delete 运算符删除了 value 属性,因此在第三行代码的 console.log() 调用中,使用 in 操作符检测该属性会得到 false。name 属性是不可配置的,因此对其使用 delete 操作符只会返回 false 而不能删除该属性(如果代码运行在严格模式下,则会抛出错误)。你可以在代理对象中使用 deleteProperty 陷阱函数以改变这种行为。

deleteProperty 陷阱函数会在使用 delete 运算符去删除对象属性时下被调用, 并且会被传入两个参数:

  1. trapTarget:需要删除属性的对象(即代理的目标对象);

  2. key:需要删除的属性的键(字符串类型或符号类型)。

Reflect.deleteProperty() 方法也接受这两个参数,并提供了 deleteProperty 陷阱函数的默认实现。你可以结合 Reflect.deleteProperty() 方法以及 deleteProperty 陷阱函数,来修改 delete 运算符的行为。例如,能确保 value 属性不被删除:

let target = {
    name: "target",
    value: 42
};

let proxy = new Proxy(target, {
    deleteProperty(trapTarget, key) {

        if (key === "value") {
            return false;
        } else {
            return Reflect.deleteProperty(trapTarget, key);
        }
    }
});

// Attempt to delete proxy.value

console.log("value" in proxy);      // true

let result1 = delete proxy.value;
console.log(result1);               // false

console.log("value" in proxy);      // true

// Attempt to delete proxy.name

console.log("name" in proxy);       // true

let result2 = delete proxy.name;
console.log(result2);               // true

console.log("name" in proxy);       // false

这段代码与 has 陷阱函数的例子相似,在 deleteProperty 陷阱函数中检查 key 的值是否为 "value"。如果是,返回 false;否则通过调用 Reflect.deleteProperty() 方法来进行默认的操作。value 属性是不能被删除的,因为该操作被 proxy 对象拦截;而 name 则能如期被删除。这么做允许你在严格模式下保护属性避免其被删除,并且不会抛出错误。