资讯专栏INFORMATION COLUMN

__proto__ 属性与 ES6 classes 的继承

newsning / 1803人阅读

摘要:即是由此我们可以轻松伪造一个实例对象可是这是对对象的属性的修改,和有什么关系静态方法的继承少年,可别忘了函数本身也是个对象哟在上面的代码中,我们使无关对象的指向构造函数的,于是使被判定为的实例。

关于 __proto__ 属性,MDN 上的解释是这样的[1]

The __proto__ property of Object.prototype is an accessor property (a getter function and a setter function) that exposes the internal [[Prototype]] (either an object or null) of the object through which it is accessed.

即是说,__proto__ 属性指向了实例对象的原型 Constructor.prototype。那么,这个属性里隐藏着怎样的黑魔法呢?

ES6 class 的实现

最近看 ECMAScript 6 的 spec,发现了一些有意思的东西,比如 class 章节:

14.5.14 Runtime Semantics: ClassDefinitionEvaluation[2.1]

With parameter className.
ClassTail : ClassHeritageopt { ClassBodyopt }
...

6.g (for class heritage)

If superclass has a [[FunctionKind]] internal slot whose value is "generator", throw a TypeError exception.

Let protoParent be Get(superclass, "prototype").

ReturnIfAbrupt(protoParent).

If Type(protoParent) is neither Object nor Null, throw a TypeError exception.

Let constructorParent be superclass.

7. Let proto be ObjectCreate(protoParent).

...

12. Let constructorInfo be the result of performing DefineMethod for constructor with arguments proto and constructorParent as the optional functionPrototype argument.

...

16. Perform MakeConstructor(F, false, proto).

...

18. Perform CreateMethodProperty(proto, "constructor", F).

...

这几行规定了类继承(class SubClass extends SuperClass {})的行为,除了众所周知的 SubClass.prototype = Object.create(SuperClass.prototype) 以外,还做了一件有趣的事:Let constructorParent be superclass, proto be ObjectCreate(protoParent), and performing DefineMethod for constructor with arguments proto and constructorParent as the optional functionPrototype argument.

追溯 functionPrototype 变量的去向,发现是这样的:

14.3.8 Runtime Semantics: DefineMethod[2.2]

With parameters object and optional parameter functionPrototype.
...

6. Let closure be FunctionCreate(kind, StrictFormalParameters, FunctionBody, scope, strict). If functionPrototype was passed as a parameter then pass its value as the functionPrototype optional argument of FunctionCreate.

...

9.2.5 FunctionCreate (kind, ParameterList, Body, Scope, Strict, prototype)[2.3]

The abstract operation FunctionCreate requires the arguments: kind which is one of (Normal, Method, Arrow), a parameter list production specified by ParameterList, a body production specified by Body, a Lexical Environment specified by Scope, a Boolean flag Strict, and optionally, an object prototype.
...

4. Let F be FunctionAllocate(prototype, Strict, allocKind).

...

9.2.3 FunctionAllocate (functionPrototype, strict [,functionKind] )[2.4]

...

12. Set the [[Prototype]] internal slot of F to functionPrototype.

...

原来 functionPrototype 被用作了 SubClass 的 [[Prototype]] 属性

Babel[2] 对继承的实现如下:

function _inherits(subClass, superClass) {
    if (typeof superClass !** "function" && superClass !** null) {
        throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
    }
    subClass.prototype = Object.create(superClass && superClass.prototype, {
        constructor: {
            value: subClass,
            enumerable: false,
            writable: true,
            configurable: true
        }
    });
    if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
}

道理我都懂,可是为什么要这样做?

[[prototype]] 与原型链

要检测一个对象是否是一个构造函数的实例,我们通常会用 O instanceof C 这样的表达式,在 spec 中,instanceof 运算符这样被定义:

12.9.4 Runtime Semantics: InstanceofOperator(O, C)[2.5]

...

2. Let instOfHandler be GetMethod(C,@@hasInstance).

...

19.2.3.6 Function.prototype[@@hasInstance] ( V )[2.6]


Let F be the this value.

Return OrdinaryHasInstance(F, V).

7.3.19 OrdinaryHasInstance (C, O)[2.6]

...

4. Let P be Get(C, "prototype").

...

7. Repeat

Let O be O.[[GetPrototypeOf]]().

ReturnIfAbrupt(O).

If O is null, return false.

If SameValue(P, O) is true, return true.

9.1.1 [[GetPrototypeOf]] ( )[2.6]

Return the value of the [[Prototype]] internal slot of O.

大致描述如下:instanceof 运算符掉用了 Function.prototype 上的内部方法 @@hasInstance,此方法将 this 对象(即 C)的 prototype 属性与实例对象 O 的 [[prototype]] 对比,如果后者 [[prototype]]null 则返回 false,如果两者相等,则返回 true,否则沿原型链向上比较,直到得出结果。

即是:

