[譯]JavaScript: __proto__


原文:http://www.2ality.com/2012/10/proto.html


本文講一下特殊屬性__proto__,通過該屬性可以獲取或設置一個對象的原型.想要理解這篇文章,你必須已經熟悉了JavaScript的原型繼承 [1].

1.特殊屬性__proto__

ECMAScript標准中規定,一個對象的內部屬性[ [Prototype]]指向自己的原型.在ECMAScript 5中,該屬性是不能被直接讀取或修改的,但是可以通過 Object.getPrototypeOf()間接的讀取到它.還可以使用 Object.create()創建一個擁有指定原型的新對象.例如,下面的代碼創建了一個對象o bj,它的原型是 myProto
> var myProto = {};
> var obj = Object.create(myProto);

> Object.getPrototypeOf(obj) === myProto
true

__proto__ (發音為“dunder proto”,dunder是“double underscore”的簡拼)最初出現在Firefox中,作為內部屬性[[Prototype]]的別名.使用__proto__的話,上面的代碼可以改寫成:

> var myProto = {};
> var obj = { __proto__: myProto };

> obj.__proto__ === myProto
true
__proto__屬性的值就是它的原型:
> obj.__proto__ === Object.getPrototypeOf(obj)
true
自從 __proto__出現在Firefox中以后,它就變的越來越流行,現在V8 (Chrome, Node.js)和Nitro (Safari)也已經支持了它.但ECMAScript 5並沒有標准化 __proto__,不過由於它現在的流行程度,它將會成為ECMAScript 6規范的一部分.

1.1 檢測是否支持__proto__

如果JavaScript引擎實現了 __proto__ ,那么下面的表達式會返回true:
Object.getPrototypeOf({ __proto__: null }) === null

1.2 對象作為Map使用

如果你需要把一個對象作為一個字符串到值的Map來使用的話(真正的Map可以有任意類型的鍵) [2],那么你必須做個判斷,如果傳入的鍵名是 "__proto__",則將它轉義為一個不和已有的屬性'__proto__'沖突的鍵名 [2].例如:
function escapeKey(key) {
    // 我們需要轉義"__proto__"成為"__proto__%",還得轉義"__proto__%"成為"__proto__%%",依次類推.
if (key.indexOf("__proto__") === 0) { return key+"%"; } else { return key; } }

2.兩個用處

__proto__兩個最大的用處是:創建一個以指定對象為原型的對象,以及,為內置類型添加子類型.

2.1 創建一個以指定對象為原型的對象

如果你要創建一個非空的對象,則使用 __proto__的優點會比較明顯:
var obj = {
    __proto__: myProto,
    foo: 123,
    bar: "abc"
};
使用 Object.create()來替代的話,你有兩種寫法,但都不是很簡潔:
// 替代方法1:創建一個空對象,然后賦值
var obj = Object.create(myProto);
obj.foo = 123;
obj.bar = "abc";

// 替代方法2:屬性描述符
var obj = Object.create(myProto, {
    foo: {
        value: 123,
        writable: true,
        enumerable: true,
        configurable: true
    },
    bar: {
        value: "abc",
        writable: true,
        enumerable: true,
        configurable: true
    }
});

2.2 為內置類型添加子類型

總所周知,在JavaScript中很難為內置類型添加子類型 [3],這有兩個原因:第一個是,JavaScript中,標准的子類型化模式是,在子類的構造函數中,將子類型的實例this通過call方法傳入到超類型的構造函數中,這樣子類型的實例就能擁有超類型的屬性.但是,大部分內置類型的構造函數都不會允許你那樣做,它們會忽略掉傳入的this.第二個原因是,一些內置類型的對象實例是很特殊的,即使你能成功的把子類型的對象實例傳進超類型的構造函數中去,你也得不到按預期工作的對象實例.最常見的例子就是數組, 如果數組中新添加了一個元素,它的length屬性會自動更新,這個特性子類型無法繼承下來.
譯者注:為自定義類型添加子類型的通用方法是:
function Super(x, y) {
this.x = x;
this.y = y;
}
function Sub(x, y, z) {
Super.call(this, x, y); //為子類型的對象實例添加超類型的屬性,子類型的對象實例作為超類型的構造函數中的this值 this.z = z; //添加子類型的擴展屬性
}

但如果是內置類型的話,就不行了.

借助__proto__的話,可以這樣實現數組的子類型:

var MyArrayProto = Object.create(Array.prototype);
//還可以寫成var MyArrayProto = {__proto__:Array.prototype};
MyArrayProto.foo = function (...) { ... };
function createMyArray() {
    var arr = Array.prototype.slice.call(arguments);
    arr.__proto__ = MyArrayProto;
    return arr;   
}
var myarr = createMyArray(1,2,3);    //myarr會有foo方法,也會有其他的數組方法

3.ECMAScript 6

正如前面所提到的,ECMAScript 6將會添加對 __proto__的支持.目前可以確定的功能有,你可以用它來獲取到一個對象的原型,還可以用在對象字面量中設置這個對象的原型.對象字面量配合 __proto__可以視為是一種更友好的 Object.create()的替代者,對於大多數情況來說,足夠好用了.

目前還不確定的功能是,能不能通過__proto__修改一個已存在對象的原型.目前最主要需要用到這個特性的地方就是給數組新增子類型,不過未來也許會有更好的方法來實現這個需求,比如通過函數Array.createArray(proto).

ECMAScript 6還可能會提供一種手段來關閉一些對象上的__proto__屬性的訪問,甚至可能是全部對象.

4.更多資料

5.參考

  1. Prototypes as classes – an introduction to JavaScript inheritance
  2. The pitfalls of using objects as maps in JavaScript
  3. Subtyping JavaScript built-ins


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM