• 行问
    2019-12-16
    实话实说,对 Class 和 super 的知识概念还不熟悉,有几个问题请教下老师

    1、在“继承”上,xxx.apply() 和 xxx.call() 算是继承吗?与 super.xxx() 又有什么区别?

    2、super.xxx() 的 this 引用是在当前环境的上下文中查找的。那么,x = super.xxx.bind(...) 绑定的 this 是从父类 or 祖先类“继承”而来,这与 constructor() 中直接调用 super() 是否一致?

    另,大师可以适当添加一些代码 or 图片 or 思维导图,在阅读理解上可以帮助我们更好理清,感谢!
    展开

    作者回复: 第一个问题,它们与super.xxx没什么关系,它们自己也不算继承。xxx.apply/xxx.call就是普通的函数调用,而super.xxx是先查找super,然后使用当前环境中的this来调用super.xxx()。

    第二个问题,super()与super.xxx()其实很不相同,它们是分别独立实现的。super()相当于get_super(current_constructor).call();而如果是在一般的、非静态声明的方法中,super.xxx()倒是与get_super(current_constructor).prototype.xxx.bind(current_this, ...)有些类似。——注意这两种情况下的current_constructor,是等同于当前正在声明的类的。

    关于图片和思维导图这类,这次极客时间的课程里面,真的没做什么。不过仅是说今天这一讲的话,可以看看之前我讲过的《无类继承》,今天的许多内容都可以看到更详细的介绍。在这里:https://v.qq.com/x/page/d0719xls8eb.html

    或者看搜狐的,还有PPT:
    http://www.sohu.com/a/258358348_609503

    
     2
  • 穿秋裤的男孩
    2020-01-07
    读后感
    1. super的出现是为了解决子类同名属性覆盖父类的属性,通过super可以直接访问到父类。
    2. 伪代码:key[[HomeObject]].__proto__ === super,不知道对不对

    作者回复: 是的。👍

    
    
  • 穿秋裤的男孩
    2020-01-07
    ECMAScript 为此约定:只能在调用了父类构造方法之后,才能使用 super.xxx 的方式来引用父类的属性,或者调用父类的方法,也就是访问 SuperReference 之前必须先调用父类构造方法
    -----------------
    eg:
    class Parent { getName() { return this } };
    class Son extends Parent { getName() { return super.getName() } };
    const s = new Son();
    s.getName(); // 会正常打印Son类。
    -----------------
    疑问:现在的js引擎是不是会自动加一个默认的constructor函数,并且执行super(),不然按照老师的说法,这边在用super之前没有super(),是不能访问super.getName()的吧?
    展开

    作者回复: 是的。确实会“自动加一个”,这在下一讲里面有讲的。^^.

    
    
  • 穿秋裤的男孩
    2020-01-07
    2. 就是一般声明,那么该方法的主对象就是该类所使用的原型,也就是 AClass.prototype。
    -----------------
    eg:
    class Parent {
       getName() { return 'Parent prototype name' }
    };
    class Son extends Parent {
       getName() {
          console.log(super.getName === Son.prototype.__proto__.getName);
          console.log(super.getName === Son.prototype.getName);
       }
    }
    const s = new Son();
    s.getName(); // true; false;
    --------------------
    问:从打印的结果看,如果是普通形式的声明,那么方法内部的super应该是指向Son.prototype.__proto__对象,而不是Son.prototype。
    我感觉这个super就是当前对象的__proto__,即伪代码:obj.__proto__ === obj.super
    展开
    
    
  • 穿秋裤的男孩
    2020-01-07
    1. 在类声明中,如果是类静态声明,也就是使用 static 声明的方法,那么主对象就是这个类,例如 AClass
    -----------------
    前提:我理解您这边的说的主对象就是指super
    -----------------
    例子:
    class Parent { static obj = { name: 'parent static obj name' } }
    class Son extends Parent {
        static obj = { name: 'son static obj name' }
        static getObj() { return super.obj === Parent.obj } // static声明的方法
    }
    Son.getObj(); // true
    --------------------
    问:按照您说的话,static声明的方法,super应该指像Son,那么Son.obj应该是指向自己的static obj,也就不应该出现super.obj === Parent.obj为true的结论。这边是不是应该是:super指向Son的__proto__,也就是Parent本身?
    展开

    作者回复: “主对象就是指super”。——不是的。

    主对象(HomeObject)是每一个静态方法和原型方法(以及原型方法中的构造方法)作为函数对象时的一个内部槽。如果设“方法为f”,那么主对象就是“f.[[HomeObject]]。于是,

    ```
    # 对于静态方法
    > f.[[HomeObject]] === MyClass // true

    # 对于原型方法
    > f.[[HomeObject]] === MyClass.prototype // true

    # 构造方法也是原型方法,即MyClass.prototype.constructor。因此
    > constructor.[[HomeObject]] === MyClass.prototype // true

    # 在类声明中,“构造方法”与“类”是同一个函数
    > MyClass === constructor // true
    > MyClass.[[HomeObject]] === MyClass.prototype // true
    ```

    接下来,super是什么呢?“super是主对象的原型”。因此:
    ```
    # 在原型方法(MyClass.prototype.f)中的super
    > Object.getPrototypeOf(f.[[HomeObject]]) === Object.getPrototypeOf(MyClass.prototype)

    # (亦即是说,在f()中使用super.x)
    > super.x === Object.getPrototypeOf(MyClass.prototype).x // true
    ```

    类似如上的,在静态方法f()中使用super.x
    ```
    # 静态方法MyClass.f中使用super.x
    > Object.getPrototypeOf(f.[[HomeObject]]) === Object.getPrototypeOf(MyClass); // true

    > super.x === Object.getPrototypeOf(MyClass).x // true
    ```

     2
    
  • Elmer
    2020-01-06
    其实很简单,在这种情况下 JavaScript 会从当前调用栈上找到当前函数——也就是 new MyClass() 中的当前构造器,并且返回该构造器的原型作为 super。
    这句话没懂。。
     JavaScript 会从当前调用栈上找到当前函数——也就是 new MyClass() 中的当前构造器, 是MyClass.prototype.constructor? 指向的是MyClass ?
    该构造器的原型?请问这里是怎么指向Object.prototype.constructor的。

    作者回复: 如下示例:
    ```
    > class MyObject {}
    > class MyObjectEx extends MyObject {}

    > MyObjectEx.prototype.constructor === MyObjectEx; // true
    > Object.getPrototypeOf(MyObjectEx) === MyObject; // true
    ```

    
    
  • 海绵薇薇
    2019-12-29
    Hello 老师好:

    感悟:

    constructor() {
        super()
    }
    为什么不能写成

    constructor() {
        super.constructor()
    }
    这种形式呢?之后过了好一会转念一想super.constructor是需要this的但是上面super调用之前是拿不到this的。

    问题1:

    a.b()方法中this是动态获取的,是作为this传入的a。
    super.b 中的this是super.b这个引用的thisValue(执行环境的this),引用的thisValue优先级高于作为this传入的super。
    通过测试发现bind绑定的this优先级高于super.a这个引用的thisValue。

    但是bind,call,apply这些方法绑定的this是存在哪里的呢?bind的mixin底层也是调用的apply。
    展开

    作者回复: ^^.

    感悟中这种转念一想特别好。有恍然大悟的感觉,然后记得特别清楚,而且很多以前不理解的东西一下子就顺了,问题就像自然而然地全都解决了一般。

    关于bind。它是单独创建了一个新的对象,用一个内部槽来放着绑定的this。类似于:
    ```
    // x = func.bind(thisValue, ...args)
    x.[[BoundTargetFunction]] = func
    x.[[BoundArguments]] = args
    x.[[BoundThis]] = thisValue
    ...
    ```
    参见:https://www.ecma-international.org/ecma-262/9.0/#sec-boundfunctioncreate

    至于call/apply,倒不麻烦,因为内部处理一个函数调用的时候,是将thisValue和args分别传入的(也就是隐式地传入一个参数),所以在调用时直接替换一下就好了,不需要暂存,也不通过引用来传递。

    
    
  • 小童
    2019-12-27
    https://github.com/aimingoo/prepack-core 我克隆下来了 我不理解在如何引擎级别打断点? 不知道怎么搞 看到一个scripts文件夹里面有 test262-runner.js文件 然后运行了脚本没运行成功。

    作者回复: 看README.md,先把repl运行起来(这是运行在node环境中的),是一个类似node的控制台,可以在上面写代码试着玩。

    如果你要调试,那么就让node运行在调试模式下。这个你查node的使用手册,会有命令行和端口,然后可以在chrome里打开指定的地址上的调试器环境。——这种情况下,由于nodejs把prepack-core运行在一个调试环境中,所以你就可以用断点跟进去看prepack-core里的代码如何运行了。这个时候的操作环境在chrome里面。

    你可以直接在chrome集成的调试器里面给nodejs里面的prepack-core项目打断点,也可以写debugger这个语句来在代码中触发。

    总之,你先弄明白如何用chrome/firefox来调用nodejs中的项目吧……这个网上有各种教程的。然后,你就可以调试prepack-core了。

    
    
  • 小童
    2019-12-27
    我在浏览器中输出的方法中没有看到 [[HomeObject]]插槽,老师这个能在哪里找到吗?

    作者回复: 绝大多数内部槽在浏览器控制台上都看不到。
    好象也没有别的办法看得到。

    如果你有兴趣,可以自己run一个引擎,然后在引擎级别打调试断点来看。我很推荐你试试这个:
    > https://github.com/aimingoo/prepack-core

    由于它的源代码也是.js的,很易读。并且是按ECMAScript逐行写的,所以对于理解ECMAScript很有帮忙。

    
    
  • 晓小东
    2019-12-25
    问题1: 如果super.xxx.bind(obj)() xxx执行上下文的thisValue域 将会被改变为obj, 而调用super.yyy()则按正常处理逻辑进行。(我这里只是测试了下执行效果, 老师可否用引擎执行层面的术语帮我们解答一下)
    问题2: super 是静态绑定的,也就是说super引用跟我们书写代码位置有关。super引用的thisValue是动态运行,是从执行环境抄写过来的,所以当我调用一个从别处声明的方法是,其super代表其他地方声明对象的父类。(不知道这样表述的正不正确)代码如下:
        let test = {
            a() {
                this.name = super.name;
            }
        }
        Object.setPrototypeOf(test, {name: 'test proto'})

        class B extends A {
            constructor() {
                super();
                this.a = test.a;
                this.a();
                console.log(this.name); // test proto
            }
        }

    问题3: super.xxx 如果是属性,也就代表对属性的进行存取两个操作 当super.xxx 进行取值时 super 所代表的的是父类的对象,super.xxx(规范引用:父类的属性引用)所以可以正常取到父类的属性值(值类型数据或引用类型据)
    但如果向super.xxx置值时,此时会发生在当前this身上(而不是父类对象身上)。 分析:应当是当super.xxxx当做左操作数时(规范引用),会从其引用thisValue取值,属性存取发生在了thisValue所在引用类型的数据数据身上,而该引用正是当前被抄写过来的this
    总结: super.xxx 取值时拿到的是父类的属性值
    super.xxx 置值时会作用在当前this, 也就是实例身上
    这样可以防止父类对象在子类实例化中被篡改

    有个问题老师,对于规范引用是否都有一个thisValue域, 是只有SuperReference有吗, 其作用是什么?
    展开

    作者回复: 问题1>
    super.xxx()中,左侧的super.xxx是一个“引用(规范类型)”,而thisValue是在调用中被绑定到这个引用上的,它相当于(super.xxx)()。接下来,如果是super.xxx.bind(obj)(),这时它相当于(x=super.xxx.bind(obj))(),而这个obj是绑定在x这个BindingFunction上的,在JavaScript中,BindingFunction有一个私有槽来存放这个obj。所以最后当作一个普通函数来执行x()的时候,才会有一个obj可以取出来并作为this。


    问题2>
    不是。
    super所依赖的HomeObject是静态绑定的。所以:
    ```
    obj = {
      foo() {
        super.xxx
      }
    }
    ````
    这段代码中,obj.foo.[[HomeObject]]是静态绑定的,恒为即HomeObject === obj。但是,`super`是依赖这个[[HomeObject]]动态计算的,super === Object.getPrototypeOf(HomeObject)。
    所以,它也就等同于Object.getPrototypeOf(obj)。但你知道,obj的原型可以重置,所以:
    ```
    Object.setPrototypeOf(obj, x = new Object);
    super === Object.getPrototypeOf(HomeObject); // 这个结果也就变掉了
    ```
    然而thisValue不是这样处理的,thisValue总是取当前执行时的上下文中的this。所以:
    ```
    f = obj.foo;
    obj2 = { f };
    obj2.f() // <-- 现在foo()里面拿到的thisValue将是obj2
    obj.foo() // <-- 现在foo()里面拿到的thisValue将是obj
    ```

    问题3>
    你的理解是对的。并且也确实应该这样。但是在ECMAScript中,super.xxx的取值实现得是“正确的”,逻辑和语义上都没问题;而super.xxx的置值是实现得不正确的,是个BUG设计,所以我才说“super是一个半吊子设计”。它根本上就是没实现完整,因此关于super.xxx,我绕过了它作为“super属性”的部分,一直尽量避开没有讲,而只是讲了“super.xxx()方法”的部分。

    ECMAScript在这上面是个坑,并且一直没修。我接受他们没有修这个地方的原因,但结果很不爽。^^.

    原因是在class中声明属性成员,以及声明属性的可见性等问题还没有被设计出来。而现在的TC39又因为这个东西设计不出来,搞了一个class fields来替代它,真的很脑残!
    (吐槽完了)

    问题4>
    只有SuperReference有。作用就是暂存一下当前上下文中的this。——因为从语法形式上来说,super.xxx()调用中xxx()方法应该得到的this是super,而实际上不是,所以需要新加一个域来暂存。

    再换而言之,在obj.foo()中,JavaScript引擎其实是帮你隐式地传了一个参数this进去对吧?那么在super.xxx()中,它需要隐式的传两个参数,就是super和thisValue,那么就需要多添加一个域。仅此而已。

     1
    
  • ttttt
    2019-12-18
    老师能不能详细贴一下每讲对应的 ecma 规范地址呢?对照着看会更好

    作者回复: 这个倒是挺难的,因为每一节讲的内容都会涉及很多个ECMAScript的点,要是指着看,反倒不见得清楚,没有上下文还可能会更乱。而且主要是多,整理起来着实不易。

    
    
  • 许童童
    2019-12-16
    老师讲得很好,我要再消化消化
    
    
我们在线,来聊聊吧