JavaScript原型
原型是一个对象,其他对象可以通过它实现属性继承。任何一个对象都可以成为原型。
所有的对象在默认的情况下都有一个原型,因为原型本身也是对象,所以每个原型自身又有一个原型(只有一种例外,默认的对象原型在原型链的顶端),这就是我们能通过原型实现继承的原因。
下面举个例子来说明原型中的东西:
function Person() { Person.prototype.name = "Altria"; Person.prototype.age = "16"; Person.prototype.job = "Saber"; Person.prototype.sayName = function () { console.log(this.name); } } var person1 = new Person(); person1.sayName(); // "Altria" var person2 = new Person(); person2.sayName(); // "Altria" console.log(person1.sayName == person2.sayName); //true
一个对象的真正原型是被对象内部的 [[Prototype]] 属性(property) 所持有。在所有实现中都无法访问到 [[Prototype]] ,但可以通过 isPrototypeOf() 方法来确定对象之间是否存在这种关系。如果 [[Prototype]] 指向调用 isPrototypeOf() 方法的对象( Person.prototype),那么这个方法就返回 true,如下所示:
console.log(Person.prototype.isPrototypeOf(person1)); //ture console.log(Person.prototype.isPrototypeOf(person2)); //ture
这里用原型对象的 isPrototypeOf() 方法测试了 person1 和 person2 。因为他们内部都有一个指向 Person.prototype 的指针,因此都返回了true。
ECMAScript5 增加了一个方法 Object.getPrototypeOf() ,可以方便地取得一个对象的原型,在所有支持的实现中,这个方法返回 [[Prototype]] 的值,例如:
console.log(Object.getPrototypeOf(person1) == Person.prototype); //true console.log(Object.getPrototypeOf(person2).name) //"Altria"
第一行代码确定 Object.getPrototypeOf() 返回的对象就是这个对象的原型;第二行代码取得原型对象中 name 属性的值 "Altria" 。
遇到浏览器不支持这个方法的时候,可以用非标准的访问器 __proto__ (IE不支持),如果这两者都不起作用的,我们需要从对象的构造函数中找到的它原型。下面的代码展示了获取对象原型的方法:
var a = {}; //Firefox 3.6 and Chrome 5 Object.getPrototypeOf(a); //[object Object] //Firefox 3.6, Chrome 5 and Safari 4 a.__proto__; //[object Object] //all browsers a.constructor.prototype; //[object Object]
hasOwnProperty() 方法可以检测一个属性是存在于实例中还是存在于原型中。这个方法(是从 Object 继承来的)只在给定属性存在于对象实例中时,才会返回 true。
function Person() {
Person.prototype.name = "Altria";
Person.prototype.age = "16";
Person.prototype.job = "Saber";
Person.prototype.sayName = function () {
console.log(this.name);
}
}
var person1 = new Person();
var person2 = new Person();
console.log(person1.hasOwnProperty("name")); //false
person1.name = "Sakura";
console.log(person1.name); //"Sakura" ----来自实例
console.log(person1.hasOwnProperty("name")); //true
console.log(person2.name); //"Altria" ----来自原型
console.log(person2.hasOwnProperty("name")); //false
原型实现继承
假如我们已经创建了一个实例对象 ,我们想要继承一个已经存在的对象的功能,比如说Array,我们可以像下面这样做( 在支持__proto__ 的浏览器中):
//IE中无效 var a = {}; a.__proto__ = Array.prototype; a.length; //0
在上面这个例子中,首先创建了一个对象a,然后通过a的原型来达到继承Array 这个已经存在的对象的功能的目的。
但原型真正魅力体现在多个实例共用一个通用原型的时候。原型对象(也就是一个对象的原型所引用的对象)的属性一旦定义,就可以被多个引用它的实例所继承(即这些实例对象的原型所指向的就是这个原型对象)。
我们先来搞清楚 constructor.prototype 属性是什么。
javascript并没有在构造函数(constructor)和其他函数之间做区分,所以说每个函数都有一个原型属性。反过来,如果不是函数,将不会有这样一个属性:
//普通函数不会成为一个构造函数,但是它依然可以有原型属性 Math.max.prototype; //[object Object] //函数旨在成为一个构造函数的原型 var A = function(name) { this.name = name; } A.prototype; //[object Object] //Math 不是函数因此没有原型属性 Math.prototype; //null
可以这样说,函数A的原型属性(prototype property )是一个对象,当这个函数A被用作构造函数来创建实例时,该函数的原型属性将被作为原型赋值给所有对象实例(即所有实例的原型引用的是函数的原型属性)。
下面的代码可以更加详细的说明问题:
//创建一个函数 b var b = function () { var one; } //使用b创建一个对象实例c var c = new b(); //查看 b 和 c 的构造函数 b.constructor; // function Function() { [native code]} b.constructor == Function.constructor; //true c.constructor; //实例c的构造函数 即 b function(){ var one; } c.constructor == b //true //b 是一个函数,查看b的原型如下 b.constructor.prototype // function (){} b.__proto__ //function (){} //b 是一个函数,由于javascript没有在构造函数constructor和函数function之间做区分,所以函数像constructor一样, //也有一个原型属性,这和函数的原型( b.__proto__ 或者 b.construtor.prototype)是不一样的 b.prototype //[object Object] 函数b的原型属性 b.prototype==b.constructor.prototype //fasle b.prototype==b.__proto__ //false b.__proto__==b.constructor.prototype //true //c是一个由b创建的对象实例,查看 c 的原型如下 c.constructor.prototype //[object Object] 这是对象的原型 c.__proto__ //[object Object] 这是对象的原型 c.constructor.prototype==b.constructor.prototype; //false c 的原型和 b 的原型比较 c.constructor.prototype==b.prototype; //true c 的原型和 b 的原型属性比较 //为函数 b 的原型属性添加一个属性max b.prototype.max = 3 //实例 c 也有了一个属性max c.max //3
//上面的例子中,对象实例 c 的原型和函数的 b 的原型属性是一样的,如果改变 b 的原型属性,则对象实例 c
的原型也会改变
要弄清楚 :一个函数的原型属性(function’s prototype property )其实和实际的原型(prototype)是没有关系的!
//(例子在IE中无效) var A = function(name) { this.name = name; } A.prototype == A.__proto__; //false A.__proto__ == Function.prototype; //true // 函数 A 的原型被设置为她的构造函数的原型属性
再来看看这个例子:
//构造函数。 this 作为一个新的对象被返回,并且它内部的 [[prototype]] 属性被设置为构造函数的默认原型属性 var Circle = function(radius) { this.radius = radius; //this.__proto__ = Circle.prototype; //这行是隐含的,只是为了说明而加 } //拓展 Circle 的默认原型属性从而拓展每个普通实例的原型 Circle.prototype.area = function() { return Math.PI*this.radius*this.radius; } //创建两个 Circle 的实例 var a = new Circle(3), b = new Circle(4); a.area().toFixed(2); //28.27 b.area().toFixed(2); //50.27
如果我更改了构造函数的原型,是不是意味着已经存在的该构造函数的实例将获得构造函数的最新版本呢?
不一定。假如修改的是原型属性,那么这样的改变将会发生。因为在 a 实际被创建之后,a.__proto__ 实际上一个是对 A.prototype 的引用。
var A = function(name) { this.name = name; } var a = new A('adam'); a.name; //'adam' A.prototype.x = 23; a.x; //23 A.__proto__.max = 20150101; a.max //undefined
但是如果我现在替换A的原型属性为一个新的对象,实例对象的原型a.__proto__却仍然引用着原来它被创建时A的原型属性。
var A = function(name) { this.name = name; } var a = new A('alpha'); a.name; //'alpha' A.prototype = {x:23}; a.x; //null
即如果在实例被创建之后,改变了函数的原型属性所指向的对象,也就是改变了创建实例时实例原型所指向的对象。
但是这并不会影响已经创建的实例的原型。
对原生态的构造函数Function,String等,扩展这个属性,我们可以使用原型达到扩展指定构造函数的所有实例的目的。
如下,对所有的string实例都实现了times这个方法,对字符串本身进行指定数目的复制。
String.prototype.times = function(count) { return count < 1 ? '' : new Array(count + 1).join(this); } "hello!".times(3); //"hello!hello!hello!"; "please...".times(6); //"please...please...please...please...please...please..."
因为每个对象和原型都有一个原型(原型也是一个对象),对象的原型指向对象的父,而父的原型又指向父的父,我们把这种通过原型层层连接起来的关系撑为原型链。这条链的末端一般总是默认的对象原型。这就是利用原型实现继承的方法。
a.__proto__ = b; b.__proto__ = c;
c.__proto__ = {}; //default object {}.__proto__.__proto__; //null
参考文章: http://blog.jobbole.com/9648/