javascript
《你不知道的JavaScript》整理(四)——原型
一、[[Prototype]]
JavaScript中的對象有一個特殊的[[Prototype]]內置屬性,其實就是對于其他對象的引用。
var myObject = {a: 2 }; myObject.a; // 2當你試圖引用對象的屬性時會觸發原型[[Get]]操作,比如myObject.a。
1. 第一步是檢查對象本身是否有這個屬性,如果有的話就使用它。
2. 如果a不在myObject中,就需要使用對象的[[Prototype]]鏈了。
?
1)Object.prototype
普通的[[Prototype]]鏈最終都會指向內置的Object.prototype。
?
2)屬性設置和屏蔽
myObject.foo = "bar";如 果 屬 性 名foo既 出 現 在myObject中 也 出 現 在myObject的[[Prototype]]鏈 上 層, 那么就會發生屏蔽。
myObject中包含的foo屬性會屏蔽原型鏈上層的所有foo屬性,因為myObject.foo總是會選擇原型鏈中最底層的foo屬性。
有些情況下會隱式產生屏蔽:
var anotherObject = {a: 2 };var myObject = Object.create(anotherObject);anotherObject.a; // 2 myObject.a; // 2 anotherObject.hasOwnProperty("a"); // true myObject.hasOwnProperty("a"); // false myObject.a++; // 隱式屏蔽! anotherObject.a; // 2 myObject.a; // 3 myObject.hasOwnProperty("a"); // true++操作相當于myObject.a = myObject.a + 1。
因此++操作首先會通過[[Prototype]]查找屬性a并從anotherObject.a獲取當前屬性值2,然后給這個值加1,接著用[[Put]]將值3賦給myObject中新建的屏蔽屬性a。
?
二、“類”
JavaScript中只有對象。
在JavaScript中,類無法描述對象的行,(因為根本就不存在類!)對象直接定義自己的行為。
1)“類”函數
function Foo() {// ... } var a = new Foo(); console.log(Object.getPrototypeOf(a) === Foo.prototype); // truea這個對象是在調用new Foo()時創建的,最后會被關聯到這個“Foo.prototype”對象上。
在JavaScript中不能創建一個類的多個實例,只能創建多個對象,它們[[Prototype]]關聯的是同一個對象。
從視覺角度來說,[[Prototype]]機制如下圖所示,箭頭從右到左,從下到上:
這個機制通常被稱為原型繼承,它常常被視為動態語言版本的類繼承。
?
2)“構造函數”
function Foo() {// ... } var a = new Foo();之所以認為Foo是一個“類”:
1. 其中一個原因是我們看到了關鍵字new,在面向類的語言中構造類實例時也會用到它。
2. 另一個原因是,看起來我們執行了類的構造函數方法,Foo()的調用方式很像初始化類時類構造函數的調用方式。
在JavaScript中對于“構造函數”最準確的解釋是,所有帶new的函數調用。
函數不是構造函數,但是當且僅當使用new時,函數調用會變成“構造函數調用”。
?
三、(原型)繼承
function Foo(name) {this.name = name; } Foo.prototype.myName = function() {return this.name; };function Bar(name, label) {Foo.call(this, name);this.label = label; } // 我們創建了一個新的 Bar.prototype 對象并關聯到 Foo.prototype Bar.prototype = Object.create(Foo.prototype); // 注意!現在沒有 Bar.prototype.constructor 了 // 如果你需要這個屬性的話可能需要手動修復一下它 Bar.prototype.myLabel = function() {return this.label; };var a = new Bar("a", "obj a"); a.myName(); // "a" a.myLabel(); // "obj a"原型繼承的機制,是指a可以“繼承”Foo.prototype并訪問Foo.prototype的myName()函數。
面這兩種方式是常見的錯誤做法:
// 和你想要的機制不一樣! Bar.prototype = Foo.prototype;// 基本上滿足你的需求,但是可能會產生一些副作用 :( Bar.prototype = new Foo();1.?第一種只是讓Bar.prototype直接引用Foo.prototype對象。因此當你執行類似Bar.prototype.myLabel = ...的賦值語句時會直接修改Foo.prototype對象本身。
2.?第二種的確會創建一個關聯到Bar.prototype的新對象。但是它使用了Foo(..)的“構造函數調用”,如果函數Foo有一些副作用(比如寫日志、修改狀態、注冊到其他對象、給this添加數據屬性,等等)的話,就會影響到Bar()的“后代”。
兩種正確的把Bar.prototype關聯到Foo.prototype的方法:
// ES6 之前需要拋棄默認的 Bar.prototype Bar.ptototype = Object.create( Foo.prototype );// ES6 開始可以直接修改現有的 Bar.prototype Object.setPrototypeOf( Bar.prototype, Foo.prototype );?
1)檢查“類”關系
在傳統的面向類環境中,檢查一個實例(JavaScript中的對象)的繼承祖先(JavaScript中的委托關聯)通常被稱為內省(或者反射)。
function Foo() { // ... } Foo.prototype.blah = ...; var a = new Foo();如何通過內省找出a的“祖先”(委托關聯)呢?
1.?第一種站在“類”的角度來判斷:
a instanceof Foo; // trueinstanceof回答的問題是:在a的整條[[Prototype]]鏈中是否有指向Foo.prototype的對象?
這個方法只能處理對象(a)和函數(帶.prototype引用的Foo)之間的關系。
?
2.?第二種判斷[[Prototype]]反射的方法,它更加簡潔:
Foo.prototype.isPrototypeOf( a ); // trueisPrototypeOf回答的問題是:在a的整條[[Prototype]]鏈中是否出現過Foo.prototype?
同樣的問題,同樣的答案,但是在第二種方法中并不需要間接引用函數(Foo),它的.prototype屬性會被自動訪問。
我們只需要兩個對象就可以判斷它們之間的關系。舉例來說:
// 非常簡單:b 是否出現在 c 的 [[Prototype]] 鏈中? b.isPrototypeOf( c );?
2)獲取一個對象的[[Prototype]]鏈
1. 在ES5中,標準的方法是:
Object.getPrototypeOf( a );console.log(Object.getPrototypeOf( a ) === Foo.prototype); // true2. 瀏覽器也支持一種非標準的方法來訪問內部[[Prototype]]屬性:
a.__proto__ === Foo.prototype; // true.__proto__的實現大致上是這樣的:
Object.defineProperty(Object.prototype, "__proto__", {get: function() {return Object.getPrototypeOf(this);},set: function(o) {// ES6 中的 setPrototypeOf(..)Object.setPrototypeOf(this, o);return o;} });訪問(獲取值)a.__proto__時,實際上是調用了a.__proto__()(調用getter函數)。
雖然getter函數存在于Object.prototype對象中,但是它的this指向對象a,所以和Object.getPrototypeOf( a )結果相同。
?
四、對象關聯
[[Prototype]]機制就是存在于對象中的一個內部鏈接,它會引用其他對象。
這個鏈接的作用是:如果在對象上沒有找到需要的屬性或者方法引用,引擎就會繼續在[[Prototype]]關聯的對象上進行查找。
這一系列對象的鏈接被稱為“原型鏈”。
var foo = {something: function() {console.log("Tell me something good...");} }; var bar = Object.create(foo); bar.something(); // Tell me something good...我們并不需要類來創建兩個對象之間的關系,只需要通過委托來關聯對象就足夠了。
Object.create()的polyfill代碼:
Object.create = function(o) {function F() {}F.prototype = o;return new F(); };使用了一個一次性函數F,我們通過改寫它的.prototype屬性使其指向想要關聯的對象,然后再使用new F()來構造一個新對象進行關聯。
?
總結
以上是生活随笔為你收集整理的《你不知道的JavaScript》整理(四)——原型的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: shell测试笔试
- 下一篇: 一些常用的图像数据库