一、原型
每个函数都有一个prototype属性,这个属性是指向一个对象的引用,这个对象称为原型对象,原型对象包含函数实例共享的方法和属性,也就是说将函数当作构造函数来调用的时候(使用new操作符调用),新创建的对象会从原型对象上继承属性和方法。
无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,默认情况下prototype属性会默认获得一个constructor(构造函数)属性,这个属性是一个指向prototype属性所在函数的指针。
var helloworld = new Function('return "Hello World";'); console.log(helloworld());//输出Hello World console.log(helloworld.prototype);//Object{} console.log(helloworld.prototype.constructor);//输出function anonymous() {return "Hello World";}
二、构造函数实例都拥有指向构造函数的constructor属性
默认情况下prototype属性都会获得一个constructor属性,因此任何对象实例化时,在幕后都会为实例添加constructor属性,并且这个属性指向创建对象的构造函数。下面创建一个Object对象,保存在变量foo中,然后验证constructor属性在创建对象后是否可用。
var foo = {}; console.log(foo.hasOwnProperty('constructor'));//输出false console.log(foo.constructor === Object); //输出true,因为object()构建了foo console.log(foo.constructor); //输出Object构造函数:function Object() { [native code] }
特别的,在原始值上使用constructor属性能够指向正确的构造函数。举一个例子:
var foo = 23; console.log(foo.constructor === Number); // 输出true
constructor属性也适用于用户自定义的构造函数。如下代码中,我们定义了一个Person()构造函数,然后使用new关键字调用构造函数来生成一个对象,一旦创建了对象,就可以使用constructor属性了。
var Person = function Person() { return 'Wow!'; }; var person = new Person(); console.log(person.constructor === Person); // 输出true console.log(person.constructor);//输出function Person() { return 'Wow!'; }
三、原型在所有function()实例上都是标准的
即使不直接使用Function()构造函数(如var add = new Function('x', 'y', 'return x + y;');),而是使用字面量表示法(如var add = function(x, y) { return x + y; }),结果也都是一样,它总是拥有一个prototype属性,默认的prototype属性是一个空对象。
var helloworld = function () { return 'Hello World'; }; console.log(helloworld.prototype);//输出Object{},如果给function命名为Foo,则这里显示为Foo {}
事情上,上面这段代码相当于如下的代码:
var helloworld = function() { return 'Hello World'; }; foo.prototype = {}; console.log(helloworld.prototype);//输出Object{}
四、将构造函数创建的实例链接至构造函数的prototype属性
当调用构造函数创建一个实例的时候,实例内部将包含一个内部指针(很多浏览器这个指针名字为__proto__)指向构造函数的prototype,这个连接存在于实例和构造函数的prototype之间,而不是实例与构造函数之间。
Array.prototype.foo = 'foo'; var myArray = new Array(); console.log(myArray.__proto__.foo);//输出foo //__proto__并不是ECMA标准,更通用的方法如下 console.log(myArray.constructor.prototype.foo);//输出foo
五、原型链的最后是Object.prototype
每个对象都有一个连接到原型的隐藏连接(__proto__),对象字面量产生的对象连接到Object.prototype,函数对象连接到Function.prototype(该原型对象本身连接到Object.prototype)。
原型链只有在检索值的时候才被用到,如果我们尝试去获取对象的某个属性值,但该对象没有此属性,那么JavaScript会试着从原型对象中获取属性值。如果那个原型对象也没有该属性,那么再从它的原型中寻找,依此类推,直到该过程最后到达终点Object.prototype。如果这个属性完全不存在于原型链中,那么结果就是undefined值。
六、用新对象替换prototype属性会删除默认构造函数属性
使用构造函数创建出来的实例,其constructor属性是从prototype里继承来的,但依然用instanceof判断类型。
var Foo = function Foo() {}; var Bar = function Bar() {}; Foo.prototype = { constructor: Bar }; var foo = new Foo(); console.log(foo.constructor);//输出function Bar() {} console.log(foo.constructor === Bar);//输出true console.log(foo instanceof Foo);//输出true console.log(foo instanceof Bar);//输出false
七、创建继承链
设计原型继承的目的是要在传统的面向对象编程语言中找到模仿继承模式的继承链,继承只是一个对象可以访问另外一个对象的属性。
var Person = function Person() { //this.foo = 'thisFOO'; }; Person.prototype.foo = 'FOO'; var Teacher = function Teacher() { }; Teacher.prototype.bar = 'BAR'; Teacher.prototype = new Person(); var teacher = new Teacher(); console.log(teacher.constructor); //输出Person() {},因为这里teacher对象与new Person()对象都没有constructor,所以这里的constructor是Person原型的constructor。 console.log(teacher instanceof Teacher); //输出true,虽然teacher.constructor为Person,但teacher为Teacher的实例。 /** * 1、teacher没有foo、bar属性,去teacher的原型里找,这里teacher的原型是Person的实例; * 2、在Person实例里依然找不到这2个属性; * (如果打开注释this.foo = 'thisFOO';,则foo属性至此已找到,输出thisFOO) * 3、因为在teacher的原型里没有找到foo、bar属性,则继续在Person实例的原型里找 * (这里指的是Person.prototye),这一步找到了foo属性,输出FOO; * 4、而bar属性到最后的Object.prototype依然找不到,所以输出undefined */ console.log(teacher.foo, teacher.bar);