分析javascript原型及原型链
我们创建的每个函数都有一个 prototype (原型)属性,这个属性是一个指针,指向一个原型对象,而这个原型对象中拥有的属性和方法可以被所以实例共享。
function person(){ } person.prototype.name = "nicholas"; person.prototype.age = 29; person.prototype.sayname = function(){ alert(this.name); }; var person1 = new person(); person1.sayname(); //"nicholas" var person2 = new person(); person2.sayname(); //"nicholas" alert(person1.sayname == person2.sayname); //true
一、理解原型对象
无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个 prototype属性,这个属性指向函数的原型对象。
在默认情况下,所有原型对象都会自动获得一个 constructor(构造函数)属性,这个属性包含一个指向 prototype 属性所在函数的指针。
当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。ecma-262 第 5 版中管这个指针叫 [[prototype]] 。
虽然在脚本中没有标准的方式访问 [[prototype]] ,但 firefox、safari 和 chrome 在每个对象上都支持一个属性__proto__ ;而在其他实现中,这个属性对脚本则是完全不可见的。
不过,要明确的真正重要的一点就是,这个连接存在于实例与构造函数的原型对象之间,而不是存在于实例与构造函数之间。
以前面使用 person 构造函数和 person.prototype 创建实例的代码为例,图 6-1 展示了各个对象之间的关系。
在此, person.prototype 指向了原型对象,而 person.prototype.constructor 又指回了 person 。
person1 和 person2 都包含一个内部属性,该属性仅仅指向了 person.prototype ;换句话说,它们与构造函数没有直接的关系。
可以调用 person1.sayname() 。这是通过查找对象属性的过程来实现的。(会先在实例上搜索,如果搜索不到就会继续搜索原型。)
用isprototypeof()方法判断实例与原型对象之间的关系
alert(person.prototype.isprototypeof(person1)); //true alert(person.prototype.isprototypeof(person2)); //true
用object.getprototypeof() 方法返回实例的原型对象
alert(object.getprototypeof(person1) == person.prototype); //true
使用 hasownproperty() 方法可以检测一个属性是存在于实例中,还是存在于原型中。
alert(person1.hasownproperty("name")); //false 来着原型
person1.name = "greg";
alert(person1.name); //"greg"——来自实例
alert(person1.hasownproperty("name")); //true
二、更简单的原型语法
前面例子中每添加一个属性和方法就要敲一遍 person.prototype 。为减少不必要的输入,也为了从视觉上更好地封装原型的功能,更常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象。
function person(){ } person.prototype = { name : "nicholas", age : 29, job: "software engineer", sayname : function () { alert(this.name); } };
在上面的代码中,我们将 person.prototype 设置为等于一个以对象字面量形式创建的新对象。最终结果相同,但有一个例外: constructor 属性不再指向 person 了。
前面曾经介绍过,每创建一个函数,就会同时创建它的 prototype 对象,这个对象也会自动获得 constructor 属性。
var friend = new person(); alert(friend instanceof object); //true alert(friend instanceof person); //true alert(friend.constructor == person); //false alert(friend.constructor == object); //true
在此,用 instanceof 操作符测试 object 和 person 仍然返回 true ,但 constructor 属性则等于 object 而不等于 person 了。
如果 constructor 的值真的很重要,可以像下面这样特意将它设置回适当的值。
function person(){ } person.prototype = { constructor : person, name : "nicholas", age : 29, job: "software engineer", sayname : function () { alert(this.name); } };
三、原生对象的原型
所有原生引用类型( object 、 array 、 string ,等等)都在其构造函数的原型上定义了方法。
例如,在 array.prototype 中可以找到 sort() 方法,而在 string.prototype 中可以找到substring() 方法。尽管可以这样做,但不推荐修改原生对象的原型。
四、原型对象的问题
原型模式的最大问题是由其共享的本性所导致的。 修改其中的一个,另一个也会受影响。
function person(){ } person.prototype = { constructor: person, name : "nicholas", age : 29, job : "software engineer", friends : ["shelby", "court"], sayname : function () { alert(this.name); } }; var person1 = new person(); var person2 = new person(); person1.friends.push("van"); alert(person1.friends); //"shelby,court,van" alert(person2.friends); //"shelby,court,van" alert(person1.friends === person2.friends); //true
五、原型链
其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。然后层层递进,就构成了实例与原型的链条,这就是所谓原型链的基本概念。
function supertype(){ this.property = true; } supertype.prototype.getsupervalue = function(){ return this.property; }; function subtype(){ this.subproperty = false; } //继承了 supertype subtype.prototype = new supertype(); subtype.prototype.getsubvalue = function (){ return this.subproperty; }; var instance = new subtype(); alert(instance.getsupervalue()); //true
一张图说明:
property 则位于 subtype.prototype 中。这是因为 property 是一个实例属性,而 getsupervalue() 则是一个原型方法。既然 subtype.prototype 现在是 supertype的实例,那么 property 当然就位于该实例中了。