要想理解JavaScript中的原型,首先要从对象说起。
对象
JavaScript中一切都是对象,相信这句话大家都不陌生,但是其实在JavaScript中这句话并不准确。JavaScript中的typeof函数可以返回变量的类型。总共有两大类型,值类型(包括undefined、number、string、boolean)和引用类型(包括function、object)。只有引用类型的变量才能算是对象。函数、数组、对象、null、new Number(10)都是对象,他们都是引用类型。
JavaScript中对象是若干属性的集合,只有属性没有方法,方法也是一种属性。下面是一个常见的对象的例子:
|
|
属性a是一个数值,属性b是一个对象,属性c是一个函数。
函数和对象的关系
上面讲到函数也是一种对象,但是函数和对象之间的关系比较复杂,因为对象不仅可以通过上述方式创建还可以通过函数创建,如下:
|
|
其实对象那一节所讲的对象创建方式的本质是:
|
|
其中Object和Array都是函数。
到此我们得到一个很玄乎的概念,对象是由函数创建的,而函数却又是一种对象。想要搞清他们之间的关系就要进入我们的正题原型的概念了。
prototype原型
函数是一种对象,对象是若干属性的集合,所以函数一定有属性。每一个函数都有一个prototype属性,该属性的值又是一个对象。默认的只有一个叫做constructor的属性,指向这个函数本身。
如上图,myFunction是一个函数,右侧方框是它的原型。原型可以自定义增多很多属性,例如Object函数的原型中有hasOwnProperty、isPrototypeOf等属性。所以,我们可以在自定义的函数的prototype中增加自己的属性。如下:
|
|
上述代码中,Person是一个函数,person是从Person函数中new出来的,person对象就可以调用Person.prototype中的属性。因为每个对象都有一个隐藏的属性“__proto__”,这个属性引用了创建这个对象的函数的prototype。即:person.__proto__ === Person.prototype。这里的”__proto__“称为隐式原型。
__proto__是一个隐藏的属性,开发者用不到这个属性值。既然每个对象都有一个__proto__属性,那么Object.prototype也是一个对象,它的prototype是什么呢?Object.prototype是一个特例,它的__proto__指向null。
函数也是一种对象,函数当然也有__proto__。函数的__proto__指向Function.prototype,因为函数对象都是由Function创建的。
当然Function也是一个函数,函数是一种对象,也有__proto__属性。既然是函数,那么它一定是被Function创建。所以Function是被自身创建的。所以它的__proto__指向了自身的Prototype。(有点晕,哈哈)
最后,Function.prototype指向对象,它的__proto__也指向Object.prototype。因为Function.prototype指向的对象也是一个普通的被Object创建的对象,所以也遵循基本的规则。
instanceof
typeof函数可以很清楚判断一个值类型变量的类型,但是对于引用类型的变量它只能判断出是object还是function,不能判断他具体是通过那个函数new出来的。这只就需要用到instanceof了。
instanceof运算符的第一个变量是一个对象,称为VAR;第二个变量一般是一个函数,称为FN。
instanceof的判断队则是:沿着VAR的__proto__这条线来找,同时沿着FN的prototype这条线来找,如果两条线能找到同一个引用,即同一个对象,那么就返回true。如果找到终点还未重合,则返回false。通过下面的例子进行进一步理解。
|
|
有过面向对象变成经验的同学应该可以想到,instanceof表示的就是一种继承关系,在js中也可以说是一种原型链的结构。
原型链
|
|
以上代码中,foo是Foo函数new出来的对象,foo.a是foo对象的基本属性,foo.b是从Foo.prototype得来,因为foo.__proto__指向的是Foo.prototype。
访问一个对象的属性时,先在其基本属性中查找,如果没有,再沿着__proto__这条链向上找,这就是原型链。访问foo.b时,f1的基本属性中没有b,于是沿着__proto__找到了Foo.prototype.b。
另外,所有的对象的原型链都会找到Object.prototype,因此所有的对象都会有Object.prototype的方法。这就是所谓的继承。