下一篇:《你不知道的javascript》笔记_thisjavascript
这一系列的笔记是在《javascript高级程序设计》读书笔记系列的升华版本,旨在将零碎未知的知识总结java
在传统编译语言的流程中,程序中的一段源代码在执行以前会经历三个步骤,统称为编译
:面试
1. 分词/词法分析(Tokenizing/Lexing)编程
这个过程会将由字符组成的字符串分解成(对编程语言来讲)有意义的代码块,这些代码块被称为词法单元(token)
。例如,考虑程序var a = 2;
。这段程序一般会被分解成为下面这些词法单元:var、a、=、2 、;
。空格是否会被看成词法单元,取决于空格在这门语言中是否具备意义
2. 解析/语法分析(Parsing)segmentfault
这个过程是将词法单元流(数组)转换成一个由元素逐级嵌套所组成的表明了程序语法结构的树。这个树被称为
抽象语法树
(Abstract Syntax Tree,AST)
3. 代码生成数组
将 AST 转换为可执行代码的过程称被称为代码生成。抛开具体细节,简单来讲就是有某种方法能够将
var a = 2;
的 AST 转化为一组机器指令,用来建立一个叫做 a 的变量(包括分配内存等),并将一个值储存在 a 中
相对于上面的流程,javascript
在语法分析和代码生成阶段有特定的步骤来对运行性能进行优化,包括对冗余元素进行优化等。闭包
引擎:从头至尾负责整个JavaScript
程序的编译及执行过程编译器:负责语法分析及代码生成等脏活累活异步
做用域:负责收集并维护由全部声明的标识符(变量)组成的一系列查询,并实施一套很是严格的规则,肯定当前执行的代码对这些标识符的访问权限编程语言
RHS 查询:当变量出如今赋值操做的右侧时进行 RHS 查询
LHS 查询:当变量出如今赋值操做的左侧时进行 LHS 查询(赋值、传参、函数执行)函数
RHS查询异常:RHS 查询在全部嵌套的做用域中遍寻不到所需的变量,引擎就会抛出 ReferenceError 异常
LHS查询异常:非严格模式下,LHS 查询失败会在全局建立变量;在严格模式中 LHS 查询失败时,并不会建立并返回一个全局变量,引擎会抛出同 RHS 查询失败时相似的 ReferenceError 异常
javascript
引擎执行代码var a = 2;
的过程?
编译阶段:var a;
,若是做用域内已存在变量 a,则忽略;若不存在,则在该做用域内声明
执行阶段:a = 2;
,对 a 进行 LHS 引用,并对其赋值
负责收集并维护由全部声明的标识符(变量)组成的一系列查询,并实施一套很是严格的规则,肯定当前执行的代码对这些标识符的访问权限
通俗的说,做用域是维护变量并肯定访问权限的一套规则
词法做用域就是定义在词法阶段的做用域。换句话说,词法做用域是由你在写代码时将变量和块做用域写在哪里来决定的,所以当词法分析器处理代码时会保持做用域不变(大部分状况下是这样的)
下面有个简单的做用域嵌套的例子:
【1】包含着整个全局做用域,其中只有一个标识符: foo。
【2】包含着 foo 所建立的做用域,其中有三个标识符: a、bar 和 b,可访问全局做用域变量。
【3】包含着 bar 所建立的做用域,其中只有一个标识符: c,可访问foo和全局做用域变量。
另外有两个比较特殊的欺骗词法
机制:
eval(..)
函数with
关键字这两个机制的反作用是引擎没法在编译时对做用域查找进行优化,由于引擎只能谨慎地认为这样的优化是无效的。使用这其中任何一个机制都将致使代码运行变慢。 不要使用它们。
书中对做用域链和做用域查找作了一个很是形象的比喻,以下图
这个建筑表明程序中的嵌套做用域链。第一层楼表明当前的执行做用域,也就是你所处的位置。建筑的顶层表明全局做用域。
LHS 和 RHS 引用都会在当前楼层进行查找,若是没有找到,就会坐电梯前往上一层楼, 若是仍是没有找到就继续向上,以此类推。一旦抵达顶层(全局做用域),可能找到了你所需的变量,也可能没找到,但不管如何查找过程都将中止
做用域查找会在找到第一个匹配的标识符时中止
早期的javascript语句中块级做用域就是函数块,这是在读本书以前我粗浅的认识。实际的块级做用域远不止如此
块级做用域:
(1)函数做用域
早期盛行的当即执行函数(IIFE)
就是为了造成块级做用域,不污染全局。经常使用的写法有:
(function(形参){函数体})(实参) (function(形参){函数体}(实参)) !function(形参){函数体}(实参)
(2) with
关键字
(3) try/catch
语句
Google 维护着一个名为 Traceur 的项目,该项目正是用来将 ES6 代码转换成兼容 ES6 以前 的环境(大部分是 ES5,但不是所有),下面是用来兼容低版本建立块级做用域的写法:
{ try { throw undefined; } catch (a) { a = 2; console.log( a ); } }
(4) let/const
关键字
在以前的两篇文章中对变量提高(预解析)有比较充分的说明:
《javascript高级程序设计》笔记:变量对象与预解析
《javascript高级程序设计》笔记:内存与执行环境
本书中定义: 当函数能够记住并访问所在的词法做用域时,就产生了闭包,即便函数是在当前词法做用域以外执行。MDN定义:闭包是函数和声明该函数的词法环境的组合
我的理解:当外部可以访问到某个函数的私有变量时,就会产生闭包(不严谨,仅用于理解)
两个经典的闭包例子:
function makeFunc() { var name = "Mozilla"; function displayName() { alert(name); } return displayName; } var myFunc = makeFunc(); myFunc(); // 'Mozilla'
思考:myFunc
是执行makeFunc
时建立的displayName
函数实例的引用,为何执行myFunc
时会打印出makeFunc
中私有变量name
呢?
解释:闭包是由函数以及建立该函数的词法环境组合而成。这个环境包含了这个闭包建立时所能访问的全部局部变量
function makeAdder(x) { return function(y) { return x + y; }; } var add5 = makeAdder(5); var add10 = makeAdder(10); console.log(add5(2)); // 7 console.log(add10(2)); // 12
分析:按照闭包能暂存变量的思路,执行makeAdder
时,会把参数暂存在所return的函数中,当再次执行函数时,会把两次的参数之和输出
闭包在js编程中随处可见,书中有这样一个结论:
在定时器、事件监听器、 Ajax 请求、跨窗口通讯、Web Workers 或者任何其余的异步(或者同步)任务中,只要使用了回调函数,实际上就是在使用闭包!
定时器闭包案例:
function wait(message) { setTimeout( function timer() { console.log( message ); }, 1000 ); } wait( "Hello, closure!" );
事件监听闭包案例:
function setupBot(name, selector) { $(selector).click( function activator() { console.log( "Activating: " + name ); }); } setupBot( "Closure Bot 1", "#bot_1" ); setupBot( "Closure Bot 2", "#bot_2" );
上面的案例中,有个相同的特色:先定义函数,后执行函数时可以调用到函数中的私有变量或者实参。这即是闭包的特色吧
(1)下面的代码输出内容?
for (var i=1; i<=5; i++) { setTimeout( function timer() { console.log( i ); }, i*1000 ); }
答案:5个6
(2)如何处理可以输出1~5
// 闭包方式 for (var i=1; i<=5; i++) { (function(index) { setTimeout( function timer() { console.log( index ); }, index*1000 ); })(i) } // ES6 方式 for (let i=1; i<=5; i++) { setTimeout( function timer() { console.log( i ); }, i*1000 ); }