08 | x => x:函数式语言的核心抽象:函数与表达式的同一性
该思维导图由 AI 生成,仅供参考
函数的一体两面
- 深入了解
- 翻译
- 解释
- 总结
JavaScript箭头函数的执行机制和特性是本文的重点内容。作者深入探讨了函数的参数传入过程以及闭包的重要性,强调闭包在创建时会实例化参数和执行体,确保每个闭包都有独立的运行环境。此外,文章还比较了简单参数类型和非简单参数类型的处理方式,并解释了在ECMAScript 6之后引入的新参数声明对函数特性的影响。通过技术分析,读者可以更好地理解箭头函数的执行机制和特性。文章还讨论了箭头函数的特点,包括不绑定this和arguments,以及在ECMAScript 6之后的规范中的变化。总的来说,本文通过深入的技术分析,帮助读者更好地理解了JavaScript中箭头函数的特点和执行机制。
《JavaScript 核心原理解析》,新⼈⾸单¥59
全部留言(32)
- 最新
- 精选
- ssala表达式也是3部分,如果以类比函数的思维来看待表达式,我认为: 1. 表达式中用到的参数构成了表达式的“参数” 2. 表达式本身构成了其逻辑体,这是表达式的逻辑部分 3. 表达式的运算结果就是它的“返回值” 单值表达式x在逻辑上等同于标题中的箭头函数。
作者回复: 非常赞的思考! 表达式和函数并没有本质不同,它们的抽象形式是一样的。事实上,如果你理解“当运算符等义于某个函数”时,那么你其实就看到了“函数式语言”范式的根本逻辑,关于这一点,我之前也讲过,在《JavaScript语言精髓与编程实践》中也是拿这个来引入函数式语言的。 当运算符等义于函数,那么操作数就是入口参数,而运算结果就是函数返回。运算在本质上就是一个IO和一个处理。这样的理解会让你看到计算理论中最基础的那些模型。 谢谢你提出这一点,真的很赞!
2019-12-2115 - 海绵薇薇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-02211 - kittyE我理解 x 这个单值表达式 是不是可以等价于 x => x; 的计算过程?毕竟直接return了 不知道理解的对不对
作者回复: 非常赞。这个答案其实比我想的还要好。^^.
2019-11-30211 - leslee为什么箭头函数不绑定 this 跟arguments
作者回复: 他的设计便是如此。使用当前上下文中的this和arguments,有些时候是很有用的,对现有的normal function的设计也是补充。所以,除了开始使用的时候不习惯之外,在语言功能的设计上还是不错的。 另外,箭头函数没有this,也意味着它不支持this(和arguments)的隐式传入,这在函数式语言特性上是更纯粹的,更接近lamada函数的概念。不过由于JavaScript语言自己的特殊性,它做不到无副作用罢了。
2019-12-257 - 海绵薇薇Hello 老师好:) 一直以来各大博客描述的包括我理解的闭包是:函数可以访问函数作用域外标识符的能力。但是本文描述闭包的角度完全不一样。这两者有联系吗?
作者回复: "函数可以访问函数作用域外标识符的能力",这个与闭包没关系,而是scope-chains的事情。不过现在这个概念也改了,在ES6之后,这里被称为环境/作用域。所以,如果仅仅是在概念层面讨论这个话题,比较容易变形。 回到提出这个概念的早期,在ES3~ES5的那个时代,概念比较简单清晰一些。有一个东西称为“对象闭包(object closure)”,这个东西写在spidermonkey的代码里面,也写在一个称为narcissus的开源项目里面,尤其是narcissus,它是JS之父本人Eich一直在维护的一个项目,也就是说,是一种正统的“概念”。——我说这一段是想说明,我下面的说法不是心血来潮随口胡诌。 Ok。什么是对象闭包呢?所谓对象闭包,就是“with (x) ...”中打开的那个用`x`的属性表来形成的闭包。所以,如果(注意这个推论哟),如果对象闭包是一种闭包,那么要么是概念上属性表等义于闭包,要么是闭包是一种属性表。 OVER。^^.
2019-12-027 - 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-075 - westfall老师给被人的回复说闭包是在函数调用的时候才创建和初始化的。但是文中又有一句话 “得到这个闭包的过程与是否调用它是无关的。”这里是不是有矛盾?
作者回复: Oh...其实主要还是我说得不清楚。 对于函数表达式来说,函数表达式总是在其它表达式运算过程中被引用的,以下面的代码为例: ``` arr.push(function() { // ... }) ``` 这个“匿名函数(表达式)”是在push()方法中被作为运算数引用的。同理,`1 + function() { ... }`这样的代码,是在+运算中被作为右操作数引用的。 函数表达式在被引用时,它自己作为运算元(运算数)会被“执行”一次,这就好象是“单值表达式”作为值也需要被“求值”一次一样。这个操作,可以理解为匿名函数表达式的实例化过程——从代码文本变成一个代码实例,而同时,这个实例的闭包就被创建了。这与“函数声明”中的函数是不一样的。 函数声明(比如说一个声明的具名函数)如下: ``` function foo() { // ... } ``` 在引擎处理到这个具名函数的声明时,它也会被实例化。但是你看到它并没有执行。执行过程要等到类似下面的代码出现时: ``` foo(); // or aCallbackPromise.then(foo) // or setTimeout(foo, 1000) ``` 类似于此的时候,当这个foo被调用时——调用行为才会创建起这个闭包来。 所以,确切地来说:函数与函数表达式,在它们的闭包的处理行为上是不一样的。函数表达式是当它被“求值到”的时候,就创建了一个闭包,而具名函数(例如foo)是在它调用、亦即时foo()发生的时候,才创建的闭包。 我应该是没有非常明确地阐述这二者的区别,这才带来了你的误解。——而在本文中,这里正好处理的是函数表达式。
2021-01-1924 - 海绵薇薇Hello,老师好:) 关于函数闭包又有了新的理解,函数形成的闭包是函数本身 + 函数当前的环境的集合。也就是说函数表达式的Result(闭包母本)中应该包含当前函数的环境(词法环境/执行逻辑的土壤)和函数体本身(对象/数据&逻辑)。
作者回复: 赞的。^^. ``` // f是函数x的一个实例 f = function x() {} // 创建f的一个闭包、初始化该闭包并在其中执行代码 f() ```
2019-12-034 - 子笺听完老师这一讲,我脑海中有幅构图: 函数有几个基本要素:执行的上下文【素材】,执行的过程【对素材的加工】,执行的结果【成果】 为了完成成果,必须要有特定的方式去处理整合素材,有合理素材处理流程 首先是整合素材:考虑到整合素材时,有普通的参数传递方式,还有一些特殊的(缺省、解构),就用了2种形式去整合素材。 然后是对素材的加工:结合我对编译的认知,这些素材都有各自的占位,在解析阶段,其实已经有了对各个占位的处理流水线,在执行阶段这些占位真正有了自己的实物,直接在已经构建的流水线上去做处理。 最终可以拿到成果 同理,我想表达式也差不多。 在这节课之前才看了老师关于如何学习的加餐,不管对错,先有自己的理解
作者回复: 赞!这是极难得的进展! 值得再赞一次!!
2019-12-203 - 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-142