【JS】关于Object.create()与原型链的面试题?
一:
var obj1 = {name:'one'};obj2 = Object.create(obj1);
obj2.name = 'two';
console.log(obj1.name);
//one
二:
var obj1 = {prop:{name:'one'}};obj2 = Object.create(obj1);
obj2.prop.name = 'two';
console.log(obj1.prop.name);
//two
三:
var obj1 = {list:['one','one','one']};obj2 = Object.create(obj1);
obj2.list[0] = 'two';
console.log(obj1.list[0]);
//two
为什么后面两段代码修改的是原型链上的属性呢?
问题是,为什么二、三中的代码不是像代码一中直接给obj2添加属性,而是修改了原型链上的属性?
求解释下一、二、三的结果?
回答
Object.create方法指定的第1个参数为新建对象的原型对象
在获取一个对象的属性值时,才会有可能沿着原型链向下寻找,属性赋值没有这个
给一个对象属性赋值时,如果这个属性不存在,那么就直接为这个对象添加这个属性并赋值(不会去理会原型链中的存在,除了某些特殊情况,如原型链中有这个属性的set方法,或这个属性被设置只读不可写的)
obj2.prop.name='two'
先计算obj2.prop的值,在原型链中被发现,然后再计算obj2.prop对应的对象(不检查原型链)中是否存在name属性~~~obj2.list[0] = 'two';
也就是先计算obj2.list属性的值,然后赋值给obj2.list属性下标为0(属性名为“0”)的属性
那么结果就好理解了吧
问题出在赋值name时上了
只解释下一和二,二和三同理,就不多说了
我们可以看出,Object.create(obj1)
都是把obj2的__proto__指向了obj1
但是下面的那么赋值是不一样的
一在赋值前可以输出下obj2.name的值,其实是one,赋值的时候obj2.name
,会当成属性去找,但是这时候是把自身的name属性赋值成了two,并不会动obj1中的属性,也就是说对象的属性是无法修改其原型链中的同名属性,而只会自身创建一个同名的属性并为其赋值。
而二在赋值的时候,prop.name
是当成一个对象去处理,发现自己没有prop对象,就会去原型链里去找,发现在obj1中找到了name,所以变成了'two'
如果一想达到同样的效果,可以使用obj2.__proto__.name
去修改obj1中的name值
自己的理解,如有不对,请指出学习~
个人是觉得楼主对一个Object的get和set操作没有理解透彻。
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)
是什么鬼,它会走如下流程:
让
ownDesc = Object.getOwnPropertyDescriptor(O, P)
;如果
ownDesc === undefined
,则进入下面步骤:让
parent = Object.getPrototypeOf(O)
;如果
parent !== null
则 执行parent.[[Set]](P, V, R)
,并返回其结果;否则让 ownDesc 等于一个值为空,且可枚举,可配置,可修改的数据描述符。然后进入3
如果 ownDesc 是数据描述符,则进入下面步骤:
如果 ownDesc 不可配置,则返回false;
如果 R 的类型不是Object,则返回false;
让
existingDesc = Object.getOwnPropertyDescriptor(R, P)
;如果
existingDesc !== undefined
则进入如下步骤:如果existingDesc是访问器描述符,则直接返回false;
如果existingDesc是不可修改的数据描述符,则返回false;
在 R 上定义 P 属性,且值为 V,并返回该值。
否则在 R 上创建一个新的 P 属性,且值为 V,并返回该值
(进入到这里,说明ownDesc是个访问器描述符),让
setter = ownDesc.set
;如果
setter === undefined
, 返回false;返回
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)
了吧?它其实走的是下面的流程。
让
desc = Object.getOwnPropertyDescriptor(O, P)
;如果
desc === undefined
,则进入下面流程让
parent = Object.getPrototypeOf(O)
;如果
parent === null
,返回 undefiend;返回
parent.[[Get]](P, R)
的结果;
如果 desc 是数据描述符,则返会
desc.value
;此时,desc一定是访问器描述符,则让
getter = desc.get
;如果
getter === undefined
, 返回 undefined;返回
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.prop
和obj1.prop
都指向了 {name: 'one'}
,
所以你再修改obj2.prop
的name属性时,便修改了obj1.prop
的name属性。
第三个例子,这个和第二个例子类似,获取 obj2.list
的时候也是返回的 obj1.list
的引用,所以都能修改。
obj2 = Object.create(obj1)是把obj1作为obj2的原型,所以obj2访问的是原型链的属性
http://www.cnblogs.com/shuiyi/p/5305435.html 刚看到的博客 写的不错
昨天翻来覆去睡不着,现在才明白原来答错一道题,罪过罪过。。
原因是没有弄清楚Object.creat()
的作用,把它当构造函数了。。看别人的答案吧,讲得挺细的。
原答案:
什么原型链啊,这根本就是考察基本类型和引用类型的传值方式。。
原型只存在于函数中,原型链才可以存在于所有引用类型中!
你可以打印一下obj1.prototype(原型),obj1.__proto__(原型链)。
关于传值,看下面的例子:
a = 1;b = a;
b = 2;
console.log(a);//1
a = {val: 1};
b = a;
b.val = 2;
console.log(a.val);//2
a = {val: 1};
b = a;
b = {val :2}; //注意这里,重写了b,这时b和a没半毛钱关系了!
console.log(a.val);//1
只能帮你到这了,自己悟去吧。。
自己找不到,就会去原型链上面找。
第一个例子,obj1有name这个属性,value是1,所以就是1.
第二个例子,obj1有一个属性叫prop,obj2没有这个属性,obj1是obj2的原型,是object.create()构造器赋予的,所以obj2的prop就找到了原型的prop,但是prop是个对象,所以这里存储的实际上是一个引用,也就是说,obj2也是找到了这个引用。相当于obj2._proto_.prop,改掉了引用的prop这个对象的name,obj1的引用没变,但是prop的name变了。
第三个例子和第二个也一样。
基本类型的 '='是值传递,引用类型的 '=' 是按地址(引用)传递的 0.0
看下这篇文章会不会好理解点!
http://kelen.cc/posts/56fb39979879f45808...
简单的说,一是给对象的属性赋值,相当于给obj2
增加了一个叫做name的属性,而二和三则是修改对象原型上的属性。
对象的原型也是一个对象,所以它是一个引用的关系,修改原型相当于修改原来的对象。
对象的get
有一个优先级的情况,比如,你要获取一个对象的name
属性,首先从对象自身开始查找,如果自身没有,则开始查找原型链。
PS:如果题主还是不理解,我可以再换一个方式回答。
因为object和array是按引用传递的,所以子对象和父对象中的prop指向的是同一个内存地址,所以通过修改子对象中prop这个对象的成员,也会影响到父对象中prop对象相应成员的值。如果不想这种行为发生,可以写一个深拷贝函数,将父对象的prop对象深拷贝到子对象中就能不互相影响。比如说,如果你这样写obj2.prop = {name:'one'};那么此时子对象和父对象的prop指向的就不是同一个对象了,只是成员的名和值相同而已,这时候再修改就不会影响到父对象的成员
Object.create()没有什么好说的,就是原型指向。 第一个在使用obj2.name="a"的时候,发现obj2中没有name属性,则添加name属性赋值为"a"(直接set)。第二个在使用obj2.prop.name="b"的时候,会先查找obj2中prop属性,发现obj2中没有prop属性,更别提后面的name属性,所以就从obj2._prop_中查找,然后查找到了prop属性,此时的obj2.prop指向为obj2._prop_.prop所以后续的操作就是对obj2._prop_.prop的操作(先get prop属性,再set name)。
以上是 【JS】关于Object.create()与原型链的面试题? 的全部内容, 来源链接: utcz.com/a/83323.html