关于new和箭头函数

吐槽君 分类:javascript

0 前情提要

最近面试遇到了如下一个问题:

    // 问:下面的console.log输出什么?
    var arrowFunction = () => {};
    console.log(new arrowFunction());
 

当时以为箭头函数就是this指向声明作用域的特殊函数,所以根据函数实体没返回具体值就返回空对象的想法答了个{},结果并不对,所以本着求知求索的想法这两天来深入研究下这个问题。

那么这就引申出很多问题:

  1. 为什么new箭头函数会报错?

  2. new的实际执行逻辑是什么?

  3. 箭头函数和普通函数是什么关系?

  4. 如何解释箭头函数的call方法?

  5. polyfill中是怎么实现箭头函数的?

1 旧认知与实践

这个问题看起来是个简单地箭头函数+new关键字实例化,当时答题的时候没深究过箭头函数和new的内部实现,只是了解箭头函数没有默认绑定this和arguments等变量,其他的跟普通函数差不多,所以套用了以前学习的常规函数实例化步骤:
以下面代码为例

    var functionConstructor = function() {
        this.a = 'normal word';
    };
    new functionConstructor('prop word);
 
1.创建空对象tar = {};
2.tar.__proty__ = functionConstructor.prototype
3.创建res = functionConstructor.call(tar, arguments)
4.如果res是对象且不是null,则返回res,否则返回tar

那带到箭头函数理应得到的是tar,即{},然而实际执行一下会发现并不是这样:
 

image.png

居然报错了(怪不得面试官之后问了一堆函数相关的问题)。
 

wdnmd.jpg

2 追本溯源

既然认知跟不上了,与其像以前一样伸手学习,不如我们追本溯源,从ES规范入手解决这个问题吧。

ps: ECMAScript@2020 Language Specification

ps2: 规范规定的内容很多,本文只阐述与问题相关的部分。

2.1 ES规范 —— 箭头函数与普通函数

我们先来看下箭头函数的运行时求值(14.2.17):

image.png

下边是普通函数的运行时求值(14.1.24):

image.png

对比下发现主要差异在于:

  1. 两个方法调用·OrinaryFunctionCreate·的第四个参数ThisMode不同,lexical-this最终会让运行环境不绑定this(及其内部检测等)或创建arguments指向。

  2. 普通函数在第3步会调用·MakeConstructor·方法,而箭头函数不会。该方法会向函数注入Consturct属性(重要)。

箭头函数的官方提示:

image.png

简译:箭头函数不会默认绑定arguments, super, this 或者 new.target(在new时指向函数自身,箭头函数会在声明时提示new.target在该语境内不合法),但super可能会存在绑定。

ps:这里的只能先挖个坑了,我要去了解环境声明的那套规范才能读明白这块。

2.2 Bonus —— 箭头函数与call

不过看规范的过程中我也解决了箭头函数.call方法到底干了啥,上文提到箭头函数会修改ThisMode = lexical,那么按照call的规范来看,其实call调用链中并不会修改原本的this,因此调用call和直接调用最终是一样的。

image.png

e.g:

    function someClass () {
        this.variable = 'inside variable';
        return () => {console.log(this.variable)};
    }

    someClass.call({})(); // inside variable
    someClass.call({}).call({variable: 'outside variable'}); // inside variable
 

2.3 ES规范 —— new关键字

其实看完函数那边,new关键字的逻辑就显得极其简单了。

执行new逻辑(12.3.5.1.1):

image.png

在校验和参数准备结束后,来到第7步时,会用IsConstructor(7.2.4)检查这个拿到的constructor是不是构造器,不是就抛出TypeError异常。

其检测方法就是这个值是不是对象,且包不包含Construct内部方法:

image.png

我们在之前对比时知道了箭头函数在执行过程中并没有运行MakeConstructor,也就没有Construct内部方法,因此按照标准IsConstructor直接返回false,再由new关键字的执行时抛出TypeError异常。

不过在Chrome运行时的运行结果会稍显不同:

image.png

ps:猜测可能是Chrome在IsConstructor方法中就抛出了错误。

2.4 Bonus —— 其他new会抛出异常的方法

[
    async function() {},
    Function.prototype,
    
    // 没有Construct的全局对象:Atomics、JSON、Math、Reflect
    Math,
    JSON,
    Reflect,
    Atomics,
    
    // 代理了没有Construct属性的对象的Proxy
].map(e => {
    try{ 
        new e();
    } catch(err) { 
        console.error('can`t use new with:', e, 'error msg:', err);
    }
})
 

3 Polyfill实现

babel => ie 10

image.png

4 小结

可能这就是温故而知新吧(误),但能学到新的东西就是好的,越挖掘越觉得自己只是略懂皮毛;同时也给自己开了个关于执行环境的新坑,这块尝试了一段时间,发现要综合很多块的内容一起看(前车之鉴),慢慢钻研,总之干就完了。

注:本文是我的第一篇文章,可能会有bug,如需对内容进行补充或勘误,可留言或私信,我会定期check并修补。

最后感谢各位的阅读和讨论。

回复

我来回复
  • 暂无回复内容