{"action":"opened","issue":{"url":"https://api.github.com/repos/lchreal6/blog/issues/7","repository_url":"https://api.github.com/repos/lchreal6/blog","labels_url":"https://api.github.com/repos/lchreal6/blog/issues/7/labels{/name}","comments_url":"https://api.github.com/repos/lchreal6/blog/issues/7/comments","events_url":"https://api.github.com/repos/lchreal6/blog/issues/7/events","html_url":"https://github.com/lchreal6/blog/issues/7","id":369876207,"node_id":"MDU6SXNzdWUzNjk4NzYyMDc=","number":7,"title":"作用域和编译过程-你不知道的JavaScript（一） d","user":{"login":"lchreal6","id":20643097,"node_id":"MDQ6VXNlcjIwNjQzMDk3","avatar_url":"https://avatars3.githubusercontent.com/u/20643097?v=4","gravatar_id":"","url":"https://api.github.com/users/lchreal6","html_url":"https://github.com/lchreal6","followers_url":"https://api.github.com/users/lchreal6/followers","following_url":"https://api.github.com/users/lchreal6/following{/other_user}","gists_url":"https://api.github.com/users/lchreal6/gists{/gist_id}","starred_url":"https://api.github.com/users/lchreal6/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lchreal6/subscriptions","organizations_url":"https://api.github.com/users/lchreal6/orgs","repos_url":"https://api.github.com/users/lchreal6/repos","events_url":"https://api.github.com/users/lchreal6/events{/privacy}","received_events_url":"https://api.github.com/users/lchreal6/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":0,"created_at":"2018-10-14T07:30:35Z","updated_at":"2018-10-14T07:30:35Z","closed_at":null,"author_association":"OWNER","body":"# 作用域和编译过程-你不知道的JavaScript（一）\r\n\r\n## JavaScript的特性\r\n我们都知道JavaScript是一门动态的弱类型语言，但事实上，它也是一种编译语言，只不过是在代码执行之前提前编译，而传统的编译语言会分为一下几步：\r\n* 分词/词法分析\r\n将由字符组成的字符串分解为有意义的代码块，例如`var a =2`，它会被分成var、a、=、2、;词法单元。\r\n* 解析/语法分析\r\n得到词法单元的数组后，编译器会将这些元素转换成有由元素逐级嵌套所组成代表程序语法的抽象树，称为抽象语法树(AST)\r\n* 代码生成\r\n将AST转换成可执行的代码。\r\n\r\nJavaScript除了上述的编译过程之外，它还包括在语法分析和代码生成等阶段的针对性能的优化。\r\nJavaScript的编译不是发生在代码构建之前，而是在代码执行之前那极短的时间。\r\n\r\n## 编译过程\r\n代码由0到1执行一般涉及一下几个机制\r\n\r\n* 引擎  \r\n负责代码的执行\r\n* 编译器  \r\n负责语法分析和代码的生成\r\n* 作用域  \r\n负责收集并维护所有声明的标识符组成的一系列查询，并实施一套严格的查询规则，确定当前代码对这些标识符的访问全向。\r\n\r\n### 作用域嵌套\r\n当一个块或者函数嵌套在另一个块或函数中，则会发生作用域的嵌套，因此在当前作用域内无法找到某个变量时，引擎就会在外部的作用域内查找，知道找到该变量，或抵达到全局作用域，若全局作用域无该变量，则显示为undefined。\r\n\r\n```javascript\r\n\r\nfunction foo(a) {\r\n    console.log( a + b);\r\n}\r\n\r\nvar b = 2;\r\n\r\nfoo(2);\r\n```\r\n\r\n上例这中，执行foo函数，在改作用域内无法找到变量b的标识符，则会在外部的作用域继续进行查找，遇到了改标识符并得知该变量b的值，则直接取到该值并显示出来。\r\n\r\n我们可以通过`var a =2`的例子来分析代码构建和执行的过程。\r\n\r\n首先编译器对这段代码进行词法分析，得到词法单元并进行语法分析构建抽象语法树（AST），但在代码生成阶段，遇到了`var a`这个标识符，会先询问作用域对其a变量进行查询，如果当前作用域有该声明，则忽略该声明，否则要求作用域在改作用域内创建一个新的变量，并命名为a。\r\n\r\n记下来这是引擎执行代码，在处理`a = 2`这段代码时，会通过作用域的规则来搜寻a，如果有则将它赋予值2，否则在全局变量上创建一个变量a（严格模式下发生错误）。\r\n\r\n## 变量查找的规则 \r\n针对变量的查找，它会有两种规则，当将某个值赋予变量时，它是通过LHS规则对变量进行查找，当想直接得到变量的具体值时，它是通过RHS规则来查找。\r\n\r\n`a = 2`这段代码对a变量根据LHS规则来查找，它的目的不是取得a的原值，而是想对a赋予2的值，在层级嵌套的作用域内，若没有a的变量声明，则会在全局作用域内生成变量a，并赋予2的值。\r\n`console.log(a)`这段代码对a变量时根据RHS规则来查找，它想得到的是a的值，在层级嵌套的作用域内，若没有a的变量，它会产生异常。\r\n\r\n```javascript\r\n\r\nfunction foo(a) {\r\n    console.log(a);\r\n}\r\n\r\nfoo(2);\r\n```\r\n上述代码引擎执行`foo(2)`时，会通过RHS规则来搜寻函数foo，然后foo函数的作用域内，因为传递了参数2，我们知道参数传递是值传递，相当于函数内部进行了`var a = 2`这样的操作，所以这个进行了LHS查询，而`console.log(a)`则想得到a的值，进行了RHS查询。\r\n\r\n## 异常\r\n针对RHS查询一般会发生两种异常错误\r\n* ReferenceError\r\n无法找到该目标变量，则会抛出这样该错误\r\n```javascript\r\nfunction foo() {\r\n    console.log(a)\r\n}\r\n\r\nfoo()  //Uncaught ReferenceError: a is not defined\r\n```\r\n\r\n* TypeError\r\n找到了该目标变量，但对其进行不合理的操作则会抛出该错误\r\n```javascript\r\nvar a = 1;\r\n\r\na(); //Uncaught TypeError: a is not a function\r\n```\r\n\r\n"}}
{"actor":{"display_login":"lchreal6"}}