引言
不是退缩,而是继续前进。
第一部分:作用域和闭包
第一章:作用域是什么
当一个块或者函数嵌套在另一个块或者函数中时,就发生了作用域嵌套。
因此在当前作用域中无法找到某个变量时,引擎就会在外层签到的作用域中继续查找,直到找到该变量,或者抵达最外层的作用域(全局作用域)为止,查找过程停止。
分工
协作
引擎:
从头到尾负责整个js程序的编译及执行过程
编译器:
负责词法分析以及代码生成
作用域:
负责收集并维护由所有声明的标识符(变量)组成一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限
编译器会在当前作用域中声明一个变量(如果声明了会忽略该声明)
运行时运气会在作用域中查找该变量,找到就赋值。
变量赋值操作会执行两个动作
js是一门编译语言,与传统编译语言的不同是:它谁不会提前编译的,编译结果也不能在分布式系统中进行移植
运行编译型语言是相对于解释型语言存在的,编译型语言的首先将源代码编译生成机器语言,再由机器运行机器码(二进制)。像C/C++等都是编译型语言。
编译型语言:程序在执行之前需要一个专门的编译过程,把程序编译成 为机器语言的文件,运行时不需要重新翻译,直接使用编译的结果就行了。程序执行效率高,依赖编译器,跨平台性差些。如C、C++、Delphi等. [1]
而相对的,解释性语言编写的程序不进行预先编译,以文本方式存储程序代码。在发布程序时,看起来省了道编译工序。但是,在运行程序的时候,解释性语言必须先解释再运行。java代码执行前的三步骤—-编译
任何js代码片段在执行前都要进行编译(通常就在执行前)
将AST转换为可执行代码的过程
将词法单元流转化成代表了程序语法结构的树—-抽象语法树AST
词法单元生成器分析 var a = 2 中的 a是独立的词法单元还是其他词法单元的一部分,调用的是有状态的解释规则,那么这个过程被称为词法分析
分词/词法分析
解析/语法分析
代码生成
问题:
变量住在哪里?
程序需要时如何找到它们?
所以需要设计良好的规则来存储变量,这套规则就被成为作用域。
问题:在哪里且如何设置这些作用域的规则呢?
引言
1.1编译原理
1.2 理解作用域
1.3 作用域嵌套
总结:作用域就是一套规则,用于确定在何处以及如何查找变量(标识符)。如果查找的目的是对变量进行赋值,那么就会使用LHS查询,如果查找的目的是回去变量的值那就会使用RHS查询。
=操作符或者调用函数时传入参数都会导致关联作用域的赋值操作。
第二章:词法作用域
运行时修改词法作用域。
欺骗词法作用域会导致性能下降。2.2.1 eval
2.2.2 with
接受一个字符串作为参数,并将其中的内容视为好像在书写时就存在在程序中的这个位置的代码。
js中有一些和eval(…)很相似的
setTimeout(…)和setInterval(…)第一个参数可以是字符串,字符串的内容可以被解释为一段攻台生成的函数代码。
new Function(…)最后一个参数可以接受代码的字符串,并将其转化为动态生成的函数。比eval(…)安全。
通常 with 被当做重复应用一个对象中的多个属性的快捷方式,可不需要重复引用对象本身
width 可以将一个没有或者有多个属性的对象处理为一个完全隔离的词法作用域。
因此这个对象的属性也会被处理为定义在这个作用域的词法标识符。
词法作用域就是定义在词法阶段的作用域。
是由你(用户)写代码时将变量和块作用域写在哪里决定的。因此词法分析器处理代码时会保持作用域不变(大多数情况下)。
查找
全局变量会自动成为全局对象,可以间接的通过全局对象属性的访问来对其进行访问——window.a
通过这种技术可以访问被同名变量所遮蔽的全局变量。但是非全局变量被遮蔽了,无论如何都无法被访问到。
2.1词法阶段
2.2 欺骗词法
第三章:函数作用域和块作用域
3.4.1with
3.4.2 try/catch
3.4.3 let
用with从对象中创建的作用域仅在with声明中而非外部作用域有效
为变量显示声明块作用域 let
在使用if/for等代码块中使用 var 声明变量时,他们最终都会属于外部作用域。
let 关键字可以将变量绑定到所在的任意用域中
let变量附加到一个已经存在的块作用域上的行为是隐式的。
使用let进行声明不会在块作用域中进行提升,声明的代码再运行之前,声明并不‘存在’
1.垃圾收集
2.let循环
3.3.1匿名和具名
3.3.2 IIFE 立即执行函数表达式
函数表达式可以是匿名的,函数声明不可以省略函数名
模块化 umd
规避冲突
第三方库通常暴露一个对象作为库的命名空间所有需要暴露给外界的功能都会成为这个对象(命名空间)的属性。
全局命名空间
模块管理
函数作用域:
俗语这个函数的全部变量都可以在整个函数的范围内使用和复用(嵌套的作用域也可以使用)
3.1 函数中的作用域
3.2 隐藏的内部实现
最小授权和最下暴露原则。设计上将具体内容私有化了。
3.3 函数作用域
外部作用域无法访问包装函数内部的任何内容。
如果 function 是声明中的第一个词,那么这个就是函数声明,否则就是函数表达式(function…3.4 块级作用域
第四章 :提升
包括变量函数在内的所有声明都会在任何代码被指向钱首先被处理。
变量和函数声明从它们在代码中的位置被‘移动’到了最上面。
这个过程就叫作提升。
每个作用域都会进行提升操作。
(函数声明会被提升,但是函数表达式不会被提升)
编译器
函数优先:函数会首先被提升,然后才是变量。
小结 :
var a =2 当做两个单独的声明,一个是编译阶段的任务,而第二个是执行阶段的任务。
第五章:作用域闭包
当函数可以记住并访问所在的词法作用域是就产生了闭包,即使函数是在当前词法作用域之外执行。
function foo (){ var a = 2; function bar(){ console.log(a); } return bar } var baz= foo(); baz();//2 朋友这里就是闭包的效果了。
bar()依然持有对该作用域的引用,而这个引用就叫作闭包。
无论用何种方式对函数类型的值进行传递,当函数在别处被调用的时候都可以被观察到闭包。
function foo (){
var a = 2;
function baz(){
console.log(a);
}
bar(baz);
}
function bar(fn){
fn();//这里也是闭包
}
foo();
把内部函数baz 传递给bar,当调用这个内部函数时(fn)它涵盖的foo()内部 作用域的闭包就可以观察到了,因为它能够访问a。
- 无论通过何种手段将内部函数传递到所在的词法作用域以外,它都会持有对原始定义作用域的引用,无论在何处执行这个函数都会使用闭包。- 如果将(访问它们各自词法作用域的)函数当作第一级的值类型并到处传递,例如:定时器、事件监听、Ajax请求、跨窗口通信、web Workers 或者其他异步或者同步任务中,只要使用了回调函数实际上就是在使用闭包。- 5.5 模块
- 模块模式需要具备两个必要条件
- 必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块实例)
- 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包。并且可以访问或者修改私有状态。