JavaScript 中的柯里化
不过多介绍柯里化的定义和应用场景,直接看实现。
柯里化实现
如果我们有以下加法函数:
1
const add = (a, b) => a + b
要怎么实现柯里化呢?
手工柯里化
我们可以选择直接手写柯里化的 add:
1
2
3
4
5
6
7
// 手工柯里化
const curryingAdd = a => b => a + b
// 使用
const inc = curryingAdd(1)
inc(1) // 2
通过闭包的嵌套,我们可以实现每次只接受一个参数的手工柯里化的函数。
但是这种方式,对于每个需要柯里化的函数,都需要侵入其实现,非常麻烦。因此并没有什么实用价值。我们需要能一种通用的方案来自动化做这个事情。
自动柯里化
柯里化的实现,核心在于根据参数数量选择返回接受剩余参数的新函数,或者返回最终结果。
在 JavaScript 中,具备实现这一切的条件,我们可以通过函数的 length
得知形参的数量,通过 arguments
对象得知实参的信息,通过闭包返回新函数也不在话下。下面开始尝试实现,先从简单的两个参数的情况开始:
λ 演算(λ-calculus)笔记
文中涉及到代码的地方,除了 λ 表达式,都尽量提供 Scheme 以及 JavaScript 代码做对照,以便加深理解。
先从基本概念开始。λ 演算包含构建 λ 项和对 λ 项执行操作。
λ 项 (lambda terms) 构建语法
-
变量 ( variable )
expression -> variable
-
应用 ( application )
expression -> expression expression
-
抽象化 ( abstraction )
expression -> λ variable . expression
为了不引起歧义,可以适当使用括号进行分组 ( grouping ):
expression -> (expression)
变量
可以理解为编程语言中的变量、标识符(一些用来绑定值的名称),例如 a
, b
, c
等等。
应用
应用是指将函数应用 ( applying ) 在实参 ( argument ) 上面。注连续多个应用是左结合的。
举例说明:
READ MORE...Scheme 中的 continuation
continuation
是一个非常抽象晦涩的概念,为了理解这个概念,翻阅了大量的资料。
下面记录一些对 continuation
的粗浅的理解。
continuation
代表了程序于某一点接下来将要执行的 “后续部分”。
在 scheme 中,操作符 all‑with‑current‑continuation
(下文开始使用缩写 call/cc
) 提供了使用 continuation
的方法。call/cc
会捕获调用处的 continuation
,然后将该 continuation
传入其参数(一个 procedure)中进行处理。
call/cc
接受一个 procedure(过程/函数)作为参数,call/cc
调用处的continuation
将作为该 procedure 的参数传入。
观察这个例子:
1
(+ 1 (call/cc (lambda (k) (+ 2 (k 3)))))
例子中,
- 首先,我们先把
call/cc
所处位置部分当作一个“空洞” - 然后,
call/cc
所处位置的continuation
,就是 “空洞” 之外的程序后续将执行的过程
1
(+ 1 空洞)
具体点,就是 “一个将对该(空洞)位置做加一的过程”,相当于:
READ MORE...如何实现一个符合 Promise/A+ 规范的 Promise
Promise/A+
要自己实现 Promise,就绕不开 Promise/A+ 规范,业界主流的 Promise 实现(包括浏览器实现)均实现了该规范。
Promise/A+ 本身规定的内容比较少,这也是我们实现一个简单的 Promise 可以依据的最简单标准了。
首先,解释一些 Promise/A+ 中的术语概念:
- promise
promise 是一种行为符合本标准的对象或函数。 - thenable
thenable 是定义了then
方法的对象或函数。 - value
value 是任何合法的 JavaScript 值(包含undefined
、thenable 或 promise)。 - exception
exception 是使用throw
语句抛出的值。 - reason
reason 是一个描述 promise 因何而 rejected 的值。
基于这些基础属于概念,下面开始解读规范的一些核心要点。
READ MORE...浏览器的事件循环机制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
console.log('脚本执行开始')
setTimeout(function () {
console.log('setTimeout 执行')
}, 0)
new Promise((resolve, reject) => {
console.log('promise')
resolve()
})
.then(function () {
console.log('promise then 1 执行')
})
.then(function () {
console.log('promise then 2 执行')
})
console.log('脚本执行结束')
结果:
1
2
3
4
5
6
脚本执行开始
promise
脚本执行结束
promise then 1 执行
promise then 2 执行
setTimeout 执行
事件循环
为了理解上面代码执行背后发生了什么,就必须从浏览器的事件循环开始说起。
首先,大家应该无数次听说过,JavaScript 本身是单线程的,所以同一时间内只能同步处理处理一件事情,异步本身并不是 JavaScript 的一部分。这个限制从好的方面来说,简单的模型可以让我们不用考虑过多的复杂性,大大简化编写程序的难度。
但换个角度,假如浏览器中的所有逻辑代码都只能连续、顺序排队同步执行下去,那么代码中的许多费时操作将会处处导致线程被阻塞住,用户的操作将很难得到及时的响应。想象一下,用户的鼠标点击,滚轮滚动,文字录入,都要等几秒后才能有响应,那是怎么样的一种景象。很显然,Web 应用将变得完全不可用。
于是,我们迫切地需要一种异步的执行模型来解决这个问题。
而 “事件循环(Event loop)” 就是这个问题的答案。浏览器使用事件循环来协调事件、用户交互、脚本、渲染、联网等,用其来实现一种不阻塞的并发模型。
NodeJS 中也有自己的一套事件循环的实现。 浏览器中,window 和每个 worker 线程,都有自己独立的一套 EventLoop,互不干扰。
那么,事件循环又是怎么一回事呢?
READ MORE...