你不知道的javaScript

前言

  最近在看你不知道的javaScript这本书,看书的过程中,有一个很明显的感觉就是自己懂得太少。javaScript既是一门充满吸引力,简单易用的语言,又是一门具有较多复杂微妙技术的语言。本着知其然知其所以然的精神,我对读完这本书做了一个简单的总结,不过很多东西要反复琢磨才能真正理解

1. 作用域是什么?

 作用域是一套规则,用于确定在何处以及如何查找变量(标识符)。如果查找的目的是对变量的赋值,那么就会使用LHS查询;如果目的是获取变量的值,就会使用就会使用RHS查询。赋值操作符会导致LHS查询,=操作符或调用函数时传入参数的操作都会导致关联作用域的赋值操作。
像var = 2这样的声明会被分解两个独立的步骤
1.首先,var a在其作用域声明新变量,这是在最开始的阶段,也就是代码执行前进行
2.接着,a = 2会查询(LHS查询)变量a并对其赋值
LHS和RHS查询都会在当前执行作用域开始,如果有需要,就会向上级作用域继续查找目标标识符,这样每次上升一级作用域,最后抵达全局作用域,无论找到或没找到都将停止

什么是LHS和RHS?

LHS和RHS的含义是赋值操作的左侧或右侧,但并不意味就是”=赋值操作符的左侧或右侧”
LHS可以理解为赋值操作的目标是谁。RHS可以理解为谁是赋值操作的源头

2. 词法作用域

词法作用域是定义在词法阶段的作用域。换句话说,词法作用域是由你在写代码时将变量和块作用域写在哪里决定的,因此词法分析器处理代码时会保持作用域不变

1
2
3
4
5
6
7
8
function foo(a){
var b = a*2;
function bar(c){
console.log(a,b,c)
}
bar(b*3)
}
foo(2)//2,4,12

在上面的例子中有三个逐级嵌套的作用域

  1. 包含整个的全局作用域,其中有一个标识符:foo
  2. 包含foo所创建的作用域,其中有三个标识符:a,bar和b
  3. 包含bar所创建的作用域,其中只有一个标识符:c

    3. let

      let关键字可以变量绑定到所在的任意作用域中(通常是{..}的内部,换句话说,let为其声明的变量隐式地了所在的块作用域
    使用let进行的声明不会在块作用域中进行提升。声明的代码被运行之前,声明并不“存在”
    例如
    1
    2
    console.log(bar);//ReferenceError
    let bar = 2 ;

let的for循环

for循环头部的let不仅将i绑定到for循环中,事实上它将其重新绑定到了循环的每一个迭代中,确保使用上一个循环迭代结束时的值重新进行赋值

1
2
3
4
5
let j;
for(j=0; j<10;j++){
let i = j;//每个迭代重新绑定
console.log(i)
}

let声明附属于一个新的作用域而不是当前的函数作用域

4. const

const也可以创建块作用域变量,但是其值是固定的(常量

1
2
3
4
5
6
7
8
9
10
var foo = true;
if (foo){
var a = 2;
const b = 3; //包含在if中的块作用域常量
a = 3;//正常
b = 4;//错误
}
console.log(a);//3
console.log(b);//ReferenceError

  函数是javaScript中最常见的作用域单元。但函数不是唯一的作用域单元,块作用域指的是变量和反函数不仅可以属于所在的作用域,也可予以属于某个代码块(通常指{..}内部)
从ES6引入了let关键字(var关键字的表亲),用来在任意代码块中声明变量,if(..){let a = 2}会声明一个劫持了if的{..}块的变量,并且将变量加到这个块中

5. 作用域闭包

闭包是基于词法作用域书写代码时所产生的自然结果
下面一段代码,清晰的展示了闭包

1
2
3
4
5
6
7
8
9
function foo(){
var a = 2;
function bar(){
console.log(a);
}
return bar;
}
var baz = foo();
baz();//2 这就是闭包的效果

  函数bar()的词法作用域能访问到foo()的内部作用域,然后我们把bar()函数本身当做一个值类型进行传递,在这个例子中bar所引用的函数对象本身当作返回值
bar()显然可以被正常运行。但它是在自己定义的词法作用域以外的地执行
在foo()执行后,通常期待foo()的整个作用域后被销毁。而闭包的”神奇”之处就是阻止这件事情的发生。事实上内部作用域依然存在,bar()本身在使用。   拜bar()所声明的位置所赐,它拥有涵盖foo()内部作用域的闭包,使得该作用域能够一直存活,以供bar()在之后的任何时间进行引用
bar()依然持有对该作用域的引用,而这个引用就叫做闭包

1
2
3
4
5
6
function wait(message){
setTimeout(function timer(){
console.log(message);
},1000);
}
wait("hello,closure!");

  将一个内部函数(名为timer)传递给setTimeout(..).timer具有涵盖wait(..)作用域的闭包,因此还保有对变量message的引用
wait(..)执行1000毫秒后,它的内部作用域并不会消失,timer函数依然保有wait(..)作用域的闭包
这就是闭包。

坚持原创技术分享,您的支持将鼓励我继续创作!