关于 Object.create() 与原型链的面试题

970 阅读3分钟
原文链接: segmentfault.com

个人是觉得楼主对一个Object的getset操作没有理解透彻。

get操作就是查询作用域中/对象中是否存在某个变量/属性,如果存在,则返回其对应的值。对于写代码的人来说,就是获取某个变量/属性的值,即Get。而Set则与之相反,是往作用域中/对象中的某个变量/属性里存值,即设置值(Set)。

第一个例子
obj2.name = 'two'这句话完成了一个get和一个set。
首先是从当前作用域中get到obj2,发现是存在的,于是得到它的值 {__proto__: {name: 'one'}},这里我把它的和这个题相关的原型链写进去了。浏览器里这个的颜色是偏暗的。
接着便是往obj2中的name属性上set了一个'two'这样的字符串值。

至于这个值为啥没有设置到obj1上的name上,你肯定很好奇,且听我慢慢道来。
往obj2的name属性上set值,在内部会转换成 obj2.[[Set]]('name', 'two', obj2)
你多半看不懂这个O.[[Set]](P, V, R)是什么鬼,它会走如下流程:

  1. ownDesc = Object.getOwnPropertyDescriptor(O, P);

  2. 如果 ownDesc === undefined,则进入下面步骤:

    1. parent = Object.getPrototypeOf(O);

    2. 如果 parent !== null 则 执行 parent.[[Set]](P, V, R),并返回其结果;

    3. 否则让 ownDesc 等于一个值为空,且可枚举,可配置,可修改数据描述符。然后进入3

  3. 如果 ownDesc 是数据描述符,则进入下面步骤:

    1. 如果 ownDesc 不可配置,则返回false;

    2. 如果 R 的类型不是Object,则返回false;

    3. existingDesc = Object.getOwnPropertyDescriptor(R, P);

    4. 如果 existingDesc !== undefined 则进入如下步骤:

      1. 如果existingDesc是访问器描述符,则直接返回false;

      2. 如果existingDesc是不可修改的数据描述符,则返回false;

      3. 在 R 上定义 P 属性,且值为 V,并返回该值。

    5. 否则在 R 上创建一个新的 P 属性,且值为 V,并返回该值

  4. (进入到这里,说明ownDesc是个访问器描述符),让 setter = ownDesc.set;

  5. 如果 setter === undefined, 返回false;

  6. 返回 setter.call(R, V);

这里面的你可能不了解的就是什么是数据描述符,什么是访问器描述符。其实区别他们的很简单,就是看有没有get或set函数,如果有get和set之一或都有,则是访问器描述符,否则就是数据描述符数据描述符一定有value,value的值可以为undefined;

说到这里,第一个例子走的路线就是

1 -> 2 -> 2.a -> 2.b -> 1 -> 3 -> 3.c -> 3.e

于是就在 obj2本身上创建了一个name属性,二并没有修改到obj1的name属性。

第二个例子
obj2.prop.name = 'two';
这是先在作用域中找 obj2,发现是存在的,于是得到它的值 {__proto__: {prop: {name: 'one'}}}
然后再找 obj2 的 prop 属性。这里就是内部的Get操作了,obj2.prop 会在内部转换成
obj2.[[Get]]('prop', obj2),你又看不懂这个O.[[Get]](P, R)了吧?它其实走的是下面的流程。

  1. desc = Object.getOwnPropertyDescriptor(O, P);

  2. 如果 desc === undefined,则进入下面流程

    1. parent = Object.getPrototypeOf(O);

    2. 如果 parent === null,返回 undefiend;

    3. 返回 parent.[[Get]](P, R)的结果;

  3. 如果 desc 是数据描述符,则返会 desc.value;

  4. 此时,desc一定是访问器描述符,则让 getter = desc.get;

  5. 如果 getter === undefined, 返回 undefined;

  6. 返回 getter.call(R);

于是在获取 obj2.prop时,走的路线如下

1 -> 2 -> 2.a -> 2.c -> 1 -> 3

得到 obj2.prop 的值为 obj1.prop{name: 'one'}
obj2.prop === obj1.prop === {name: 'one'};
由于 obj2.propobj1.prop 都指向了 {name: 'one'},
所以你再修改obj2.prop的name属性时,便修改了obj1.prop的name属性。

第三个例子,这个和第二个例子类似,获取 obj2.list 的时候也是返回的 obj1.list 的引用,所以都能修改。