浅谈作用域
当我们新建一个可以储存变量的值,怎么才能读取到这个变量呢?能访问到这个变量,就说明符合作用域规则,作用域规则就可以说是 js 引擎读取变量的规则。在 js 中变量分为两种,全局变量和局部变量,全局变量 (拥有全局作用域) 可以在整个 js 应用中被所有代码访问到,从程序开始分配内存直至结束才会被释放(出于对代码的性能、可读性、以及潜在冲突考虑应该减少使用)。局部变量在函数中声明的变量,函数的参数(作用域是局部性的),在函数体外,或者说的当前作用域的上层是无法直接读取的。下面再说下作用域嵌套,产生作用域链的事,在我们实际编写代码的时候,比如:
var a = ‘a’;
function demo(b) {
let c = ‘c’;
function print(c) {
console.log(a + ‘ ‘ + b + ‘ ‘ + c);
}
print(c);
}
demo(‘b’);
执行结果:
在 print 方法执行的时候,会从当前的执行作用域开始查找变量,如果找不到,就继续向上一级继续查找,如果找到则会停止,如果没有就会继续直至当最后到达全局作用域时,此时无论找到还是没找到,都会结束查找。
st=>start: 当前作用域
io1=>start: 上层作用域
io2=>start: …
cond=>end: 全局作用域
st->io1->io2->cond
(简单写了个流程图方便脑补)
接下来还是这段代码让我们来做一个小小的修改。
var a = ‘a’;
function demo(b) {
let c = ‘c’,
a = ‘d’;
function print(c) {
console.log(a + ‘ ‘ + b + ‘ ‘ + c);
}
print(c);
}
demo(‘b’);
执行结果:
结果是输出了上一层的变量 a,这也就是作用域查找的机制,作用域始终从运行时所处的最内部作用域开始,逐渐向外层查找,当遇到第一个符合条件的结果就会返回,停止查找。
变量提升
先简单上一段代码
a = ‘a’;
var a;
console.log(a);
执行结果:为什么会有这种结果呢?声明在后,反而打印出前面声明得值,其实 js 在编译的时候会先进行声明操作,之后再进行赋值操作,也就是只要是在作用域中的声明一出现,就将在代码本身被执行前优先进行处理。也可以将这个过程理解为所有的声明 (变量和函数) 都会被“提升”到作用域的最顶端。(虽然函数声明和变量声明都会被提升,但是函数优先级要高于变量)
(在 ES6 引入了 let 和 const 可以用来声明创建块作用域。for 循环头部定义强烈建议用 let, 感兴趣的人可以去看看 js 函数作用域和块作用域)
闭包
提到闭包很多人脑袋里都会感觉模模糊糊的,闭包在概念上定义是能够读取其他函数内部变量的函数,在中,只有函数内部的子函数才能读取这个函数局部变量,这时候前面说的作用域起到作用了,所以我们现在可以这样理解,定义在一个函数内部的函数,这个函数可以访问其他函数的变量,就可以称为“闭包”。下面来聊聊闭包的用处,主要就是。1. 能够读取到函数内部的变量,2. 可以让变量始终保存在内存中(这点要注意后面解释),下面上代码。
function demo() {
let a = ‘a’;
function print() {
console.log(a);
}
return print;
}
var clo = demo();
clo();
执行结果:这样我们就在外层取得了 demo 函数内部局部变量的 a, 也就是闭包实现从外部读取局部变量的能力。下面说一说让变量始终保存在内存中这点.
function demo() {
let a = 1;
addA = () => a+=1;
function print() {
console.log(a);
}
return print;
}
var clo = demo();
clo();
addA();
clo();
执行结果:闭包可以阻止,demo 内部作用域都被垃圾清理机制回收销毁,可以看到局部变量 a 第二次执行比第一次 +1,这就说明第一次运行结束后啊,没有被回收,因为 print()仍在使用这个作用域所以 demo 不会被回收。(闭包会使得对应的变量一直保存在内存中,不会被清理回收,对内存消耗大,别滥用)