彻底理解JavaScript执行上下文

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"前面我们已经学习了JavaScript的"},{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/8cbe43f79e69b4b645a09e3db","title":""},"content":[{"type":"text","text":"事件循环机制"}]},{"type":"text","text":",了解了一段代码是如何被JavaScript引擎执行的,这是粒度最粗的执行单位。接下来,我们开始学习粒度较小的单位:函数的执行机制,以及和函数执行过程相关的所有问题。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"闭包closure"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在计算机领域,闭包closure有三个完全不同的意义:"}]},{"type":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"在编译原理中,它是处理语法产生式的一个步骤;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"在计算几何中,它表示包裹平面点集的凸多边形;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"在编程语言领域,它表示一种特殊的函数。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上个世界60年代,主流编程语言是基于lambda演算的函数式编程语言,所以最初的闭包定义,使用了大量的函数式术语。一个比较模糊的定义是“带有一系列信息的λ表达式”。其实,λ表达式就是函数。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们可以这样简单理解一下,闭包其实只是一个绑定了执行环境的函数,这个函数并不是印在书本里的一条简单的表达式,闭包与普通函数的区别是,它携带了执行的环境,就像人在外星中需要自带吸氧的装备一样,这个函数也带有在程序中生存的环境。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"普通的函数就是一系列表达式的集合,只要给定参数,就会得到确定的结果。而闭包还可以携带大量上下文信息,这函数的执行有时候很难理解,但这也是闭包之所以强大的地方。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最初的闭包定义中,包含两部分内容:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"环境部分"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" - 环境"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" - 标识符列表"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"表达式部分"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这个定义对应到JavaScript标准中,则是:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"环境部分"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" - 环境:函数的词法环境(执行上下文的一部分)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" - 标识符列表:函数中用到的未声明变量"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"表达式部分:函数体"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们可以认为,JavaScript函数完全符合闭包的定义。它的环境部分是由函数词法环境部分组成,标识符列表是函数中用到的未声明变量,表达式部分就是函数体。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"变量作用域"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"每个编程语言中都有作用域这个概念,JavaScript也不例外。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"JavaScript中的作用域无非两种:全局变量和局部变量,函数内部可以直接读取全局变量,而函数外部无法获取内部的局部变量。这里有一个地方需要注意,函数内部声明变量的时候,一定要使用"},{"type":"codeinline","content":[{"type":"text","text":"var"}]},{"type":"text","text":"命令。如果不用的话,你实际上声明了一个全局变量!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":" function f1(){\n    n = 999;\n }\n\n f1();\n\n alert(n); // 999"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果非要在函数外部获取函数内的局部变量,通过在函数里面定义一个函数,将要返回的局部变量返回,再返回新定义的函数也能实现。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"  function f1(){\n\n    var n=999;\n\n    function f2(){\n      alert(n);\n    }\n\n    return f2;\n\n  }\n\n  var result=f1();\n\n  result(); // 999"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"闭包作用"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"闭包的作用主要有两个,一个是前面说的读取函数内部的局部变量,另一个是让某些变量始终保存在内存中。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"  function f1(){\n\n    var n=999;\n\n    nAdd = function(){n+=1} //没有使用var关键字,所以定义了一个全局变量\n\n    function f2(){\n      alert(n);\n    }\n\n    return f2;\n\n  }\n\n  var result=f1();\n\n  result(); // 999\n\n  nAdd();\n\n  result(); // 1000"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在上面的代码中,"},{"type":"codeinline","content":[{"type":"text","text":"f1"}]},{"type":"text","text":"运行之后,就会将局部变量"},{"type":"codeinline","content":[{"type":"text","text":"n"}]},{"type":"text","text":"保存在内存中,同时在全局作用域中加入"},{"type":"codeinline","content":[{"type":"text","text":"nAdd"}]},{"type":"text","text":",运行"},{"type":"codeinline","content":[{"type":"text","text":"nAdd"}]},{"type":"text","text":"保存在内存中的局部变量"},{"type":"codeinline","content":[{"type":"text","text":"n"}]},{"type":"text","text":"就会被加1,从而得到1000。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"执行上下文"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"执行一段JavaScript代码,不光需要全局变量和局部变量,还需要处理"},{"type":"codeinline","content":[{"type":"text","text":"this"}]},{"type":"text","text":"、"},{"type":"codeinline","content":[{"type":"text","text":"with"}]},{"type":"text","text":"等特殊语法,这些信息让JavaScript代码的执行变得更加复杂。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"JavaScript标准把一段代码,执行所需要的一切信息定义为“执行上下文”。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"版本演变"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"执行上下文的定义经历了多个版本的演变。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"ES3"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"执行上下文在ES3中,包含三个部分。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"scope:作用域,也常常被叫做作用域链。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"variable object:变量对象,用于存储变量的对象。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"this value:this值。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"ES5"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在ES5中,改进了命名方式,把执行上下文最初的三个部分改为下面这个样子。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"lexical environment:词法环境,当获取变量时使用。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"variable environment:变量环境,当声明变量时使用。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"this value:this值。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"ES2018"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在ES2018中,执行上下文又变成了这个样子,this值被归入lexical environment,但是增加了不少内容。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"lexical environment:词法环境,当获取变量或者this值时使用。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"variable environment:变量环境,当声明变量时使用"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"code evaluation state:用于恢复代码执行位置。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Function:执行的任务是函数时使用,表示正在被执行的函数。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ScriptOrModule:执行的任务是脚本或者模块时使用,表示正在被执行的代码。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Realm:使用的基础库和内置对象实例。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Generator:仅生成器上下文有这个属性,表示当前生成器。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果从实现语言的角度去分析这些内容,这些内容确实不容易理解。但从实际代码出发,去一步一步分析在代码执行过程中,需要哪些信息,然后再思考,有助于理解这些内容。比如这些代码:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":" var b = {}\n let c = 1\n this.a = 2;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"要执行它,我们需要知道以下信息:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"var"}]},{"type":"text","text":" 把 "},{"type":"codeinline","content":[{"type":"text","text":"b"}]},{"type":"text","text":" 声明到哪里;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"b"}]},{"type":"text","text":" 表示哪个变量;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"b"}]},{"type":"text","text":" 的原型是哪个对象;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"let"}]},{"type":"text","text":" 把 "},{"type":"codeinline","content":[{"type":"text","text":"c"}]},{"type":"text","text":" 声明到哪里;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"this"}]},{"type":"text","text":" 指向哪个对象。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"var"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"var"}]},{"type":"text","text":"是用于声明变量的关键字,它最大的缺陷就是会穿透当前作用域,让变量跑到到上层作用域中。例如下面的代码:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":" if (true) {\n var test = true;\n }\n\n alert(test); // true\n\n for (var i = 0; i < 10; i++) {\n console.log(i)\n }\n\n alert(i); // 10"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以,"},{"type":"codeinline","content":[{"type":"text","text":"var"}]},{"type":"text","text":"会穿透"},{"type":"codeinline","content":[{"type":"text","text":"if"}]},{"type":"text","text":"、"},{"type":"codeinline","content":[{"type":"text","text":"for"}]},{"type":"text","text":"等代码块,进入更上层的作用域。但是,如果在function内定义的变量,则不会影响函数外部。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":" function sayHi() {\n if (true) {\n var phrase = \"Hello\";\n }\n\n alert(phrase); // works\n }\n\n sayHi();\n alert(phrase); // Error: phrase is not defined"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"针对"},{"type":"codeinline","content":[{"type":"text","text":"var"}]},{"type":"text","text":"的这种问题,在没有"},{"type":"codeinline","content":[{"type":"text","text":"let"}]},{"type":"text","text":"的时代,用"},{"type":"text","marks":[{"type":"strong"}],"text":"立即执行的函数表达式(IIFE)"},{"type":"text","text":",通过创建一个函数,并立即执行,就像上面的例子一样,可以完美解决变量提升的缺陷。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":" (function() {\n var message = \"Hello\";\n alert(message); // Hello\n })();"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"let"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"let"}]},{"type":"text","text":"是从ES6开始引入的新的变量声明方式,比起"},{"type":"codeinline","content":[{"type":"text","text":"var"}]},{"type":"text","text":"的诸多弊病,"},{"type":"codeinline","content":[{"type":"text","text":"let"}]},{"type":"text","text":"做了非常明确的梳理和规定。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"为了实现"},{"type":"codeinline","content":[{"type":"text","text":"let"}]},{"type":"text","text":",JavaScript在运行时引入了块级作用域。以下语句中,都会产生let使用的作用域:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"for"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"if"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"switch"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"try/catch/finally"}]}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另外,如果用"},{"type":"codeinline","content":[{"type":"text","text":"let"}]},{"type":"text","text":"重新变量,会报错。如下所示:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"let user;\nlet user; // Uncaught SyntaxError: Identifier 'user' has already been declared"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"为了减少不必要的麻烦,建议使用多使用 "},{"type":"codeinline","content":[{"type":"text","text":"let"}]},{"type":"text","text":",甚至全用 "},{"type":"codeinline","content":[{"type":"text","text":"let"}]},{"type":"text","text":" 声明变量。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"函数Function"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"执行上下文"},{"type":"text","text":"是JavaScript代码执行所需要的一切信息。也就是说,一段代码的执行结果依赖于执行上下文的内容,如果执行上下文不一样了,相同的代码很可能产生不同的结果。在JavaScript中,切换执行上下文最重要的场景就在函数调用。下面,我们先来认识一下,JavaScript中一共有多少种函数。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"函数类型"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"普通函数"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"普通函数是用"},{"type":"codeinline","content":[{"type":"text","text":"function"}]},{"type":"text","text":"关键字定义的函数。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"js"},"content":[{"type":"text","text":" function foo() {\n // code\n }"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"箭头函数"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"箭头函数使用 "},{"type":"codeinline","content":[{"type":"text","text":"=>"}]},{"type":"text","text":" 运算符定义的函数。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"const foo = () => {\n // code\n}"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"class中定义的函数"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"class"}]},{"type":"text","text":"中定义的函数,也就是类的访问器属性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":" class C {\n foo(){\n // code\n }\n }"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"生成器函数"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"用 "},{"type":"codeinline","content":[{"type":"text","text":"function *"}]},{"type":"text","text":" 定义的函数。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":" function foo*(){\n // code\n }"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"类(构造器)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"用"},{"type":"codeinline","content":[{"type":"text","text":"class"}]},{"type":"text","text":"定义的类,实际上也是函数。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":" class Foo {\n constructor(){\n // code\n }\n }"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"异步函数"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"普通函数、箭头函数、生成器函数加上"},{"type":"codeinline","content":[{"type":"text","text":"async"}]},{"type":"text","text":"关键字。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":" async function foo(){\n // code\n }\n const foo = async () => {\n // code\n }\n async function foo*(){\n // code\n }"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"总共8种函数类型,它们的执行上下文,对于普通变量没有什么特殊之处,都是遵循了“继承定义时环境”的规则,主要差异来自 "},{"type":"codeinline","content":[{"type":"text","text":"this"}]},{"type":"text","text":" 关键字。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"this"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"普通函数的this"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "},{"type":"codeinline","content":[{"type":"text","text":"this"}]},{"type":"text","text":" 关键字是JavaScript执行上下文中非常重要的一个组成部分,同一个函数调用方式不同,得到的this值也不同。例如下面这段代码:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":" function showThis(){\n console.log(this);\n }\n\n var o = {\n showThis: showThis\n }\n\n showThis(); // global\n\n o.showThis(); // o"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"对此现象,一般认为普通函数的 "},{"type":"codeinline","content":[{"type":"text","text":"this"}]},{"type":"text","text":" 指向函数运行所在的环境。例如,在上面的例子中,"},{"type":"codeinline","content":[{"type":"text","text":"showThis()"}]},{"type":"text","text":"运行在全局环境中,而 "},{"type":"codeinline","content":[{"type":"text","text":"o.showThis()"}]},{"type":"text","text":" 运行在 "},{"type":"codeinline","content":[{"type":"text","text":"o"}]},{"type":"text","text":" 这一对象中,因此才得出这样的结果。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 更准确的理解是,"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"strong"}],"text":"this"}]},{"type":"text","marks":[{"type":"strong"}],"text":" 是由调用它所使用的引用决定的"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 我们获取函数的表达式,实际上返回的并非函数本身,而是一个Reference类型(JavaScript七种标准类型之一)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Reference类型由两部分组成:一个对象和一个属性值。在上面的例子中,"},{"type":"codeinline","content":[{"type":"text","text":"showThis()"}]},{"type":"text","text":" 产生的Reference类型便是全局对象"},{"type":"codeinline","content":[{"type":"text","text":"global"}]},{"type":"text","text":" 或者 "},{"type":"codeinline","content":[{"type":"text","text":"window"}]},{"type":"text","text":",和属性showThis构成;"},{"type":"codeinline","content":[{"type":"text","text":"o.showThis()"}]},{"type":"text","text":" 产生的Reference类型又是对象o和属性 "},{"type":"codeinline","content":[{"type":"text","text":"showThis"}]},{"type":"text","text":" 构成。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以,"},{"type":"codeinline","content":[{"type":"text","text":"this"}]},{"type":"text","text":" 的真正含义:"},{"type":"text","marks":[{"type":"strong"}],"text":"调用函数时使用的引用Reference,决定了函数执行时刻的 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"strong"}],"text":"this"}],"marks":[{"type":"strong"}]},{"type":"text","marks":[{"type":"strong"}],"text":" 值。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"箭头函数的this"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"把上面的函数改成箭头函数,执行之后发现不管用什么调用,它的值都不变。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"\t\tvar showThis = () => {\n console.log(this);\n }\n\n var o = {\n showThis: showThis\n }\n\n showThis(); // global\n\n o.showThis(); // global\n\n var o = {}\n\n o.foo = function foo(){\n console.log(this);\n return () => {\n console.log(this);\n return () => console.log(this);\n }\n }\n\n o.foo()()(); // o, o, o"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"访问器属性的this"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":" class C {\n showThis() {\n console.log(this);\n }\n }\n var o = new C();\n var showThis = o.showThis;\n\n showThis(); // undefined\n o.showThis(); // o"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在类中的“方法”,结果又不太一样,使用showThis这个引用去调用方法时,得到了undefined,在对象上调用得到对象本身。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"按照上面的方法,可以验证得出:生成器函数、异步生成器函数和异步普通函数跟普通函数行为是一致的,异步箭头函数与箭头函数行为是一致的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"this的机制"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如上文所示,函数不但能够记住定义时的变量,而且还能记住"},{"type":"codeinline","content":[{"type":"text","text":"this"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"为什么能记住这些值?实际上,JavaScript标准中,为函数规定了用于保存定义时上下文信息的私有属性[["},{"type":"codeinline","content":[{"type":"text","text":"Environment"}]},{"type":"text","text":"]]。当一个函数执行时,会创建一条执行环境记录,记录的外层词法环境会被设置为函数的[["},{"type":"codeinline","content":[{"type":"text","text":"Environment"}]},{"type":"text","text":"]]。这就是在切换执行上下文。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"JavaScript用一个栈来管理执行上下文,这个栈中的每一项又包含一个链表。如下图所示:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/87/870a597f321793e79bcef82c5cbcc3c1.jpeg","alt":null,"title":"执行上下文栈结构","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"调用函数时,会入栈一个新的执行上下文;调用结束时,此执行上下文会被弹出栈。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":" this "}]},{"type":"text","text":"是一个更为复杂的机制,JavaScript标准定义了 [["},{"type":"codeinline","content":[{"type":"text","text":"thisMode"}]},{"type":"text","text":"]] 私有属性。它有三个取值:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"lexical"},{"type":"text","text":":表示从上下文中找"},{"type":"codeinline","content":[{"type":"text","text":"this"}]},{"type":"text","text":",这对应了箭头函数。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"global"},{"type":"text","text":":表示当"},{"type":"codeinline","content":[{"type":"text","text":"this"}]},{"type":"text","text":"为"},{"type":"codeinline","content":[{"type":"text","text":"undefined"}]},{"type":"text","text":"时,取全局对象,对应了普通函数。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"strict"},{"type":"text","text":":当严格模式时使用,"},{"type":"codeinline","content":[{"type":"text","text":"this"}]},{"type":"text","text":"严格按照调用时传入的值,可能为"},{"type":"codeinline","content":[{"type":"text","text":"null"}]},{"type":"text","text":"或者"},{"type":"codeinline","content":[{"type":"text","text":"undefined"}]},{"type":"text","text":"。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在上面的代码中,对象方法和普通函数的"},{"type":"codeinline","content":[{"type":"text","text":" this "}]},{"type":"text","text":"有差异,就是因为class被设计为默认按照strict模式执行。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面说函数执行时,会创建一条新的环境记录,将外层词法环境设置为函数的[["},{"type":"codeinline","content":[{"type":"text","text":"Environment"}]},{"type":"text","text":"]]。除此之外,还会根据"},{"type":"codeinline","content":[{"type":"text","text":"this"}]},{"type":"text","text":"关键字的[["},{"type":"codeinline","content":[{"type":"text","text":"thisMode"}]},{"type":"text","text":"]]来标记此记录的[["},{"type":"codeinline","content":[{"type":"text","text":"ThisBindingStatus"}]},{"type":"text","text":"]]私有属性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"当代码执行遇见"},{"type":"codeinline","content":[{"type":"text","text":"this"}]},{"type":"text","text":"关键字时,会逐层检查当前环境中的[["},{"type":"codeinline","content":[{"type":"text","text":"ThisBindingStatus"}]},{"type":"text","text":"]],当找到有"},{"type":"codeinline","content":[{"type":"text","text":"this"}]},{"type":"text","text":"的环境记录时,便可获取当前的"},{"type":"codeinline","content":[{"type":"text","text":"this"}]},{"type":"text","text":"值。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这种规则的导致的结果就是,嵌套的箭头函数中的"},{"type":"codeinline","content":[{"type":"text","text":"this"}]},{"type":"text","text":"都指向外层"},{"type":"codeinline","content":[{"type":"text","text":"this"}]},{"type":"text","text":"值。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":" var o = {}\n o.foo = function foo(){\n console.log(this);\n return () => {\n console.log(this);\n return () => console.log(this);\n }\n }\n\n o.foo()()(); // o, o, o"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"操作this的内置函数"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"JavaScript提供了两个函数:"},{"type":"codeinline","content":[{"type":"text","text":"Function.prototype.call"}]},{"type":"text","text":" 和 "},{"type":"codeinline","content":[{"type":"text","text":"Function.prototype.apply"}]},{"type":"text","text":" 可以指定函数调用时传入的"},{"type":"codeinline","content":[{"type":"text","text":"this"}]},{"type":"text","text":"值。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":" function foo(a, b, c){\n console.log(this);\n console.log(a, b, c);\n }\n foo.call({}, 1, 2, 3);\n foo.apply({}, [1, 2, 3]);"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"call "}]},{"type":"text","text":"和"},{"type":"codeinline","content":[{"type":"text","text":" apply "}]},{"type":"text","text":"的作用是一样的,只是传参的方式不一样。前者传入分开的参数,后者传入一个参数数组。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此外,还有 "},{"type":"codeinline","content":[{"type":"text","text":"Function.prototype.bind"}]},{"type":"text","text":",它可以生成一个绑定过的函数。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"总结"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"要想彻底理解JavaScript的代码执行机制,就必须理解执行上下文、"},{"type":"codeinline","content":[{"type":"text","text":"this"}]},{"type":"text","text":"、闭包和函数,它们共同构成JavaScript最常用的代码执行单元。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"执行上下文:一段代码执行所需要的所有信息,包括变量、"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"strong"}],"text":"this"}],"marks":[{"type":"strong"}]},{"type":"text","marks":[{"type":"strong"}],"text":"等,经历了多个版本的更迭,内容越来越丰富;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"函数:一段逻辑上相关的代码组织形式,是最基本的代码执行单元,JavaScript中共有8种函数;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","marks":[{"type":"strong"}],"text":"this"}],"marks":[{"type":"strong"}]},{"type":"text","marks":[{"type":"strong"}],"text":":指向函数运行所在的环境,由调用它所使用的的引用Reference决定,里面有更加复杂的内在机制;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"闭包:其实就是绑定了执行环境信息的函数,这让函数的执行变得复杂,同时也是强大的原因。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章