何时在当前对象VS上创建属性原型?

时间:2016-10-04 02:04:18

标签: javascript performance

我正在阅读Google的JS optimization page,并进入了本节:

  

将实例变量声明/初始化放在原型上,用于具有值类型的实例变量...这样可以避免每次调用构造函数时不必要地运行初始化代码。

我想,"等等,然后你无法更改实例中的值"

但事实证明你可以,这让我感到惊讶。

为什么,当您更改原型提供给您的值时,是否会在对象本身上设置它?

x = { one: 1 }
y = Object.create(x)
y.one // 1, of course
y.one++;
y.one // 2
x.one // 1! why isn't this 2?

定义此行为的规则是什么?

是左手边查找总是在最近的对象上,但右手边查找是否会遍历原型链?

如果是这样,这将适用于引用和值类型,但是变异(y.arr.push(1))的诱惑可能太多了,因此Google建议仅​​针对值执行此操作?

2 个答案:

答案 0 :(得分:3)

它有点复杂。

右侧:查找原型链,找到匹配的第一个(无论是getter还是数据属性),如果没有找到,则返回undefined

左手边:查找原型链,如果你发现了一个setter用途;否则,在对象本身上设置数据值。

如果简单的任务改变原型,那将是可怕的。 (想象一下,例如,International Prototype Kilogram自动改变重量,如果你削减一个你自己的公斤重量,以及随之而来的混乱。)

  

这是否只是一个明确的设定者,而不仅仅是任何一个可编辑的属性?我可以麻烦你问一个例子,它有一个更高的原型设定器和一个没有的例子吗?

var x = {
  data: 1,
  hidden: 1,
  set accessor(val) {
    this.hidden = val;
  },
  get accessor() {
    return this.hidden;
  }
};

var y = Object.create(x);

console.log("orig: x.data:", x.data, "; y.data:", y.data); // 1, 1

y.data = y.data + 1;
console.log("inc: x.data:", x.data, "; y.data:", y.data); // 1, 2

delete y.data; // removes y.data, so y.data is again looked up at x.data
console.log("del data: x.data:", x.data, "; y.data:", y.data); // 1, 1

console.log("----------");

console.log("orig: x.accessor:", x.accessor, "; y.accessor:", y.accessor); // 1, 1

y.accessor = y.accessor + 1;
console.log("inc: x.accessor:", x.accessor, "; y.accessor:", y.accessor); // 1, 2
console.log("     x.hidden:", x.accessor, "; y.hidden:", y.hidden); // 1, 2

delete y.accessor; // silent fail, y.accessor doesn't exist
console.log("del acc: x.accessor:", x.accessor, "; y.accessor:", y.accessor); // 1, 2
console.log("         x.hidden:", x.hidden, "; y.hidden:", y.hidden); // 1, 2

delete y.hidden;
console.log("del hidden: x.accessor:", x.accessor, "; y.accessor:", y.accessor); // 1, 1
console.log("            x.hidden:", x.hidden, "; y.hidden:", y.hidden); // 1, 1

您可以在此处看到的差异是,我们可以在y.data之后删除y.data = ...(数据属性),但我们无法在y.accessor之后删除y.accessor = ...,因为它不会不存在 - x.accessor用于设置y.hidden

答案 1 :(得分:1)

那是因为属性赋值([[Set]]内部方法)到达原型对象x上的属性,但接收者仍然是y。然后,该属性在y上定义。 x上的媒体资源保持不变。

从技术上讲,作业y.one = 2执行此操作:

  1. y.[[Set]]("one", 2, y)被召唤。
  2. 由于y没有"one"个属性,因此调用会重定向到父y.[[GetPrototypeOf]]()x
  3. x.[[Set]]("one", 2, y)被召唤。
  4. 由于x拥有"one"自己的属性,这是一个数据属性(即不是getter / setter访问器)并且是可写的,因此在y上定义了一个新属性
  5. y.[[DefineOwnProperty]]("one", PropertyDescriptor{[[Value]]: 2})
  6. 如果您不想这样,可以使用自定义访问者:

    var one = 1;
    var x = {
      get one() {
        return one;
      },
      set one(value) {
        one = value;
      }
    };
    var y = Object.create(x);
    console.log(y.one); // 1
    y.one++;
    console.log(y.one); // 2
    console.log(x.one); // 2