尧以是传之舜,舜以是传之禹,禹以是传之汤,汤以是传之文、武、周公,文、武、周公传之孔子,孔子传之孟轲。
—韩愈《原道》
绝大多数作者,对于“变量提升”的本原,语焉而不详。而本书着眼于浏览器中JavaScript代码的生命周期,讲解了函数上下文、原型链、事件循环等内容。
尽管这不是一本论述浏览器内核工作原理的专著,但是通过阅读本书,浏览器在我眼里已不再是个纯粹的黑盒子了。只有明了了JavaScript引擎的基本运行机理,才能编写出更为可靠的代码。
JavaScript与Scheme同为动态语言,可参照《计算机程序的构造和解释》中的“求值的环境模型”来理解函数与生成器函数的执行上下文。迭代器与生成器提供了一种实现“流”的新方式,有利于函数式代码的编写。代理(Proxy)起到了一种类似于Python中运算符重载与装饰器的作用。原型链,就是一种继承树,只不过可以显示地控制继承关系;而在其他语言里类的定义就决定了继承关系。Promise对象可以被链式调用,这是因为调用后会返回另一个Promise对象。
文摘
注册标识符
如果是创建一个函数环境,那么创建形参及函数参数的默认值。如果是非函数环境,将跳过此步骤。
如果是创建全局或函数环境,就扫描当前代码进行函数声明(不会扫描其他函数的函数体),但是不会执行函数表达式或箭头函数。对于所找到的函数声明,将创建函数,并绑定到当前环境与函数名相同的标识符上。若该标识符已经存在,那么该标识符的值将被重写。如果是块级作用域,将跳过此步骤。
扫描当前代码进行变量声明。在函数或全局环境中,查找所有当前函数以及其他函数之外通过var声明的变量,并查找所有通过let或const定义的变量。在块级环境中,仅查找当前块中通过let或const定义的变量。对于所查找到的变量,若该标识符不存在,进行注册并将其初始化为undefined。若该标识符已经存在,将保留其值。
this
当调用函数时,除了传入在函数定义中显式声明的参数之外,同时还传入两个隐式参数:arguments与this。参数this表示被调用函数的上下文对象,即与函数调用相关联的对象。
使用关键字new调用函数会触发以下几个动作:
创建一个新的空对象;
该对象作为this参数传递给构造函数,从而成为构造函数的函数上下文;
新构造的对象作为new运算符的返回值。
如果构造函数返回一个对象,则该对象将作为整个表达式的值返回,而传入构造函数的this将被丢弃。但是,如果构造函数返回的是非对象类型,则忽略返回值,返回新创建的对象。
箭头函数的this与声明所在的上下文的相同。
原型链
每一个函数都具有一个原型对象;
每一个函数的原型都具有一个constructor属性,该属性指向函数本身;
构造器对象的原型被设置为新创建的对象的原型。
constructor属性的存在仅仅是为了说明该对象是从哪儿创建出来的。instanceof操作符,会检查操作符右边的函数的原型是否存在于操作符左边的对象的原型链上。
事件循环
事件循环基于两个基本原则:
一次处理一个任务;
一个任务开始后直到运行完成,不会被其他任务中断。
事件循环通常至少有两个事件队列:宏任务队列和微任务队列;
单次循环迭代中,最多处理一个宏任务(其余的在队列中等待),而队列中的所有微任务都会被处理;
当微任务队列处理完成并清空时,事件循环会检查是否需要更新UI渲染,如果是,则会重新渲染UI视图。
当代码对DOM进行一系列连续的读取和写入操作时,浏览器每次都会强制重新计算布局信息,这会引起布局抖动。
通过内置的CustomEvent构造函数和dispatchEvent方法,创建和分发自定义事件,减少应用程序不同部分之间的耦合。