认识原型
开始原型的介绍之前,首先来认识一下什么是原型?
在JavaScript中,原型也是一个对象,通过原型可以实现对象的属性继承, JavaScript的对象中都包含了一个" [[Prototype]]"内部属性,这个属性所对应的就是该对象的原型。
"[[Prototype]]"作为对象的内部属性,是不能被直接访问的。所以为了方便查看一个对象的原型,Firefox和Chrome中提供了"proto"这个非标准(不是所有浏览器都支持)的访问器(ECMA引入了标准对象原型访问器"Object.getPrototype(object)")。
实例分析(在此过程中自己可以画图 到最后和我们图例对比)
function Person(name, age){
this.name = name;
this.age = age;
this.getInfo = function(){
console.log(this.name + " is " + this.age + " years old");
};
}
var will = new Person("Will", 28);
在上面的代码中,通过了Person这个构造函数创建了一个will对象。下面就通过will这个对象一步步展开了解原型。
Step1 查看对象will的原型
通过下面代码,可以查看对象will的原型:
```
console.log(will.__proto__);
console.log(will.constructor);
```
结果分析:
-
"Person {}"对象就是对象will的原型,通过Chrome展开可以看到,"Person {}"作为一个原型对象,也有"proto"属性(对应原型的原型)。
-
在这段代码中,还用到了"constructor"属性。在JavaScript的原型对象中,还包含一个"constructor"属性,这个属性对应创建所有指向该原型的实例的构造函数。
-
通过"constructor"这个属性,我们可以来判断一个对象是不是数组类型
function isArray(myArray) { return myArray.constructor.toString().indexOf("Array") > -1; } -
在这里,will对象本身并没有"constructor"这个属性,但是通过原型链查找,找到了will原型(will.proto)的"constructor"属性,并得到了Person函数。
-
Step2 查看对象will的原型(will.proto)的原型
既然will的原型"Person {}"也是一个对象,那么我们就同样可以来查看"will的原型(will.proto)的原型"。
运行下面的代码:
console.log(will.__proto__ === Person.prototype);
console.log(Person.prototype.__proto__);
console.log(Person.prototype.constructor);
console.log(Person.prototype.constructor === Person);
结果分析:
-
首先看 "will.proto === Person.prototype",在JavaScript中,每个函数都有一个prototype属性,当一个函数被用作构造函数来创建实例时,该函数的prototype属性值将被作为原型赋值给所有对象实例(也就是设置实例的__proto__属性),也就是说,所有实例的原型引用的是函数的prototype属性。了解了构造函数的prototype属性之后,一定就明白为什么第一句结果为true了。
- prototype属性是函数对象特有的,如果不是函数对象,将不会有这样一个属性。
-
当通过"Person.prototype.proto"语句获取will对象原型的原型时候,将得到"Object {}"对象,后面将会看到所有对象的原型都将追溯到"Object {}"对象。
-
对于原型对象"Person.prototype"的"constructor",根据前面的介绍,将对应Person函数本身。
通过上面可以看到, "Person.prototype"对象和Person函数对象通过"constructor"和"prototype"属性实现了相互引用(后面会有图展示这个相互引用的关系) 。
Step 3: 查看对象Object的原型
通过下面代码,可以查看对象will的原型:
console.log(will.__proto__);
console.log(will.constructor);
结果分析:
-
"Person {}"对象就是对象will的原型,通过Chrome展开可以看到,"Person {}"作为一个原型对象,也有"proto"属性(对应原型的原型)。
-
在这段代码中,还用到了"constructor"属性。在JavaScript的原型对象中,还包含一个"constructor"属性,这个属性对应创建所有指向该原型的实例的构造函数。
-
通过"constructor"这个属性,我们可以来判断一个对象是不是数组类型
function isArray(myArray) { return myArray.constructor.toString().indexOf("Array") > -1; } -
在这里,will对象本身并没有"constructor"这个属性,但是通过原型链查找,找到了will原型(will.proto)的"constructor"属性,并得到了Person函数。
-
Step 4: 查看对象Function的原型
在上面的例子中,Person是一个构造函数,在JavaScript中函数也是对象,所以,我们也可以通过"proto"属性来查找Person函数对象的原型。
console.log(Person.__proto__ === Function.prototype);
console.log(Person.constructor === Function)
console.log(typeof Function);
console.log(Function);
console.log(Function.prototype);
console.log(Function.prototype.__proto__);
console.log(Function.prototype.constructor);
结果分析 :
- 在JavaScript中有个Function对象(类似Object),这个对象本身是个函数;所有的函数(包括Function,Object)的原型(proto)都是"Function.prototype"。
- Function对象作为一个函数,就会有prototype属性,该属性将对应"function () {}"对象。
- Function对象作为一个对象,就有"proto"属性,该属性对应"Function.prototype",也就是说,"Function.proto === Function.prototype"
- 对于Function的原型对象"Function.prototype",该原型对象的"proto"属性将对应"Object {}"
对比prototype和__proto__(通过以上案例得出此结论)
对于"prototype"和"proto"这两个属性有的时候可能会弄混,"Person.prototype"和"Person.proto"是完全不同的。
在这里对"prototype"和"proto"进行简单的介绍:
- 对于所有的对象,都有__proto__属性,这个属性对应该对象的原型
- 对于函数对象,除了__proto__属性之外,还有prototype属性,当一个函数被用作构造函数来创建实例时,该函数的prototype属性值将被作为原型赋值给所有对象实例(也就是设置实例的__proto__属性)
图例结合(得出此图)
通过上面结合实例的分析,相信你一定了解了原型中的很多内容。
但是现在肯定对上面例子中的关系感觉很凌乱,一会儿原型,一会儿原型的原型,还有Function,Object,constructor,prototype等等关系。
现在就对上面的例子中分析得到的结果/关系进行图解,相信这张图可以让你豁然开朗。
对于上图的总结如下:
- 所有的对象都有"proto"属性,该属性对应该对象的原型
- 所有的函数对象都有"prototype"属性,该属性的值会被赋值给该函数创建的对象的"proto"属性
- 所有的原型对象都有"constructor"属性,该属性对应创建所有指向该原型的实例的构造函数
- 函数对象和原型对象通过"prototype"和"constructor"属性进行相互关联