对原型及原型链的简单理解

2018-11-14
本文约1.2k字

JavaScript的语言特点

不同于Java和C++这些基于类的面向对象语言,JavaScript是一种基于原型的面向对象语言,因为JavaScript没有类的概念。

万物皆对象?

JavaScript中一共有6种主要类型:stringnumberbooleannullundefinedobject,常见到的关于“JavaScript中万物皆对象”的说法其实是错误的,stringbooleannumbernullundefined本身并不是对象,即使typeof null会返回object也是因为不同的对象在底层都表示为二进制,在JavaScript中如果二进制前三位都为0的话会被判断为object类型,null的二进制表示是全0,自然前三位也是0,所以执行typeof时会返回“object”。

造成万物皆对象的错觉实际上是因为JavaScript中有许多特殊的对象子类型,也被称为内置对象,包括StringNumberBooleanObjectFunctionArrayDateRegExpError。这些内置函数可以通过new调用来当作构造函数,引擎可以根据实际情况将stringnumber等字面量转换成对象。

__proto__和prototype

在JavaScript中,对象是拥有属性和方法的数据。每一个对象都有一个叫做__proto__的内置属性,它包含对指定对象的内部原型的引用。

对象的创建基本有3种方法:

1、字面量创建

1
2
3
var foo = {
name:blackstar
}

2、构造函数

1
2
3
4
function Foo(){
this.name = blackstar
}
var foo = new Foo()

3、Object.create()

1
2
3
4
var foo1 = {
name:blackstar
}
var foo2 = Object.create(foo1)

补充一个小Tips:var foo = {}其实是var foo = new Object()的语法糖。相似的,function Foo(){}var Foo = new Function()的语法糖、var arr = []var arr = new Array()的语法糖。

所以前两种方法都是借助了构造函数创建对象,这种方式创建的对象__proto__属性指向其构造函数的prototype方法。每一个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。要注意:只有函数对象才有prototype,普通对象并没有

说得再多可能都没有一个例子来得更直观易懂,来吧!

1
2
3
4
5
6
7
function Foo(){
this.name = 'blackstar'
}
var foo = new Foo()
console.log(foo.__proto__)
console.log(Foo.prototype)
console.log(foo.__proto__===Foo.prototype)

通过构造函数Foo我们创建了一个对象foo,让我们到控制台里去看一看它的__proto__指向什么?

shot1

确实对象foo的__proto__指向了其构造函数Foo的prototype,Foo.prototype就是foo的原型对象。

构造函数的prototype中有一个constructor属性指回到该构造函数,实例对象也有一个constructor属性指向创建它的构造函数。

1
2
3
Foo.prototype.constructor === Foo

foo.constructor === Foo

大部分情况foo.__proto__ === foo.constructor.prototype(不包括Object.create()创建的) ,因为Object.create()的作用是创建一个具有指定原型且可选择性地包含指定属性的对象,它所创建的对象的__proto__指向的是这个指定原型。

这其中指向来指向去的关系结合图示去理解会更容易一些:

prototype

有一个很有趣的事情,Object.__proto__===Function.prototype,这是因为Object的构造函数是Function。

原型链

使用原型对象的好处是可以让所有的对象实例共享它所包含的属性和方法,也就是说可以不必在构造函数中定义对象实例的信息,而是直接将它们添加到原型对象中。js引擎查找对象属性时先查找对象本身是否存在,如果没有就去它的原型对象上查找,由于所有的对象都有__proto__属性,这就形成了一个链条,也就是原型链。

改造一下上面的例子:

1
2
3
4
5
6
function Foo(){
this.name = 'blackstar'
}
Foo.prototype.age = 25
var foo = new Foo()
console.log(foo.age)

shot2

我们为foo的原型对象添加age这个属性,寻找age属性时由于foo自身并没有,于是向上查找它的原型对象,诶,真好Foo.prototype上有,完毕。

这个原型链如下图所示,原型链的顶端是Object.prototype,而__proto__最终指向null

prototype chain

原型链继承

原型对象可以让所有的对象实例共享它所包含的属性和方法,利用这一特点我们可以实现继承。

1
2
3
4
5
6
7
8
9
10
11
function Cat(name){
this.name = name
}
function Animal(){
this.eat = function(){
console.log('eat food')
}
}
Cat.prototype = new Animal()
var miao = new Cat('miao')
console.log(miao.eat())

shot3

通过将miao的原型对象指向Animal便继承了原本自身没有的eat方法。

关于继承,这里只是引申一个简单的例子,更详细的值得另开篇章讨论,这里暂且不细说。