[[prototype]]:
JavaScript中对象有一个特殊的[[prototype]]内置属性,其实就是对于其他对象的引用,几乎所有的对象在创建时
[[prototype]]属性都会被赋予一个非空的值。
还是一个对象属性查找的例子:
var obj = { a:1 }; //引用对象属性,触发[[GET]]操作 //对于默认的[[GET]]操作来说,第一步检查对象中是否有属性a,有的话就使用它 obj.a; //1
如果在对象中找不到a的话,就要使用对象的[[prototype]]链了。
对于默认的[[GET]]操作来说,如果无法在对象本身中找到需要的属性,就会继续访问对象的[[prototype]]链了:
var obj = { a:1 };
//创建一个关联对象obj的新对象 var newObj = Object.create(obj); console.log(newObj); //{} //结果打印的1,并不是newObj中的属性a的值,而是其[[prototype]]中的属性a的值 console.log(newObj.a); //1
上面的例子中,在它的[[prototype]]中找到了属性a。
如果碰到在自身中找不到属性,同时在它不为空的[[prototype]]链中也找不到的时候,它会继续查找下去,这个过程会持续
到找到匹配的属性或者查找完整个[[prototype]]链,如果还是找不就会返回undefined。
使用for...in循环遍历对象时原理和查找[[prototype]]链类似,任何通过原型链可以访问的(且enumerable为true)的属性都
会被枚举出来,使用in操作符在对象中是否存在一样会查找[[prototype]]链(无论属性是否可枚举)。
var obj = { a:1, b:2 }; Object.defineProperty(obj,‘c‘,{ value:3, writable:true, enumerable:false, configurable:true }); var newObj = Object.create(obj); //属性c不可枚举 for(var i in newObj){ //原型中可枚举的属性a,b被打印出来 console.log(i); //a,b } //原型中的属性a(可枚举),c(不可枚举),通过in操作符都可以判断出来 console.log(‘a‘ in newObj,‘c‘ in newObj); //true , true
Object.prototype:
所有普通的[[prototype]]链最终都会指向内置的Object.prototype,由于所有的“普通”(内置,不是特定主机的扩展)对象都
源于(或者说吧[[prototype]]链的顶端设置为)这个Object.prototype对象,这个对象中包含许多通用的方法,想toString等。
属性设置和屏蔽:
当我们给对象添加一个属性的时候,并不仅仅是在对象上添加一个属性或者在修改原有属性:
var obj = {};
obj.a = 111;
分析一下可能出现的几种情况:
① obj中已经存在属性a:属性赋值只会修改已存在的属性的值。
② obj和它的[[prototype]]链中都不存在属性a:属性直接被添加到对象obj中并赋值。
③ obj及其[[prototype]]链中国都存在a:obj中属性a赋值,且屏蔽掉[[prototype]]链中的属性,因为对象的属性赋值都会选择
[[prototype]]链最底层的属性。
出乎意料的情况其实发生在obj中不存在属性a,而[[prototype]]链中存在的时候:
④ 如果在[[prototype]]链中存在名为a的普通数据访问属性,且没有标记为writable:false的时候,那就会直接在obj对象上添
加属性,这个属性是屏蔽属性。
⑤ 如果在[[prototype]]链中存在名为a的普通数据访问属性,且标记为writable:false的时候,那么属性赋值即不会修改
[[prototype]]链上的属性,也不会在obj上创建一个新属性,如果是运行在严格模式下还会报错。否则这条一句会被忽略,
总之,不会发生屏蔽。
⑥ 如果在[[prototype]]链中存在,且它是一个setter,那就一定会调那个setter,属性不会被添加到对象obj中。
var obj = { a:1 }; //定义属性b为可写 Object.defineProperty(obj,‘b‘,{ value:2, writable:true, enumerable:true, configurable:true }); //创建对象newObj,关联上obj var newObj = Object.create(obj); //为newObj添加属性b newObj.b = 666; console.log(newObj); //{b:666} //重新定义属性b为只读 Object.defineProperty(obj,‘b‘,{ writable:false, enumerable:true, configurable:true }); //再次创建一个指向obj的新对象,覆盖之前的newObj newObj = Object.create(obj); //在其上级关联对象obj中已经存在只读的属性b的情况下,在newObj中添加一个属性啊 newObj.b = 777; console.log(newObj); //{} ---->创建只有属性b失败 //重新定义属性b,为其设置属性访问符get和set Object.defineProperty(obj,‘b‘,{ get:function(){ return ‘this is getter‘ }, set:function(val){ console.log(val); }, enumerable:true, configurable:true }); newObj = Object.create(obj); //在其关联的上级对象中存在一个属性b设置了setter的情况下,newObj添加只有属性b newObj.b = ‘newObj add b‘; console.log(newObj); //‘this is getter‘,{}
可见,大多数像我一样的前端恐怕都下意识的以为,以上清空中,都会常见只有属性(遮罩属性)。so,还是得多看看书啊!
当然,如果希望在⑤⑥的情况下依然和你之前想的那样创建遮罩属性,那么就不能用obj.b的形式创建,而是使用definePrototype
赋值。
有些情况会隐式创建遮蔽:
var obj = {a:1}; //创建一个关联obj的对象newObj var newObj = Object.create(obj); console.log(obj.a,newObj.a); //1,1 console.log(obj.hasOwnProperty(‘a‘),newObj.hasOwnProperty(‘a‘)); //true,false newObj.a ++; console.log(obj.a,newObj.a); //1,2 console.log(obj.hasOwnProperty(‘a‘),newObj); //true,{a:2}
其实上面例子中之所以创建了隐式遮蔽,是因为newObj .a++; (等价于)==》newObj.a = newObj.a + 1;
constructor:
function Bar(){}; console.log(Bar.prototype.constructor === Bar); //true var a = new Bar; console.log(a.constructor === Bar); //true
Bar.prototype默认有一个公有且不可枚举的属性constructor,这个属性引用的是对象关联的函数。
看起了a.constructor === Bar为真意味着a确实有一个指向Bar的属性constructor,但是,事实并不是这样。
实际上.constructor引用同样被委托(JavaScript中的原型链间的关系用“委托”表述更合适)给了Bar.prototype,
而Bar.prototype.constructor默认指向Bar。
Bar.prototype的constructor属性只是在Bar声明时的默认属性,如果创建一个新对象让Bar.prototype指向这个新对象,
那么新对象不会自动获得constructor属性:
function Bar(){}; Bar.prototype = {name:‘new prototype‘}; var a = new Bar; console.log(a.constructor === Bar); //false console.log(a.constructor === Object); //true
我们已经知道了,其实本身实例a上面并没有属性constructor,实际上我们a.constructor的时候是委托给Bar.prototype的,但是我们为
Bar.prototype指定了新的对象,而这个对象中并没有属性constructor,这样就会继续沿着[[prototype]]链往上找,然后委托到链顶端的
Object.prototype,这个对象有属性constructor,指向内置的Object函数。
当然,我们可以给Bar.prototype对象手动添加一个constructor属性,不过这需要添加一个符合正常情况的不可枚举属性。
function Bar(){}; Bar.prototype = {name:‘new prototype‘}; Object.defineProperty(Bar.prototype,‘constructor‘,{ value:Bar, writable:true, enumerable:false, configurable:true }); var a = new Bar; console.log(a.constructor === Bar); //true console.log(a.constructor === Object); //false
(原型)继承:
看一个典型的“原型风格”代码:
function Bar(name){ this.name = name; }; Bar.prototype.myName = function(){ return this.name; }; function Baz(name,label){ Bar.call(this,name); this.label = label; }; Baz.prototype = Object.create(Bar.prototype); Baz.prototype.myLabel = function(){ return this.label; }; var a = new Baz(‘Baz‘,‘bbbbbaaaaaazzzzzz‘); console.log(a.myName(),a.myLabel()); //‘Baz‘,‘bbbbbaaaaaazzzzzz‘
这段代码的核心部分是 “Baz.prototype = Object.create(Bar.prototype);”,我们抛弃了Baz默认的prototype对象,而是重新为它定义
了一个新的对象。下面有两种常见的错误做法:
1,Baz.prototype = Bar.prototype;
//这种方式并不会创建一个关联到Bar.prototype新的对象,它是直接将Baz.prototype引用到Bar.prototype。这样的话,当你在
//Baz.prototype上面添加方法或属性的时候,同时会直接修改Bar.prototype。
2,Baz.prototype = new Bar;
//当Bar中有一些副作用的修改的时候会影响到Baz的后代。
ES6中新增了一个setPrototypeOf方法,可以用标准,可靠的方法来修改关联。对比一下我们用的方式:
Baz.prototype = Object.create(Bar.prototype); //ES6之前需要抛弃默认的Baz.prototype
Object.setPrototypeOf(Baz.prototype,Bar.prototype); //ES6中直接修改现有的Baz.prototype
检查“类”关系:
假设有对象a,如何寻找对象a委托的对象。在传统的面向类环境中,检查一个对象的继承祖先通常被称为"内省"(或反射)。
第一种方式instanceof操作符:
function Bar(){}; var a = new Bar; console.log(a instanceof Bar); //true
instanceof操作符返回的是:在a的整条[[prototype]]链中,是否有指向Bar.prototype的对象。
然而,这个方法只能处理对象a和函数Bar间的关系,如果想判断两个对象之间是否通过[[prototype]]关联,只用instanceof无法实现。
第二种判断[[prototype]]反射的方法:
function Bar(){}; var a = new Bar(); console.log(Bar.prototype.isPrototypeOf(a)); //true //对象间的反射关系 var Bar = {name:‘Bar‘}; var a = Object.create(Bar); console.log(Object.getPrototypeOf(Bar).isPrototypeOf(a)); //true
直接获取一个对象的[[prototype]]链,在ES5中的标准方法:
Object.getPrototypeOf(a);
例如上面的例子中:Object.getPrototypeOf(a) === Bar.prototype; //true
绝大多数浏览器(也不是所有)支持一个非标准的方法来访问内部prototype属性:
a.__proto__ === Bar.prototype //true
如果想直接查找[[prototype]]链的话(甚至可以通过.__proto__.__proto__......来遍历),这个方法很好用。
和之前的constructor属性一样,__proto__并不存在当前所用对象中,也是存在与内置的Object.prototype中。
__proto__看起了更像一个属性,其实它更像一个setter/getter,它的实现大概如下:
//模拟实现 Object.definePrototype(Object.prototype,‘__proto__‘,{ get:function(){ return Object.getPropertyOf(this); }, set:function(protoObj){ //ES6中的setPrototypeOf Object.setPrototypeOf(this,protoObj); return protoObj; } });
对象关联:
在上面的例子中经常用到Object.create()来创建关联对象:
var Bar = { name:‘Bar‘ }; var newBar = Object.create(Bar); console.log(newBar.name); //"Bar"
Object.create会创建一个关联到我们指定对象的对象,这样就可以充分放回prototype机制的力量(委托),并且避免不必要
的麻烦(比如使用new的构造函数调用会生成.prototype和.constructor的引用);
Object.create(null)会创建一个不包含原型链的对象,通常用它来存储数据。
Object.create是ES5创建的函数,如果想要支持ES5之前的应用的话,有以下代码实现:
if(!Object.create){ Object.create = function(obj){ function F(){}; F.prototype = obj; return new F; } }
学习笔记=>《你不知道的JavaScript(上卷)第二部分》第五章:原型
原文:https://www.cnblogs.com/huangzhenghaoBKY/p/9853047.html