JavaScript 的原型物件總是讓人糾結。即使是經驗豐富的JavaScript專家甚至是其作者,經常對這個概念給出很有限的解釋。我相信問題來自於我們對原型最早的認識。原型總是與new, constructor 以及令人困惑的prototype屬性緊密聯繫。事實上,原型是一個相當簡單的概念。為了更好地理解它,我們需要忘記我們所『學到』的構造原型,然後,追本溯源。
什麼是原型?
原型是一個從其他物件繼承屬性的物件。
是不是任何物件都可以是原型?
是的
那些物件有原型?
每個物件都有一個預設的原型。原型本身就是對象,每一個原型本身也存在著一個原型。 (只有一個例外,預設的物件原型在每條原型鏈的頂端,其他的原型在原型鏈的後面)
退一步說,什麼又是對象呢?
在JavaScript中一個物件是以鍵值對保存的任意的無序集合,如果它不是原始類別(undefined,null,boolean.nuber或string),它就是一個物件。
你可以認為每個物件都有一個prototype. 但當我寫({}).prototype的時候,我卻得到了undefined,你瘋不瘋?
忘記你所掌握的關於prototype屬性的理解- 這很可能是迷惑的根源. 一個物件真正的prototype是內部[[Prototype]]屬性. ECMA 5介紹了標準的存取方法,Object.getPrototypeOf(object )。這個最新的實作已被Firefox, Safari, Chrome and IE9所支援. 另外,除了IE,所有的瀏覽器都支援非標準的存取方法__proto__.不然的話,我們只能說物件的建構方法就是它的prototype屬性.
var a = {}; //Opera 或 IE<=8下失败 Object.getPrototypeOf(a); //[object Object] //IE下失败 a.__proto__; //[object Object] //所有浏览器 //(but only if constructor.prototype has not been replaced and fails with Object.create) a.constructor.prototype; //[object Object]
很好, false 是原始型別, 為什麼false.__proto__ 會回傳一個值呢?
當訪問原始類型的原型(prototype),它會強制轉化為一個物件。
//(works in IE<=8 too, due to double-negative) false.__proto__ === Boolean(false).__proto__; //true
我想使用原型實作繼承,我現在該怎麼做?
為一個實例添加原型屬性,幾乎是沒有意義的.除非一種情況,那就是,很有效率的添加屬性直接到實例本身.假設我們已經有了一個對象,要共享已經存在的對象的功能.例如Array,我們可以這樣做
//fails in IE<=8 var a = {}; a.__proto_ = Array.prototype; a.length; //0
但是我們可以看到原型的真正強大在於多個實例共享相同原型。原型物件的屬性只被定義一次就可以被它所引用的所有實例所繼承。使用原型對效能和程式可維護性的提升效果是很顯而易見的。那麼這就是構造函數產生的原因嗎?是的,建構函式提供了一個便捷的跨瀏覽器機制來實現實例建立時的公用原型分配。 。
在給一個例子之前,我需要知道constructor.prototype property是做什麼的?
好吧,首先,JavaScript不區分構造函數和其它方法,所以每個方法都有prototype屬性。反而任何不是方法的,都沒有這樣的屬性。
//永远不是构造函数的方法,无论如何都是有prototype属性的 Math.max.prototype; //[object Object] //构造函数也有prototype属性 var A = function(name) { this.name = name; } A.prototype; //[object Object] //Math不是一个方法,所以没有prototype属性 Math.prototype; //null
現在可以定義: 一個方法的prototype屬性是當這個方法被用作建構子來建立實例時賦給該實例的prototype的物件。
非常重要的一點是,要理解方法的prototype屬性和實際的prototype沒有任何關係。
//(在IE中会失败) var A = function(name) { this.name = name; } A.prototype == A.__proto__; //false A.__proto__ == Function.prototype; //true - A的prototype是它的构造函数的prototype属性
能給個例子不?
以下的程式碼,可能你已經看過或用過上百次了,但這裡又把它搬上來了,但可能會有些新意。
//构造器. <em>this</em> 作为新对象返回并且它内部的[[prototype]]属性将被设置为构造器默认的prototype属性 var Circle = function(radius) { this.radius = radius; //next line is implicit, added for illustration only //this.__proto__ = Circle.prototype; } //扩充 Circle默认的prototype对象的属性因此扩充了每个由它新建实例的prototype对象的属性 Circle.prototype.area = function() { return Math.PI*this.radius*this.radius; } //创建Circle的两个示例,每个都会使用相同的prototype属性 var a = new Circle(3), b = new Circle(4); a.area().toFixed(2); //28.27 b.area().toFixed(2); //50.27
這很棒。如果我改變了constructor的prototype屬性,即使是已存在的實例物件也可以立刻存取新的prototype版本嗎?
嗯......不完全是。如果我修改的是現存prototype的屬性後,那麼確實是這種情況,因為物件建立時a.__proto__引用了A.prototype所定義的物件。
var A = function(name) { this.name = name; } var a = new A('alpha'); a.name; //'alpha' A.prototype.x = 23; a.x; //23
但是如果我將prototype屬性用一個新物件代替,a.__proto__ 仍然指向原始物件。
var A = function(name) { this.name = name; } var a = new A('alpha'); a.name; //'alpha' A.prototype = {x:23}; a.x; //null
一個缺省的prototype是什麼樣的?
一個擁有constructor屬性的物件。
var A = function() {}; A.prototype.constructor == A; //true var a = new A(); a.constructor == A; //true (a 的constructor属性继承自它的原型)
instanceof與prototype有啥關係?
如果A的prototype屬性出現在a的原型鏈中,則表達式a instanceof A會傳回true。這意味著我們可以欺騙instanceof,讓它失效。
var A = function() {} var a = new A(); a.__proto__ == A.prototype; //true - so instanceof A will return true a instanceof A; //true; //mess around with a's prototype a.__proto__ = Function.prototype; //a's prototype no longer in same prototype chain as A's prototype property a instanceof A; //false
那么我还能利用原型干些其它的什么事儿?
记得我曾经说过每一个构造器都拥有一个prototype属性,利用该属性可以将原型赋值给所有由构造器产生的实例?其实这同样适用于本地构造器,例如Function和String。通过扩展(而不是替换)这个属性,我们可以更新每个指定类型对象的prototype。
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..."
告诉我更多关于继承与原型是怎么工作的。原型链又是什么东东?
因为每个对象和每个原型(本身)都有一个原型,我们可以想象, 一个接一个的对象连接在一起形成一个原型链。 原型链的终端总是默认对象(object)的原型。
a.__proto__ = b; b.__proto__ = c; c.__proto__ = {}; //默认对象 {}.__proto__.__proto__; //null
原型继承机制是内在且隐式实现的。当对象a要访问属性foo时,Javascript会遍历a的原型链(首先从a自身开始),检查原型链的每一个环节中存在的foo属性。如果找到了foo属性就会将其返回,否则返回undefined值。
直接赋值会咋样?
当直接为对象属性赋值时,原型继承机制就玩不转了。a.foo='bar'会直接赋值给a的foo属性。要想为原型对象的属性赋值,你需要直接定位原型对象的该属性。
关于javascript原型就讲全了。我觉得对于原型概念的理解,我把握的还是比较准确的,但是我的观点无论如何也不是最后的结果。请随便告之我的错误之处或提出和我不一致的观点。