[译]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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值