YouDon'tKnowJS记录(一)

作用域和闭包

作用域是什么

(根据名称查找变量的一套规则)
js 动态的 弱类型的 编译语言

编译器

(语法分析和代码生成)
var a = 2; 编译器将它分解为var, a, =, 2, 这么几个词法单元,再将词法单元解析成一个树结构
代码生成: 在当前作用域声明一个变量a, 运行时引擎在作用域中查找变量a, 若能找到则赋值

引擎

LHS查询 找到变量容器本身,从而可以对其赋值 eg: a = 2;
RHS查询 取到它的源值 eg: foo(2);

(最好先在开头把变量全都声明 var a, b, c, d;)

作用域嵌套

引擎从当前的执行作用域开始查找变量,找不到则向上一级继续查找,直到抵达最外层的全局作用域

RHS时,当在所有作用域都找不到该变量,引擎抛出 ReferenceError 异常 eg: console.log(b);
LHS时,会创建一个全局变量(非严格模式下) eg: a=2;

严格模式(ES5引入)
禁止自动或隐式创建全局变量

词法作用域

由函数所声明的位置来定义

欺骗词法作用域

(导致编译时无法优化,不要使用)

1.eval (非严格模式)

1
2
3
4
5
6
function foo(str, a){
eval(str);
console.log(b, a);
}
var b = 2;
foo("var b = 3;", 1); // 3, 1`

执行eval(..)后, 引擎并不在意前面的代码是以动态形式插入进来,并对词法作用域的环境进行修改
动态生成代码的场景罕见, 影响性能, 尽量避免使用

2.with (非严格模式)

with 通常被当作重复引用 同一个对象中的多个属性 的快捷方式

1
2
3
4
5
6
7
8
9
10
var obj = {
a: 1;
b: 2;
c: 3;
};
//重复obj
obj.a = 2; obj.b = 3; obj.c =4;
//快捷
with(obj) {
a=3; b=4; c=5; }

with 会凭空创建一个全新的词法作用域
若console.log(a) 若 obj里没有a属性 with(obj){a=3;…}会导致 自动创建一个全局变量a

函数作用域

属于这个函数的全部变量可以在整个函数范围内使用及复用

  • 立即执行函数表达式IIFE ()(); / (());

块作用域

如 for() 循环 with try/catch

let

let 可以将变量绑定到所在的任意作用域中
用{..} 为let 创建一个用于绑定的块
使用let 声明 不会在块作用域中进行提升

提升

先声明 再执行
只有声明会被提升
每个作用域内 声明都会提升
顺序:
1.函数
2.变量
重复声明:
后面的函数声明覆盖前面的 (以最后的为准)
前面的变量声明忽略后面的 (以最前的为准)
块内部的函数声明会被提升到所在作用域的顶部, 应避免在块内部声明函数
如 if(a){function foo() {…}}

闭包

闭包就是 能够读取其他函数内部变量的函数
作用:
1.可以读取函数内部的变量
2.让这些变量的值始终保持在内存中 (内存消耗大,不能滥用闭包)(解决方法:在退出函数之前,将不使用的局部变量全部删除)
eg: 在函数的内部,再定义一个函数

1
2
3
4
5
6
7
8
9
function f1(){
    var n=999;
    function f2(){
      alert(n);
    }
    return f2;
  }
  var result=f1();
  result(); // 999

eg: 定时器 事件监听器 ajax请求 跨窗口通信 等

循环和闭包

1
2
3
4
for(var i=1;i <=5; i++) {
setTimeout( function timer(){
console.log(i);},i*1000);
}

输出五次6 与预期不符
尽管循环中五个函数在各个迭代中分别定义,但都被封闭在一个共享的全局作用域中, 实际上只有一个i
在循环中 每个迭代都需要一个闭包作用域

1
2
3
4
5
6
7
for(var i=1; i<=5; i++)
{ (function() {
var j = i;
setTimeout( function timer(){
console.log(j);},j*1000);
})();
}

使用let声明

重返块作用域
let可以劫持块作用域 (本质上是 将一个块转换成一个可以关闭的作用域)
for循环头部的let声明 每次迭代都会声明

1
2
3
4
for(let i=1;i <=5; i++) {
setTimeout( function timer(){
console.log(i);},i*1000);
}

模块

为创建内部作用域而调用了一个包装函数
包装函数的返回值必须包括至少一个对内部函数的引用

分享到:
Disqus 加载中...

如果长时间无法加载,请针对 disq.us | disquscdn.com | disqus.com 启用代理