JavaScript进阶:深入理解原型与原型链
一、普通对象和函数对象
对象可以分为普通对象和函数对象,Object和Function是ECMAScript是自带函数对象。看如下代码:
function fun1 () {};
var fun2 = new Function();
var ob1 = {};
var ob2 = new Object();
var ob3 = new fun1();
console.log (typeof Function); //Function
console.log(typeof Object); //Function
console.log(typeof fun1); //Function
console.log(typeof fun2); //Function
console.log(typeof ob1); //Object
console.log(typeof ob2); //Object
console.log(typeof ob3); //Object
在上面例子中ob1、2、3为普通对象。fun1、2为函数对象,因为fun1、fun2归根结底是通过new Function()创建的。
二、原型对象
在JavaScript中,每当定义一个对象的时候,对象中都包含一些预定义的属性。其中每个函数对象都会有一个prototype属性,这个属性指向函数的原型对象。这里有一个特别重要的特性:每个对象(普通对象和函数对象)都有_proto_属性,但是只有函数对象才有prototype属性
看如下代码:
function Person() {
Person.prototype.name = "tom";
Person.prototype.sayname = function () {
alert (this.name);
}
}
var person1 = new Person();
var person2 = new Person();
alert(person1.name == person2.name); //true,因为两个实例的name属性,都是同一属性,即原型对象中的name。
如果不好理解,可以把上面代码转换一下:
Person.propertype = {
name = "tom";
sayname = function () {
alert (this.name);
}}
上面提到对象可分为普通对象和函数对象,这里原型对象便是普通对象了。可以这样认为原型对像就是Person.prototype。
三、原型对象中的construtor属性
上述原型对象Person.prototype,这里只给了他一个name属性,默认情况下所有的原型对象自动获得一个construtor属性。construtor属性是一个指针,指向它的构造函数,在例子中指向Person。基本构造函数有Number()、Boolean()、String()、Function()、Object()等等。如果通过构造函数创建新对象,这种创建方法都有一个共同点,那就是代码格式为:var 【对象】 = new 【构造函数】。
Person.prototype.constructor == Person;//true
看看如下代码:
console.log(Person.prototype.constructor === person1.constructor); //true
这里严格相等的原因是实例person1的constructor属性和Person.prototype的constructor属性是同一样东西,正确地说person1的constructor属性继承于原型对象Person.prototype。
四、_proto_
JS在创建对象(不论是普通对象还是函数对象)的时候,都有一个叫做_proto_的内置属性,用于指向创建它的构造函数的原型对象。
对象person1有一个_proto_属性,创建它的构造函数是Person,构造函数的原型对象是Person.prototype,所以
person1._proto_ = Person.prototype;
Person的原型对象为Person.prototype;
Person.prototype.constructor = Person;
person1._proto_ = Person.prototype;
person1.constructor = Person;
五、检验对原型链的理解
1、person1_proto_是什么?
解:意思是实例对象person1的构造函数的原型对象,首先person1的构造函数是Person,然后Person的原型对象是Person.prototype。所以person1_proto = Person.prototype。
2、Person_proto_是什么?
解:因为Person在创建时是通过function Person(){}的形式创建的,所以Person的构造函数是Function,而Function的构造函数是Function.prototype。所以Person_proto = Function.prototype。
3、Person.prototype._proto_是什么?
解:在前面提过Person.prototype是一个普通对象,所以他的构造函数是Object,Object的原型对象是Object.prototype;
4、Object._proto_是什么?
解:Object和Person一样是函数对象,所以两者答案一样。
5、Object.prototype._proto_是什么?
解:这个比较特殊,结果为null,因为null位于原型链的顶端。
六、函数对象
所有函数对象,包括:Number、Boolean、String、Function、Object的_proto_都是Function.prototype。
Number.__proto__ === Function.prototype // true
Number.constructor == Function //true
Boolean.__proto__ === Function.prototype // true
Boolean.constructor == Function //true
String.__proto__ === Function.prototype // true
String.constructor == Function //true
Object.__proto__ === Function.prototype // true
Object.constructor == Function // true
Function.__proto__ === Function.prototype // true
Function.constructor == Function //true
Array.__proto__ === Function.prototype // true
Array.constructor == Function //true
RegExp.__proto__ === Function.prototype // true
RegExp.constructor == Function //true
Error.__proto__ === Function.prototype // true
Error.constructor == Function //true
Date.__proto__ === Function.prototype // true
Date.constructor == Function //true
ECMAScript中内置构造器有12个,这里列举了8个构造器。剩下的如Global不可以访问,Arguments仅在函数调用时由JS引擎创建,Math、JSON是以对象形式存在的。他们的构造函数是Object,_proto_是Object.prototype。如下代码:
Math.__proto__ === Object.prototype // true
Math.construrctor == Object // true
JSON.__proto__ === Object.prototype // true
JSON.construrctor == Object //true
所有的构造器都继承于Function.prototype,甚至包括根构造器Object和Function自身。
Function.prototype也是唯一一个类型为Function的原型对象。其他构造器的原型对象的类型都是Object。看如下代码:
console.log(typeof Function.prototype) // function
console.log(typeof Object.prototype) // object
console.log(typeof Number.prototype) // object
console.log(typeof Boolean.prototype) // object
console.log(typeof String.prototype) // object
console.log(typeof Array.prototype) // object
console.log(typeof RegExp.prototype) // object
console.log(typeof Error.prototype) // object
console.log(typeof Date.prototype) // object
console.log(typeof Object.prototype) // object
这里可能有一个疑问,Function.prototype是一个Function,那么Function.prototype的构造函数应该是Function,Function.prototype._proto_岂不是Function.prototype,这样就进入一个死循环。其实ESMASript中Function.prototype._proto是Object.prototype,而Object.prototype._proto_则是原型链最顶端的null。
六、构造器的继承
在Object的原型对象里面有着如下图中的所有属性,
而当我们创建一个数组时:
var arr = new Array();
arr继承了Array.prototype的所有属性和方法,如下图:
但是却没有找到constructor,按照道理来说,每一个函数对象的原型对象都应该有constroctor才对。原因是,因为Array.prototype是一个对象,Array.prototype继承了Object.prototype的所有属性和方法,所以Array.prototype最终是拥有constructor属性的。