本文正在参加「金石计划 . 瓜分6万现金大奖」 ”
前言
已知:对于默认的取值操作来说,如果无法在对象本身找到需要的属性,就会继续访问对象的prototype链,如下例
var anotherOnject ={
a:2
}
var myObject = Object.create(anotherOnject)
console.log(myObject.a); //2
稍后我会介绍object.create()的原理,现在只需要知道它会创建一个对象并把这个对象的prototype关联到指定对象上。
也就是说现在myObject对象的prototype关联到了anotherObject。显然myObject.a并不存在,但尽管如此,属性访问仍然成功地找到了a的值2。如果还没有找到,那就一直在prototype链上持续查找下去,尽管尽头是undefined。
for...in
使用for...in遍历对象时的原理和查找prototype链基本类似,任何可以通过原型链访问到的属性都会被枚举出来。使用in操作符来检查属性在对象中是否存在,同样会查找对象的整条原型链(无论是否可枚举)
var anotherObject ={
a:2
}
//创建一个关联到anotherObject的对象
var myObject = Object.create(anotherObject)
for (const k in myObject) {
console.log(k,"k"); //a
}
console.log("a" in myObject); //true
下面来理解一下prototype
原型
我们来验证一下:
function Foo(){
}
var a = new Foo()
Object.getPrototypeOf(a) = Foo.prototype //true
最直接的解释就是,a这个对象是在调用new Foo()时创建的,其中的会给a一个內部的prototype链接,关联到Foo.prototype指向的那个对象。
即new Foo()会产生一个新对象a,这个新对象的内部链接关联的是Foo.prototype对象。
实际上,new Foo()这个函数调用并没有直接的创建关联,这个关联只是意外的副作用,new Foo()只是间接的完成了我们的目标(一个对象关联到其他对象的新对象,也就是关联Foo.prototype的对象a)
那么有没有更直接一点的呢?当然,功臣就是Object.create(),顺便说一下原型继承。
function Foo(){
this.name=name
}
Foo.prototype.myName = function(){
return this.name
}
function Bar(name,label){
Foo.call(this,name)
this.label= label
}
Bar.prototype=Object.create(Foo.prototype)
Bar.prototype.myLabel = function(){
return this.label
}
var a = new Bar("a","obj a")
a.myName()
a.myLabel()
以上为典型的原型继承风格。
这段代码核心部分就是Bar.prototype=Object.create(Foo.prototype)。调用object.create()会凭空 创建一个“新对象” 并把新对象内部的prototype关联到你指定的Foo.prototype中(本例子是如此)
换句话说,这代码的意思是,创建一个新的Bar.prototype对象并把它关联到Foo.prototype
ES6开始之后可以直接修改现有的Bar.prototype了
Object.setPrototypeOf(Bar.prototype,Foo.prototype)
检查类关系
思考下面的代码:
function Foo(){
}
Foo.prototype.blah=...;
var a = new Foo()
我们如何通过内省找出a的祖先是Foo呢?通过instanceof
a instanceof Foo //true
Instanceof操作符的左操作数是一个普通的对象,右操作数是一个函数。
instanceof回答的问题是:在a的整条prototype链中是否有指向Foo.prototype的对象?
下面是第二种判断prototype反射的方法,它更加简洁:
Foo.prototype.isPrototypeOf(a) //true
同样也是提问Foo.prototype是否出现在a的prototype中? 是的。
我们 也可以直接获取一个对象的prototype链:
Object.getPrototypeOf(a)
获取到原型链后也会与Foo.prototype确定一下是否存在。
Object.getPrototypeOf(a) === Foo.prototype //true
最后一种,直接使用原型链:
a.__proto__ = Foo.prototype //true
关于__proto__的实现大致是这样的:
Object.defineProperty(Object.prototype,"__proto__",{
get:function(){
return Object.getPrototypeOf(this)
},
set:function(o){
Object.setPrototypeOf(this,o)
return o;
}
})
在object.prototype中设置了key: __ proto __ ,value 为 {get: ,set: } 的键值对。
因此,访问a.__ proto__时,实际上是调用了get函数由于是隐式绑定了this,所以this指向a,所以和Object.getPrototypeOf(a)的结果相同。
_ proto _是可设置属性,之前的代码中使用ES6中object.setPrototypeOf()进行设置。
JavaScript对于双斜线有一个非官方的称呼,叫它笨蛋proto。
现在我们知道了prototype机制就是存在于对象中的一个内部链接,它会引用其他对象。通常来说,这个链接的作用是,如果没有找到需要的属性或者方法引用,引擎就会在继续prototype关联的对象上进行查找,以此类推,这一系列对象的链接被称为原型链。
object.create
它的原理:创建一个新对象,把它关联到我们指定的对象上去。
object.create = function(o){
function F(){} //创建新对象
F.prototype = o //与o对象关联
return new F()
}
由于Object.create可以被模拟,所以这个应用非常广泛,出于完整性的考虑,还是举个例子更深刻的理解一下:
var anotherObject={
a:2
}
var myObject = Object.create(anotherObject,{
b:{
enumerable:false,
writable:true,
configurable:false,
value:3
},
c:{
enumerable:false,
writable:true,
configurable:false,
value:4
}
})
使用create将myObject关联到anotherObject上去。
myObject.hasOwnProperty("a") //false
myObject.hasOwnProperty("b") //true
myObject.hasOwnProperty("c") //true
观察到第一个为什么是false呢?因为它只会分析myObject表面是否有这个对象(如果想更深一步判断的话可以用 “a” in myObject;它可以遍历到原型链中查找是否有a的存在,它会返回true。)
但仍然不影响从myObject上的三个a,b,c属性,因为即使没有在myObject本身上找到,也会去它的上一层原型链中查找是否存在该属性的,直至原型链尽头。
myObject.a; myObject.b; myObject.c这三个变量分别都能找到是2,3,4.
由于a这个变量是去原型链中的anotherObject找到的,这种myObject上本身不存在a变量但是也可以正常工作获取到a变量的场景,从內部来说,我们的实现遵循的就是委托设计模式。