资讯专栏INFORMATION COLUMN

理解 JavaScript 作用域

dadong / 2964人阅读

摘要:作用域链前面说,作用域是根据名称查找变量的一套规则。把这样一层一层嵌套的作用域,叫做作用域链。因为这个函数名无法被外部作用域所访问。的进阶用法是给其传入参数这样的好处是可以缩短查询时的作用域链。

上一篇文章中分析了 JS 中的数据类型和变量。这一篇文章将分析作用域,以及回答上一篇文章中变量提升的原因。

什么是作用域

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

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。

作用域查找

JS 引擎会进行两种查找,LHS 和 RHS。怎么理解?L 和 R 可以说代表左和右。什么的左和右?赋值操作的。

这里的赋值操作不一定出现 =,比如参数传递也是一个赋值操作。

当变量出现在赋值操作的左边时,引擎就会对这个变量进行 LHS 查找;当出现在右边时(这个还可以理解为取得变量的源值),就会进行 RHS 查找。

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

foo(2);

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

调用 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 中作用域的基本知识。

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

关于

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

欢迎一起交流学习。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/99470.html

相关文章

  • 深入理解JavaScript作用作用

    前言 JavaScript中有一个被称为作用域(Scope)的特性。虽然对于许多新手开发者来说,作用域的概念并不是很容易理解,本文我会尽我所能用最简单的方式来解释作用域和作用域链,希望大家有所收获! 想阅读更多优质文章请猛戳GitHub博客 作用域(Scope) 1.什么是作用域 作用域是在运行时代码中的某些特定部分中变量,函数和对象的可访问性。换句话说,作用域决定了代码区块中变量和其他资源的可见...

    baiy 评论0 收藏0
  • 深入理解JavaScript作用作用

    前言 JavaScript中有一个被称为作用域(Scope)的特性。虽然对于许多新手开发者来说,作用域的概念并不是很容易理解,本文我会尽我所能用最简单的方式来解释作用域和作用域链,希望大家有所收获! 想阅读更多优质文章请猛戳GitHub博客 作用域(Scope) 1.什么是作用域 作用域是在运行时代码中的某些特定部分中变量,函数和对象的可访问性。换句话说,作用域决定了代码区块中变量和其他资源的可见...

    ytwman 评论0 收藏0
  • 谈谈javascript语法里一些难点问题(二)

    摘要:讲作用域链首先要从作用域讲起,下面是百度百科里对作用域的定义作用域在许多程序设计语言中非常重要。原文出处谈谈语法里一些难点问题二 3) 作用域链相关的问题 作用域链是javascript语言里非常红的概念,很多学习和使用javascript语言的程序员都知道作用域链是理解javascript里很重要的一些概念的关键,这些概念包括this指针,闭包等等,它非常红的另一个重要原因就...

    Enlightenment 评论0 收藏0
  • 【译】理解JavaScript作用

    摘要:作用域是代码的不同部分在运行期间的可见性。大多数开发者想当然地理解作用域,但毫无疑问,有它自己的说明。变量可能是全局作用域的,或者是方法作用域的。总而言之,不要重复声明变量,使用良好命名,尽力避免在声明前调用和执行任何东西。 原文链接:https://hackernoon.com/unders... 什么是作用域? 就像JavaScript中的其他东西一样,作用域并无特别之处。尽管大多...

    betacat 评论0 收藏0
  • Javascript】深入理解this作用问题以及new/let/var/const对this作

    摘要:理解作用域高级程序设计中有说到对象是在运行时基于函数的执行环境绑定的在全局函数中,等于,而当函数被作为某个对象调用时,等于那个对象。指向与匿名函数没有关系如果函数独立调用,那么该函数内部的,则指向。 理解this作用域 《javascript高级程序设计》中有说到: this对象是在运行时基于函数的执行环境绑定的:在全局函数中,this等于window,而当函数被作为某个对象调用时,t...

    snowLu 评论0 收藏0
  • 深入理解JavaScript (3) —— 作用

    摘要:针对有经验的开发者,纠正一个过时的理解你可能听过这句话没有块级作用域。详见而且,在不使用新语法的前提下,没有块级作用域的正确理解应该是只有全局作用域和函数作用域。各自的作用域下,用各自的。 针对有经验的开发者,纠正一个过时的理解:你可能听过这句话javascript没有块级作用域。所谓块,就是大括号{}中间的语句。诚然,在ES6之前,这句话是完全正确的,但ES中新增的两个命令let和c...

    jerry 评论0 收藏0

发表评论

0条评论

dadong

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<