编译原理: 三个步骤
例子: var a = 2 分解实例
LHS 与 RHS
LHS: 变量出现在赋值操作的左侧(试图找到变量的容器本身)
RHS: 变量出现在右侧(其实就是查询取到这个变量的源值)
为什么区分LHS和RHS很重要: 因为在变量还没声明时这两种查询的行为是不同的。RHS在在所有作用域中查询失败会报出RerferenceError异常,LHS则会创建出一个新的变量(非严格模式下)。
RerferenceError异常同作用域判别失败关联,TypeError异常代表作用域判别成功但是操作是不合法的。
词法作用域(其实就是静态作用域)是由你在写代码时将变量和块作用域写在哪里来决定的。
作用域会在查找到第一个匹配的标识符时停止,从最内层的向全局作用域查询,会有屏蔽效应。(非全局的变量被屏蔽了无论如何都访问不到)词法作用域只会查找一级标识符,如:foo.bar.baz 只会查找foo然后访问对应的属性。
欺骗词法:
console.log(a);
var a = 2;
// 输出结果为 undefined
console.log(a)
let a = 2
// 会报错
上面的情况就是变量提升行为。
引擎会在解释js代码之前首先对其进行编译。编译中的第一部分工作就是找到所有的声明,并用合适的作用域将他们关联起来。所以,包括变量和函数在内的所有声明都会在任何代码被执行之前首先被处理。
console.log(a);
var a = 2;
//实际的执行顺序为:
var a;
console.log(a)
a = 2;
这个过程就好像变量和函数声明从他们在代码中出现的位置被移动到了最上面,这个过程就是提升。
函数声明会被提升,但是函数表达式却不会被提升:
foo()
var foo = function() {
// ....
}
// 实际解释为
var foo;
foo()
foo = function(){}
// 所以是TypeError,就是 找到了foo的变量 但是它不是一个函数不能运行。
函数声明和变量声明都会被提升,但是是函数鲜卑提升,然后才是变量。
foo() // 输出1
var foo;
function foo() {
console.log(1);
}
// foo函数先被提升,然后再声明 var foo变量属于重复声明会被忽略。
function foo() {
var a = 2;
function bar() {
console.log(a);
}
return bar
}
var baz = foo()
baz() // 2 ----这就是闭包
接下来解释一下以上的代码片段:
函数bar()的词法作用域能够访问foo()的内部作用域。然后我们将bar()函数本身当作一个值类型进行传递。在这个例子中我们把bar所引用的函数对象本身当作返回值。
在foo()执行后,其返回值(其实也就是内部的bar()函数)赋值给baz并调用baz(), 实际上只是通过不同的标识符引用调用了内部的函数bar()
这个例子中,bar在自己定义的词法作用域以外的地方执行。
在foo()执行后,通常会期待foo()的整个内部作用域被销毁,被垃圾回收机制回收。而闭包的神奇机制就在于会阻止这个功能。事实上内部作用域依然存在,因此没有被回收。谁在用这个内部作用域?其实就是bar()本身在使用。
拜bar()所声明的位置所赐,它拥有涵盖foo()内部作用域的闭包,使得该作用域能够一直存活,以供bar()在之后任何时间进行引用。
bar()依然持有对该作用域的引用,而这个引用就叫做闭包。这个函数在定义时的词法作用域以外的地方被调用。闭包使得函数可以继续访问定义时的词法作用域。
要说明闭包,foe循环是最常见的例子
for(var i=0; i<=5; i++) {
setTimeout( function() {
console.log(i)
}, i*1000);
}
/**
我们的预期可能是 分别输出1~5,每秒一次,每次一个。但是实际上,代码运行的输出结果是 每秒一次的频率输出五个6。 因为循环结束的条件是i=6,由于异步执行,在控制台输出时循环已经结束了,i为6。这里的问题可能是,我们以为循环的每次迭代运行时都会给自己捕获一个i的副本。但是根据作用域的原理,实际情况尽管循环中的五个函数都是在迭代中分别定义的,但是他们都被封锁在一个共享的全局作用域,只有一个i。所有函数共享一个i。
*/
// 再次尝试
for(var i=0; i<=5; i++) {
(function() {
setTimeout( function() {
console.log(i)
}, i*1000);
})();
}
/**
IIFE会通过声明并立即执行一个函数来创建作用域。显然现在我们拥有更多词法作用域了,但是这样也不行。为什么呢?疑问作用域是空的,它要获取i最后还是会到全局中去拿。
*/
// 最后尝试
for(var i=0; i<=5; i++) {
(function(j) {
setTimeout( function() {
console.log(j)
}, j*1000);
})(i);
}
// or
for(var i=0; i<=5; i++) {
(function() {
var j = i;
setTimeout( function() {
console.log(j)
}, j*1000);
})();
}
// or (let 劫持块作用域)
for(let i=0; i<=5; i++) {
setTimeout( function() {
console.log(i)
}, i*1000);
}
// 我们通过在词法作用域内每次保存一个i的副本就可以了。问题解决~
模块的特征:
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。