理解 JavaScript 做用域

上一篇文章中分析了 JS 中的数据类型和变量。这一篇文章将分析做用域,以及回答上一篇文章中变量提高的缘由。前端

什么是做用域

做用域是一套规则,保存着变量,等待被引擎所查找。segmentfault

var a = 1;
console.log(a);  // => 1
console.log(b);  // => ReferenceError

当打印 a 时,引擎就去做用域中查找 a,找到把结果返回。若是查找失败,那么就会报错。闭包

词法做用域

JS 采用的词法做用域,也能够说是静态做用域。简单来讲,词法做用域是由写代码时将变量写在哪里决定的。函数

先看一段代码:学习

var a = 1;

function fn() {
    var a = 2;
    return a;
}

fn();  // => 2

当执行函数 fn 时,会返回 2,而不是 1。spa

做用域查找

JS 引擎会进行两种查找,LHS 和 RHS。怎么理解?L 和 R 能够说表明左和右。什么的左和右?赋值操做的。code

这里的赋值操做不必定出现 =,好比参数传递也是一个赋值操做。ip

当变量出如今赋值操做的左边时,引擎就会对这个变量进行 LHS 查找;当出如今右边时(这个还能够理解为取得变量的源值),就会进行 RHS 查找。作用域

function foo(a) {
    console.log(a);
}

foo(2);

对于变量 a 来讲,引擎会进行两次查找,1 次 LHS,1 次 RHS。rem

调用 foo(),并传入参数 2,这时存在着一个赋值操做即 a = 2,进行一次 LHS 查找。打印 a 时,须要获取 a 的源值,因此进行一次 RHS 查找。

若是查询失败呢?

对于 LHS 来讲,给未声明的赋值就会查询失败。

a = 2;

可是咱们知道,上面的代码在非严格模式下并不会报错,而变量 a 会被自动建立。

而对于 RHS 来讲,直接使用未声明的变量就会报 ReferenceError。

console.log(a); // => ReferenceError

另外,RHS 虽然查询成功,可是却对查询结果进行非法操做,就会报 TypeError。

var foo = 1;
foo(); // => TypeError

做用域链

前面说,做用域是根据名称查找变量的一套规则。而在实际状况中,常常出现多个做用域嵌套的状况。

function foo(a) {
    console.log(a + b);
}
var b = 2;
foo(2); // => 4

当引擎对 b 进行 RHS 查找时,在当前做用域没法找到,引擎就会在外层做用域中查找,直到找到这个变量,或者直到抵达最外层做用域(全局做用域)为止。

LHS 查找也是如此。

把这样一层一层嵌套的做用域,叫作做用域链。

函数做用域

函数做用域是指,属于这个函数的所有变量均可以在这个函数的范围内使用及复用。

function foo() {
    var a = 1;
}

console.log(a); // => ReferenceError

也就是说,函数外部将没法访问函数内部的变量。

可是这倒是很是有用的。咱们能够利用函数隐藏内部实现,使其外部没法访问、修改等。

当即执行函数表达式

利用函数做用域,能够将外部做用域没法访问的内容包装起来。可是,带来了额外的一个问题,函数名自己“污染”了所在的做用域。

这时,就提出了 IIFE(当即执行函数表达式)。

(function foo() {
    // ...
}());

即包装了内部函数,又避免了引入函数名。由于这个函数名没法被外部做用域所访问。

IIFE 的进阶用法是给其传入参数:

(function fn(global) {
    // ...
})(window);

这样的好处是能够缩短查询时的做用域链。

块做用域

ES6,经过 let 和 const 引入了块做用域。

if (true) {
    let a = 1;
}
console.log(a); // => ReferenceError

变量提高

上一篇文章中中提到了变量提高。

在 JS 中,var a = 1; 这行代码其实会被当作 var aa = 2,并在两个阶段去执行。

在编译阶段,执行声明操做;在执行阶段,执行赋值操做。

全部的变量声明都会被提高到做用域的顶部,这个过程叫作“提高”。

函数声明也会发生提高,而且函数声明会先于变量提高:

var foo = 1;
function foo () {}

typeof foo; // => 'number'

注意,只有函数声明会被提高,而函数表达式不会被提高。

var foo = 1;
var foo = function () {}

typeof foo; // => 'function'

小结

这篇文章梳理了 JavaScript 中做用域的基本知识。

接下来会介绍执行上下文和闭包这两个概念,它们与做用域息息相关。

关于

这是个人公众号,记录着个人前端博客,没事儿也分享一些电影、书籍。

欢迎一块儿交流学习。

相关文章
相关标签/搜索