几乎任何对象有一个[[prototype]]属性,在标准中,这是一个隐藏属性。该属性指向的是这个对象的原型。
几乎所有对象的原型链末端都是Object.prototype
1. 字面量方式
var obj1={x:1,y:2}
var obj2={
x:1,
y:2,
o:{
z:3,
n:4
}
}
如上,直接在大括号中包裹对象的属性key及其值value。
2. 构造函数模式
function sum(a,b){
this.aa= a;
this.bb= b;
this.add = function(){
return (aa+bb);
}
}
var calc =new sum(2,3);
console.log(calc.add) //5
注意:这个new
关键字是必须,如果不加,sum
就不会当成构造函数调用,而只是一个普通的函数,这时候,sum()
内部的this
指向就会是全局window
或者global
,将会意外地给他的外部作用域即window添加属性。
3. Object.create()
var obj=Object.create({x:1})
obj.x //1
typeof obj.toString //function
obj.hasOwnProperty('x') //false
var obj=Object.create(null)
obj.toString //undefined
第一行代码,create()
方法是Object
内置方法,接收了一个对象作为参数,使用访问obj.x
将会返回值1
。
但是需要注意的是,使用这种方式创建的对象,其实是将obj
的__proto__
指向了{x:1}
这个对象,并不是在obj
本身内部创建了一个置为1
的属性x
,所以hasOwnProperty()
方法的时候,将会返回false
正是因为Object.create()
能够重置对象的__proto__
指向,所以如果在第5行代码中,将obj
的原型指向了null
之后,obj
的原型链末端就从Object.prototype
变成null
,obj
本身以及原型链上都不在包含toString
这个方法,所以将会返回undefined
4. 原型链方式
function foo(){}
foo.prototype.z=3
var obj=new foo()
obj.y=2
obj.x=1
obj.x //1
obj.y //2
obj.z //3
typeof obj.toString //function
'z' in obj //true
obj.hasOwnProperty('z') //false
以上代码,首先使用function
定义了一个函数对象,这个函数对象默认带有了一个名为prototype
的隐藏属性,这个prototype
属性是一个对象类型的属性,指向这个对象的原型,并且在这个属性中存在一个名为__proto__
的标签。
在第二行代码中,又为这个prototype
属性增加了一个属性,所以现在的prototype
如下:
第三行代码中,使用new
关键字实例化了一个obj
,这个obj
对象的原型__proto__
将会指向构造函数的prototype
属性,也就是foo.prototype
,然后在这个obj
对象上,增加了两个属性。
以上几行代码可用下图表示:
当访问obj.x
的时候,obj
本身就已经存在x
这个属性,所以直接返回对应的值1
,同理访问obj.y
返回对应值2
。
当访问obj.z
的时候,发现obj
本身并不存在z
这个属性,所以沿着原型链往上找,也就是内部__proto__
标签指向的构造函数的prototype
属性foo.prototype
,在这上面找到了z
,并返回对应值3
。
实际上,这个__proto__
可以继续指向下去,这就构成了原型链,实际上,如果在上一步没有找到z
属性的话,那么还会继续沿着foo.prototype
的__proto__
往下继续找,直到找到这个z
属性,或者到达原型链的末端,查找才算结束,返回找到的对应值或者undefined
。
而这条原型链的最末端就是Object.prototype
,它里面的__proto__
指向null
。
正是因为原型链的存在,所以一些类似于toString
(注意,这里没有括号,不是toString() )的方法,尽管我们并没有在obj
上定义,但是因为这个toString
方法是在Object.prototype
上存在的,也就是处在 obj
的原型链上,所以obj
也能拿到这个方法。
因为obj.z
属性是从原型上继承而来,所以当使用in
方法的时候,会返回true
,但是如果使用hasOwnProperty()
的方法,将会返回false
,因为这个z
属性并不是obj
本身(own)的属性,而是继承于原型链。
obj.z=5
obj.hasOwnProperty('z') //true
foo.prototype.z //3,不变
obj.z //5,发生了变化
obj.z=undefined
obj.z //undefined
delete obj.z
obj.z //3
上面说到,obj.z
返回的结果,其实是从obj
的原型链上找到的,并不是obj
这个对象本身的属性,那么如果在obj
对象上再增加一个obj
本身的z
属性,将会发生什么呢?
第二段代码的第一行,在obj
对象上增加了一个z
属性,值为5
,然后使用hasOwnProperty()
方法,返回结果true
,这表明现在返回的z
属性是obj
对象本身的属性,并不是原型链上返回的。
而这个时候,其实obj
原型链上的z
属性并没有修改,也没有删除,还是保持原样,所以foo.prototype.z
的值仍然是3
。
原因还是在于对象属性的遍寻机制上。
在obj.z=5
这一句,首先判断obj
这个对象是否存在z
这个属性,如果存在就将这个z
属性的值修改为5
,没有就直接创建,并不会在原型链上查找,所以obj
这个对象内部,就被创建出了z
这个属性。
而在obj.z
进行访问的时候,首先先在这个对象obj
本身寻找z
这个属性,如果没找到再沿原型链往上找,但是现在已经在它本身找到了,所以就不再往上找了,返回值为5
。
这里需要注意的是,如果还想访问原型链上的z
属性,那么将obj.z
置为undefined
或者null
,都是没用的,最终访问obj.z
的时候,将会返回undefined
或者null
,而不是5
.
这是因为将obj.z
置为undefined
或者null
,只是将obj.z
这个属性对象的值,我们可以称之为value
置为了undefined
或者null
,而obj
这个对象本身的z
属性,我们可以称之为key
,其实还是存在的,所以访问的依旧是obj
本身的z
属性,而不是原型链上的。
这个时候,可以使用delete
关键字,将obj
上的z
属性完全删除。
注意,这个delete
关键字,只会删除obj
本身的z
属性,并不会删除obj
原型链上的z
属性。
删除之后,obj
对象中就不存在z
这个属性(key)了,然后再访问z
这个属性的时候,就会在原型链上查找。