Scheme 中的 continuation
continuation 是一个非常抽象晦涩的概念,为了理解这个概念,翻阅了大量的资料。
下面记录一些对 continuation 的粗浅的理解。
continuation 代表了程序于某一点接下来将要执行的 “后续部分”。
在 scheme 中,操作符 call-with-current-continuation(下文开始使用缩写 call/cc) 提供了使用 continuation 的方法。call/cc 会捕获调用处的 continuation,然后将该 continuation 传入其参数(一个 procedure)中进行处理。
call/cc接受一个 procedure(过程/函数)作为参数,call/cc调用处的continuation将作为该 procedure 的参数传入。
观察这个例子:
(+ 1 (call/cc (lambda (k) (+ 2 (k 3)))))例子中,
- 首先,我们先把
call/cc所处位置部分当作一个“空洞” - 然后,
call/cc所处位置的continuation,就是 “空洞” 之外的程序后续将执行的过程
(+ 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...浏览器的事件循环机制
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('脚本执行结束')结果:
脚本执行开始promise脚本执行结束promise then 1 执行promise then 2 执行setTimeout 执行事件循环
为了理解上面代码执行背后发生了什么,就必须从浏览器的事件循环开始说起。
首先,大家应该无数次听说过,JavaScript 本身是单线程的,所以同一时间内只能同步处理处理一件事情,异步本身并不是 JavaScript 的一部分。这个限制从好的方面来说,简单的模型可以让我们不用考虑过多的复杂性,大大简化编写程序的难度。
但换个角度,假如浏览器中的所有逻辑代码都只能连续、顺序排队同步执行下去,那么代码中的许多费时操作将会处处导致线程被阻塞住,用户的操作将很难得到及时的响应。想象一下,用户的鼠标点击,滚轮滚动,文字录入,都要等几秒后才能有响应,那是怎么样的一种景象。很显然,Web 应用将变得完全不可用。
于是,我们迫切地需要一种异步的执行模型来解决这个问题。
而 “事件循环(Event loop)” 就是这个问题的答案。浏览器使用事件循环来协调事件、用户交互、脚本、渲染、联网等,用其来实现一种不阻塞的并发模型。
NodeJS 中也有自己的一套事件循环的实现。 浏览器中,window 和每个 worker 线程,都有自己独立的一套 EventLoop,互不干扰。
那么,事件循环又是怎么一回事呢?
READ MORE...使用 MutationObserver 监视 DOM 变化
MutationObserver 可以用来监控 DOM 的变动。 这个 API 定义在 DOM4 中,被设计来替代已经废弃的 DOM3 事件:Mutation Events。
该 API 与事件不同的是,它并不会在每个 DOM 节点变化后立即执行回调函数,而是在 DOM 操作都完成后,将所有变化记录存储在数组中,在事件循环的 microtask 阶段执行回调函数,一次性处理这些存储的变化。
注: 一次事件循环,从 macrotask queue 中取出一个任务执行,执行完毕后,执行整个 microtask queue 中的任务。
构造函数:
MutationObserver(callback)callback 在每个 DOM 变动中被调用,这个回调函数接受两个参数:
- mutationRecords 改变记录列表
- observer 即 MutationObserver 实例
JavaScript 闭包
闭包(词法闭包)的定义
闭包是「函数」以及该函数相关的「引用环境」组合而成的实体。
这个定义中,包含了两个核心的要素:
- 函数:一段可以执行的代码
- 引用环境:生成闭包时的词法环境
观察以下这段 JavaScript 代码:
function outer() { var _private = 'private'
return function inner() { return _private }}
var getPrivate = outer()console.log(getPrivate.name) // 'inner'getPrivate() // 'private'函数 outer 调用后,会将在其内部定义的函数 inner 暴露出来并赋值给 getPrivate,在下一行代码可以确认这点。
再继续下一行,我们调用 getPrivate,发现成功得到了 outer 的本地变量 _private,尽管此时 outer 已经返回(我们学习过的编程知识告诉我们,函数执行时在栈上分配的变量会在离开函数执行环境时被销毁)。这个例子的实际结果说明 outer 的本地变量 _private 被保存在某个地方(堆上分配)继续可用。
根据这些表现,我们可以确认这就是一个典型的闭包。
实际上,这个闭包在 outer 被调用的时候创建。它在某个地方保存了函数 inner 所需要的引用环境,使得离开了创建闭包的环境(outer)时,对自由变量的引用依旧有效(直至闭包的生命周期结束才一并被回收)。
READ MORE...注:自由变量是指在函数内使用,但在函数之外的定义的变量,它既不是本地变量,也不是参数。