为什么代理中的 set() 陷阱有时会触发 TypeError 而有时不会在无效写入操作之后?

问题描述

tl;dr:我发现有时在代理的 set() 陷阱中返回 false 不会触发任何 TypeError,有时会。为什么会发生这种情况?


据我所知,代理中的 set() 陷阱必须返回 truefalse 以表示是否允许写入操作。如果返回 false,则应触发 TypeError

然而,这并不总是发生。看看这两个例子:

示例 1(无效,但没有 TypeError)

let page = { currentPage: "Home" };

page = new Proxy(page,{
  set(target,property,value) {
    if (typeof value !== "string") return false;
    
    //... additional validation logic Could be here

    target[property] = value;
    return true;
  }
});

// Attempt an invalid operation:
page.currentPage = 123;
console.log(page.currentPage); // currentPage is still "Home"
// (writing didn't work,but it didn't trigger any TypeError to let me kNow!)
  

示例 2(无效,并抛出 TypeError) (示例取自javascript.info

let numbers = [];

numbers = new Proxy(numbers,{ // (*)
  set(target,value) { // to intercept property writing
    if (typeof val == 'number') {
      target[prop] = val;
      return true;
    } else {
      return false;
    }
  }
});

numbers.push(1); // added successfully
numbers.push(2); // added successfully
console.log("Length is: " + numbers.length); // 2

// Attempt an invalid operation:
numbers.push("test"); // TypeError ('set' on proxy returned false)

alert("This line is never reached (error in the line above)");

无效的集合操作不起作用(正如预期的那样),但为什么我有时会收到 TypeError 而有时不会呢?理想情况下,我希望总是得到一个错误,以便我可以快速发现并纠正我的错误。每当我在 set() 陷阱中返回 false 时,手动抛出错误的唯一解决方法是什么?

非常感谢您提供任何帮助或见解。

解决方法

您需要使用严格模式从失败的 setter 中获取异常。在草率模式下,赋值运算符只是忽略了这个问题。

(function(){
    "use strict";

    const page = new Proxy({ currentPage: "Home" },{
      set(target,property,value) {
        if (typeof value !== "string") return false;

        //... additional validation logic could be here

        target[property] = value;
        return true;
      }
    });

    page.currentPage = 123; // TypeError,as expected
    console.log(page.currentPage);
})();

,

false 陷阱返回 set 只会在执行赋值的代码在严格模式下运行时触发错误。在非严格模式下,false 会阻止赋值,但不会出错。

在您的第一段代码中,代码仅在非严格模式下运行。如果将其修改为严格,则会得到 TypeError:

"use strict";

let page = { currentPage: "Home" };

page = new Proxy(page,{
  set(target,value) {
    if (typeof value !== "string") return false;
    
    //... additional validation logic could be here

    target[property] = value;
    return true;
  }
});

// Attempt an invalid operation:
page.currentPage = 123;
console.log(page.currentPage); // currentPage is still "Home"
// (writing didn't work,but it didn't trigger any TypeError to let me know!)

不同的函数或方法可能运行或不运行严格模式:

let obj = new Proxy(
  { foo: "hello" },{ set() { return false; } }
);

function nonStrict(x) {
  x.foo = 2;
}

function strict(x) {
  "use strict";
  x.foo = 2;
}

nonStrict(obj);   // OK
console.log(obj); // { foo: "hello" }

strict(obj);      // Error
console.log(obj);

在第二个代码块中,执行写入的是 .push() 方法。该方法显然在严格模式下运行,这就是为什么在尝试写入数组索引时会出现 TypeError 的原因。

所有类(以及其他 ES6+ 结构,如生成器、模块等)都自动处于严格模式:

class Foo { 
  foo = "hello";
  change() {
    this.foo = "world";
  }
};

const plain = new Foo();
plain.change();     // OK
console.log(plain); // { foo: "world" }

let proxy = new Proxy(
  new Foo(),{ set() { return false; } }
);
proxy.change();     // Error
console.log(proxy);

数组被认为是一个类,因此它的方法是严格的。

const arrayLike = { length: 0 };

Array.prototype.push.call(arrayLike,"hello");
console.log(arrayLike); // { 0: "hello",length: 1 }

let proxy = new Proxy(
  arrayLike,{ set() { return false; } }
);
Array.prototype.push.call(proxy,"world"); // Error
console.log(arrayLike);