O instanceof C =>
  O.__proto__ === C.prototype ? true:
    O.__proto__.__proto__ === C.prototype ? true :
        ...

由此我们可以轻松伪造一个实例对象:

class A {
    whoami() {
        return "Instance of A";
    }
}

let a = new A();

let b = {};
Object.setPrototypeOf(b, A.prototype); // b.__proto__ = A.prototype

a.whoami() =** b.whoami(); // true
b instanceof A; // true

可是这是对对象的 __proto__ 属性的修改,和 SubClass.__proto__ 有什么关系?

静态方法的继承

少年,可别忘了 JavaScript 函数本身也是个对象哟!

在上面的代码中,我们使无关对象 b__proto__ 指向构造函数 Aprototype,于是使 b 被判定为 A 的实例。同时,A 的所有原型方法都被 b 所继承!

换句话说,如果将 SubClass__proto__ 属性指向 SuperClass,父类上的所有属性都将被子类继承!比如:

class A {
    static whoami() {
        return "A Constructor!";
    }

    greet() {
        return "hello world!";
    }
}

function B() {}
Object.setPrototypeOf(B, A);

B.whoami(); // "A Constructor!"

此时,我们再将 B.prototype__proto__ 属性指向 A.prototype,即可完成原型方法的继承:

Object.setPrototypeOf(B.prototype, A.prototype);

let b = new B();
b.greet(); // "hello world!"

b instanceof B; // true
b instanceof A; // true

如此一来,子类就构造完成了!可以开开心心造孩子去了!

恶搞:让函数 B 成为函数 A 的实例

利用 instanceof 运算符的定义,我们还能玩出一些神奇的事,比如:

function A() {};
A.prototype = A;

function B() {};
Object.setPrototypeOf(B, A);

B instanceof A; // true!

(全文完)

参考资料

Object.prototype.__proto__ - JavaScript | MDN

ECMAScript 2015 Language Specification

Babel · The compiler for writing next generation JavaScript

重编自我的博客,原文地址:https://idiotwu.me/proto-property-and-es6-classes-inheritance/

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/86037.html

相关文章

  • 再和“面向对象”谈恋爱 - 继承(五)

    摘要:面向对象里最大的特点应该就属继承了。在第二篇文章里说过原型实例跟构造函数之间的继承,并且还讲了一道推算题。 通过上一篇文章想必各位老铁已经熟悉了class了,这篇文章接着介绍继承。面向对象里最大的特点应该就属继承了。一个项目可能需要不断的迭代、完善、升级。那每一次的更新你是要重新写呢,还是在原有的基础上改吧改吧呢?当然,不是缺心眼的人肯定都会在原来的基础上改吧改吧,那这个改吧改吧就需要...

    Airmusic 评论0 收藏0
  • ES6 中Class创建对象继承实现

    摘要:使用类创建实例对象也是直接对类使用命令,跟中构造函数的用法一致。中没有构造函数,作为构造函数的语法糖,同时有属性和属性,因此同时存在两条继承链。子类的属性,表示构造函数的继承,总是指向父类。 1 Class in ES6 ES6提出了类(Class)的概念,让对象的原型的写法更像面向对象语言写法。 ES6中通过class定义对象,默认具有constructor方法和自定义方法,但是包含...

    zhou_you 评论0 收藏0
  • ES6 中Class创建对象继承实现

    摘要:使用类创建实例对象也是直接对类使用命令,跟中构造函数的用法一致。中没有构造函数,作为构造函数的语法糖,同时有属性和属性,因此同时存在两条继承链。子类的属性,表示构造函数的继承,总是指向父类。 1 Class in ES6 ES6提出了类(Class)的概念,让对象的原型的写法更像面向对象语言写法。 ES6中通过class定义对象,默认具有constructor方法和自定义方法,但是包含...

    wind5o 评论0 收藏0
  • oop

    摘要:也就是说,的构造函数,对应的类的构造方法。上面代码表明,类的数据类型就是函数,类本身就指向构造函数。使用的时候,也是直接对类使用命令,跟构造函数的用法完全一致。 OOP 标签(空格分隔): 未分类 ES5 构造函数(constructor),其实就是一个普通函数,但是内部使用了this变量,对构造函数使用new运算符,就能生成实例,并且this变量会绑定在实例对象上。 var cat...

    Gu_Yan 评论0 收藏0
  • ES6 class继承super关键词深入探索

    摘要:请看对应版本干了什么可知,相当于以前在构造函数里的行为。这种写法会与上文中写法有何区别我们在环境下运行一下,看看这两种构造函数的有何区别打印结果打印结果结合上文中关于原型的论述,仔细品味这两者的差别,最好手动尝试一下。 ES6 class 在ES6版本之前,JavaScript语言并没有传统面向对象语言的class写法,ES6发布之后,Babel迅速跟进,广大开发者也很快喜欢上ES6带...

    jubincn 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<