Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

5. You-Dont-Know-JS之作用域与闭包 #5

Closed
funfish opened this issue Sep 16, 2017 · 0 comments
Closed

5. You-Dont-Know-JS之作用域与闭包 #5

funfish opened this issue Sep 16, 2017 · 0 comments
Labels

Comments

@funfish
Copy link
Owner

funfish commented Sep 16, 2017

前言

读的书多了,渐渐也能有自己的体会,书有平庸之作,也有佳作,但有些时候我觉得更多要看人。
在看了Vue和backbone的源码后,越发感觉自己的代码水平有待提高,但常见的各种动物书都看过了,一时间不知道看啥书好。于是在傻乎逛了一圈后,发现《你不知道的JavaScript》这本书,严格来说是上卷,很早以前就听大名,仿佛和高程三齐名,只是一直以没有时间为理由,没有接触。巧的是在傻乎上发现《你不知道的JavaScript》已经在github上出了中文版,而且是全套完整的,幸福来的太突然。细读之,颇有收获,故在此分享。

编译器

文中一开始就讲到编译器理论,离开大学后就没有接触过这东西,再次看到,觉得很nice;
编译过程通常来说分为三步:1.分词/此法分析,这个有点类似搜索引擎里面的拆字; 2.解析,将步骤一生成的代码片段表示为一个抽象语法树;3.生成代码就是将抽象语法树转化为可执行代码;

正如上文说所,引擎在执行代码前,代码会先经过编译器编译;如var a = 2,编译过程会出现编译器和作用域之间的互动,编译器在遇到var a的时候会通过作用域判断有无该声明,没有就声明一个a变量,有就略过;而代码编译后执行的情况,则是引擎向作用域请求,去寻找a变量,再进行赋值操作;就这样var a = 2被分成两步操作。
看到这里,你大概就发现作用域在里面扮演的中间角色,编译过程中将变量参数创建在作用域内,而执行阶段引擎通过访问作用域得到变量,并进行其他操作;分工是如此的明!!作用域有点像数据库的味道,被互相用来用去。。。。文中还提到了LHS和RHS查询,LHS查询简而言之赋值操作,RHS则是取值操作;如console.log(a)这里面就涉及到两个RHS,console的查找和a的RHS引用。
提到LHS和RHS可以加深我们对编译器,引擎和作用域三者之间关系的理解,书中还有趣的提到三者之间聊天的情况,值得玩味;关于LHS和RHS,还涉及到了引擎抛出的错误ReferenceErrorTypeError,前者正如字面解释引用错误,当RHS查询的时候,若在作用域没有发现这个变量,就抛出异常;相同的如果作用域解析成功了,但是进行非法操作,就会TypeError

词法作用域,函数与块儿作用域

在词法作用域里面提到了evalwith这些被好多人提到的黑暗操作,本以为一辈子都见不到了,没想到在自家项目上居然看到有人用。。。立马delete掉;eval这些是很灵活,但是它会大大的影响编译过程中的优化,因为eval这些的出现会导致编译中产生的词法作用域无效:

但如果 引擎 在代码中发现一个 eval(..) 或 with,它实质上就不得不 假定 自己知道的所有的标识符的位置可能是无效的,因为它不可能在词法分析时就知道你将会向eval(..)传递什么样的代码来修改词法作用域,或者你可能会向with传递的对象有什么样的内容来创建一个新的将被查询的词法作用域。

IIFE立即调用函数表达式,最开始接触的时候是在看jQuery里面用到的,这样可以把自己声明的变量和外界隔离开来,避免环境污染;let和const带来的块级作用域,自然是无可辩驳的,以前全局作用域和函数作用域,现在已经基本不用var只用let和const了。。。。。var重复声明就直接覆盖,而且变量提升还会带来undefined的覆盖;以下面例子

var tmp = new Date();

function f() {
  console.log(tmp);
  if (false) {
    var tmp = 'hello world';
  }
}

f(); // undefined

输出结果将会是undefined,而使用块级作用域就很不一样了

提升,作用域闭包

变量提升,在之前的博客中也有提到过,书中再次用编译的思想解释。对于变量声明,编译中自然是在的目的之一就是在作用域创建该变量,后面才轮到引擎执行可执行语句,于是产生声明提升,执行的时候才有初始化,正如下面代码

foo(); // 不是 ReferenceError, 而是 TypeError!

var foo = function bar() {
    // ...
};

提升部分有兴趣可以看看之前一篇博客:void 0 以及let const var 的理解

闭包的概念就有点老生常谈了,如今ES6有了块作用域的概念,一切都好办多了,模块间的import和export语法,也让代码运用更加灵巧,只是结合这本书出版时间,不难发现,在以前这概念可是相当新颖的;

@funfish funfish added the Book label Nov 27, 2017
@funfish funfish changed the title You-Dont-Know-JS之作用域与闭包 5. You-Dont-Know-JS之作用域与闭包 Mar 11, 2018
@funfish funfish closed this as completed Jun 2, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant