JavaScript 核心原理解析
周爱民
《JavaScript 语言精髓与编程实践》作者,南潮科技(Ruff)首席架构师
32699 人已学习
新⼈⾸单¥59
登录后,你可以任选3讲全文学习
课程目录
已完结/共 28 讲
开篇词 (1讲)
JavaScript 核心原理解析
15
15
1.0x
00:00/00:00
登录|注册

08 | x => x:函数式语言的核心抽象:函数与表达式的同一性

以表达式实现箭头函数的能力
表达式与函数的异同
表达式与计算过程的等价性
闭包的特性
参数的初始化方法
函数界面的值操作
传入参数的过程
匿名函数的意义
箭头函数的特性
函数的本质
初始器的赋值
let 变量的特性
无初值的绑定
求值过程
绑定实际传入的参数
非惰性求值
参数的绑定关系解除
非简单参数的特殊模式
静态视角下的函数声明
闭包的运行环境
执行体
参数
运行期上下文
闭包的创建与实例化
静态视角 vs. 动态视角
函数作为数据
思考题
知识回顾
最小化的函数式语言示例
意外
传入参数的处理
简单参数类型
两个语义组件
函数的执行过程
一个简单参数类型的箭头函数下,究竟藏着什么?

该思维导图由 AI 生成,仅供参考

你好,我是周爱民。
在运行期,语句执行和特殊的可执行结构都不是 JavaScript 的主角,多数情况下,它们都只充当过渡角色而不为开发人员所知。我相信,你在 JavaScript 中最熟悉的执行体一定是全局代码,以及函数
而今天,我要为你解析的就是函数的执行过程
如同在之前分析语句执行的时候与你谈到过的,语句执行是命令式范型的体现,而函数执行代表了 JavaScript 中对函数式范型的理解。厘清这样的基础概念,对于今天要讨论的内容来说,是非常重要和值得的。
很多人会从对象的角度来理解 JavaScript 中的函数,认为“函数就是具有[[Call]]私有槽的对象”。这并没有错,但是这却是从静态视角来观察函数的结果。
要知道函数是执行结构,那么执行过程发生了什么呢?这个问题从对象的视角是既观察不到,也得不到答案的。并且,事实上如果上面这个问题问得稍稍深入一点,例如“对象的方法是怎么执行的呢”,那么就必须要回到“函数的视角”,或者运行期的、动态的角度来解释这一切了。

函数的一体两面

用静态的视角来看函数,它就是一个函数对象(函数的实例)。如果不考虑它作为对象的那些特性,那么函数也无非就是“用三个语义组件构成的实体”。这三个语义组件是指:
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

JavaScript箭头函数的执行机制和特性是本文的重点内容。作者深入探讨了函数的参数传入过程以及闭包的重要性,强调闭包在创建时会实例化参数和执行体,确保每个闭包都有独立的运行环境。此外,文章还比较了简单参数类型和非简单参数类型的处理方式,并解释了在ECMAScript 6之后引入的新参数声明对函数特性的影响。通过技术分析,读者可以更好地理解箭头函数的执行机制和特性。文章还讨论了箭头函数的特点,包括不绑定this和arguments,以及在ECMAScript 6之后的规范中的变化。总的来说,本文通过深入的技术分析,帮助读者更好地理解了JavaScript中箭头函数的特点和执行机制。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《JavaScript 核心原理解析》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(32)

  • 最新
  • 精选
  • ssala
    表达式也是3部分,如果以类比函数的思维来看待表达式,我认为: 1. 表达式中用到的参数构成了表达式的“参数” 2. 表达式本身构成了其逻辑体,这是表达式的逻辑部分 3. 表达式的运算结果就是它的“返回值” 单值表达式x在逻辑上等同于标题中的箭头函数。

    作者回复: 非常赞的思考! 表达式和函数并没有本质不同,它们的抽象形式是一样的。事实上,如果你理解“当运算符等义于某个函数”时,那么你其实就看到了“函数式语言”范式的根本逻辑,关于这一点,我之前也讲过,在《JavaScript语言精髓与编程实践》中也是拿这个来引入函数式语言的。 当运算符等义于函数,那么操作数就是入口参数,而运算结果就是函数返回。运算在本质上就是一个IO和一个处理。这样的理解会让你看到计算理论中最基础的那些模型。 谢谢你提出这一点,真的很赞!

    2019-12-21
    15
  • 海绵薇薇
    Hello,老师好:),也不知道想问啥,感觉还是没有理清楚,望指点,问题如下: 0. 闭包是函数表达式的值? 1. let c = function (a) {} c是闭包(上面匿名函数的实例),并且闭包中有标识符a(简单参数) let e = c e应该也是闭包,并且指向c(e === c 为true),也有标识符a 现在看来e和c闭包中标识符a是同一个,但是不应该是同一个。应该是哪里有了根本性问题,导致这样的理解。 2. 函数一体两面,逻辑和数据。那么闭包指的是逻辑?数据?还是函数的另一个名字表示的逻辑和数据?

    作者回复: 先把你的第一个问题回复了,你试着再推演一下其它的。——你的第一个问题问得非常到位,是你思考上下文中的关键点。赞! “闭包是函数表达式的值?”答案即“是”,且“非”。 首先说“是”的部分。单看ECMAScript这里: https://tc39.es/ecma262/#sec-function-definitions-runtime-semantics-evaluation 你会发现,一个函数表达式执行之后,就是“Return closure”。所以,这个说法在ECMAScript层面是对的。 但是我通常在这里说,这个过程得到的其实还只是一个“函数实例”。为什么呢,比如说: ``` for (var arr = [], i = 0; i<10; i++) arr.push(function() {}); ``` 这里有几个匿名函数在arr[]里面?答案是有10个。所以函数表达式每执行一次,就得到一个函数实例。你的示例`let c = function() {}`也一样,右操作数每执行一次就得到一个新的。 但是这是函数实例。函数闭包其实是在它调用的时候才创建和初始化的。——只不过,所有的、因为函数调用而产生的闭包,都是上面这个实例的一个“副本”。所以,一个实例其实也可以有多个闭包。这里的意思是说,ECMAScript在上面把它叫闭包:既没错,它是所有其它闭包的“母本”;也不太对,因为它不是你在执行过程看到的任何一个闭包。 ——一个函数实例可以有多个闭包,这很少见,但的确是存在的。比如递归,其实是一个函数的多次调用,那这个函数(实例)只有一个,闭包却是有多个了。

    2019-12-02
    2
    11
  • kittyE
    我理解 x 这个单值表达式 是不是可以等价于 x => x; 的计算过程?毕竟直接return了 不知道理解的对不对

    作者回复: 非常赞。这个答案其实比我想的还要好。^^.

    2019-11-30
    2
    11
  • leslee
    为什么箭头函数不绑定 this 跟arguments

    作者回复: 他的设计便是如此。使用当前上下文中的this和arguments,有些时候是很有用的,对现有的normal function的设计也是补充。所以,除了开始使用的时候不习惯之外,在语言功能的设计上还是不错的。 另外,箭头函数没有this,也意味着它不支持this(和arguments)的隐式传入,这在函数式语言特性上是更纯粹的,更接近lamada函数的概念。不过由于JavaScript语言自己的特殊性,它做不到无副作用罢了。

    2019-12-25
    7
  • 海绵薇薇
    Hello 老师好:) 一直以来各大博客描述的包括我理解的闭包是:函数可以访问函数作用域外标识符的能力。但是本文描述闭包的角度完全不一样。这两者有联系吗?

    作者回复: "函数可以访问函数作用域外标识符的能力",这个与闭包没关系,而是scope-chains的事情。不过现在这个概念也改了,在ES6之后,这里被称为环境/作用域。所以,如果仅仅是在概念层面讨论这个话题,比较容易变形。 回到提出这个概念的早期,在ES3~ES5的那个时代,概念比较简单清晰一些。有一个东西称为“对象闭包(object closure)”,这个东西写在spidermonkey的代码里面,也写在一个称为narcissus的开源项目里面,尤其是narcissus,它是JS之父本人Eich一直在维护的一个项目,也就是说,是一种正统的“概念”。——我说这一段是想说明,我下面的说法不是心血来潮随口胡诌。 Ok。什么是对象闭包呢?所谓对象闭包,就是“with (x) ...”中打开的那个用`x`的属性表来形成的闭包。所以,如果(注意这个推论哟),如果对象闭包是一种闭包,那么要么是概念上属性表等义于闭包,要么是闭包是一种属性表。 OVER。^^.

    2019-12-02
    7
  • Elmer
    非简单参数是以let声明的 function test (x = 2) {var x = 3; console.log(x)} // output 3 而let x = 2; var x = 3 却会报错 这个该怎么解释呢

    作者回复: 终于写完了这个问题的回复,拖得太久了,不过希望回复能解您的所惑,值得一读。放在了这里: https://aimingoo.github.io/

    2020-07-07
    5
  • westfall
    老师给被人的回复说闭包是在函数调用的时候才创建和初始化的。但是文中又有一句话 “得到这个闭包的过程与是否调用它是无关的。”这里是不是有矛盾?

    作者回复: Oh...其实主要还是我说得不清楚。 对于函数表达式来说,函数表达式总是在其它表达式运算过程中被引用的,以下面的代码为例: ``` arr.push(function() { // ... }) ``` 这个“匿名函数(表达式)”是在push()方法中被作为运算数引用的。同理,`1 + function() { ... }`这样的代码,是在+运算中被作为右操作数引用的。 函数表达式在被引用时,它自己作为运算元(运算数)会被“执行”一次,这就好象是“单值表达式”作为值也需要被“求值”一次一样。这个操作,可以理解为匿名函数表达式的实例化过程——从代码文本变成一个代码实例,而同时,这个实例的闭包就被创建了。这与“函数声明”中的函数是不一样的。 函数声明(比如说一个声明的具名函数)如下: ``` function foo() { // ... } ``` 在引擎处理到这个具名函数的声明时,它也会被实例化。但是你看到它并没有执行。执行过程要等到类似下面的代码出现时: ``` foo(); // or aCallbackPromise.then(foo) // or setTimeout(foo, 1000) ``` 类似于此的时候,当这个foo被调用时——调用行为才会创建起这个闭包来。 所以,确切地来说:函数与函数表达式,在它们的闭包的处理行为上是不一样的。函数表达式是当它被“求值到”的时候,就创建了一个闭包,而具名函数(例如foo)是在它调用、亦即时foo()发生的时候,才创建的闭包。 我应该是没有非常明确地阐述这二者的区别,这才带来了你的误解。——而在本文中,这里正好处理的是函数表达式。

    2021-01-19
    2
    4
  • 海绵薇薇
    Hello,老师好:) 关于函数闭包又有了新的理解,函数形成的闭包是函数本身 + 函数当前的环境的集合。也就是说函数表达式的Result(闭包母本)中应该包含当前函数的环境(词法环境/执行逻辑的土壤)和函数体本身(对象/数据&逻辑)。

    作者回复: 赞的。^^. ``` // f是函数x的一个实例 f = function x() {} // 创建f的一个闭包、初始化该闭包并在其中执行代码 f() ```

    2019-12-03
    4
  • 子笺
    听完老师这一讲,我脑海中有幅构图: 函数有几个基本要素:执行的上下文【素材】,执行的过程【对素材的加工】,执行的结果【成果】 为了完成成果,必须要有特定的方式去处理整合素材,有合理素材处理流程 首先是整合素材:考虑到整合素材时,有普通的参数传递方式,还有一些特殊的(缺省、解构),就用了2种形式去整合素材。 然后是对素材的加工:结合我对编译的认知,这些素材都有各自的占位,在解析阶段,其实已经有了对各个占位的处理流水线,在执行阶段这些占位真正有了自己的实物,直接在已经构建的流水线上去做处理。 最终可以拿到成果 同理,我想表达式也差不多。 在这节课之前才看了老师关于如何学习的加餐,不管对错,先有自己的理解

    作者回复: 赞!这是极难得的进展! 值得再赞一次!!

    2019-12-20
    3
  • HoSalt
    「一个函数实例可以有多个闭包,这很少见,但的确是存在的。比如递归,其实是一个函数的多次调用,那这个函数(实例)只有一个,闭包却是有多个了。」 老师,看了你回的回答,感觉表达了闭包是函数执行调用时才有的,而我的认知里闭包是和函数具体的执行调用无关的 // 1 function test () { } test() 我理解的闭包是和test相关的,和test()无关 // 2 test function () { return bar () { } } var a = test() test有个闭包,test()执行后的闭包是bar的,和test()这个过程无关 // 3 function test(i) { if(i < 10 || i > 0) return return test(i - 1) } test(5) 这个递归过程,闭包是test,和执行递归的过程无关 上面是我对闭包的理解,还有就是函数调用是一个表达式,表达式怎么会产生闭包,即使返回了一个函数,那也是和返回函数相关的闭包,与当前执行的函数无关 我们常说闭包使用不当会导致内存泄漏,以2的函数为例,执行test()即使造成了内存泄漏也是关联到bar的闭包,而非test的闭包,这也是我认为闭包和执行过程无关的原因之一 老师,不知道我上面的理解有什么问题,感觉和你在评论区与大家讨论的不一样

    作者回复: 有一个简单示例来说明“一个函数实例可以有多个闭包”。如下: ``` function foo() { return argument.callee; } foo()() ``` 在这个例子中,foo被执行了两次。并且在第二次执行的时候直接使用了arguments.callee——也就是第一次执行时的那个函数实例“本身(callee)”。 这种情况下,第一个实例没有被释放(它内部发起了一次call function,因此栈不能释放),只可能是新建一个同一实例的闭包。 最后,闭包是动态运行期的概念,是发生于函数调用行为之中的(为每次calll创建一个)。与运行期无关的,是词法环境,早先的时候也称为作用域(scope)。

    2020-05-14
    2
收起评论
显示
设置
留言
32
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部