理解JavaScript的作用域

栏目: JavaScript · 发布时间: 4年前

内容简介:当一个块或函数嵌套在另一个块或函数中时,就发生了作用域的嵌套。作用域嵌套的查询规则:这样由多个执行上下文的变量对象构成的链表就叫做作用域链。而与词法作用域相对的是动态作用域,函数的作用域是在函数调用的时候才决定的。
  • 作用域
  • 作用域嵌套与作用域链
  • 查询异常
  • 词法作用域(静态作用域)
  • 函数作用域
  • 全局作用域
  • 块级作用域
  • 变量提升机制
  • 函数优先

作用域

  • 是什么? 作用域是指程序源代码中定义变量的区域。
  • 有什么用? 作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。

作用域嵌套与作用域链

当一个块或函数嵌套在另一个块或函数中时,就发生了作用域的嵌套。作用域嵌套的查询规则:

  • 首先,JS引擎从当前的执行作用域开始查找变量。
  • 然后,如果找不到,引擎会在外层嵌套的作用域中继续查找。
  • 最后,直到找到该变量,或抵达最外层的全局作用域为止。

这样由多个执行上下文的变量对象构成的链表就叫做作用域链。

function foo(b) {
    // 变量a在全局作用域下查询得到的
    console.log(a + b) // 4
}
var a = 2

foo(2)
复制代码

查询异常

  • 如果获取未声明的变量的值会导致 ReferenceError 异常。
  • 如果对未声明过的变量进行赋值:
    • 在非严格模式下,JS引擎会为其自动创建一个全局变量且进行赋值。
    • 如在严格模式下,会导致 ReferenceError 异常。
var a = 10

function sum() {
    // b 是没有显式声明的,但被隐式创建为全局变量
    b = 20
    return a + b
}

console.log(sum()) // 30
console.log(c) // ReferenceError: c is not defined
// 因为c是未定义的变量,无法使用
复制代码

词法作用域(静态作用域)

  • 词法作用域就是定义在词法阶段的作用域,简单说就是函数的作用域在函数定义的时候就决定了。
  • 词法作用域查找规则是:作用域查找是从内到外进行查找的,直到找到第一个匹配的标识符时停止。

而与词法作用域相对的是动态作用域,函数的作用域是在函数调用的时候才决定的。

词法作用域的经典例子

请思考以下2个例子输出的结果

// 例子1
var scope = "global scope";

function checkscope() {
    var scope = "local scope";

    function f() {
        return scope;
    }

    return f();
}

checkscope();

// 例子2
var scope = "global scope";

function checkscope() {
    var scope = "local scope";

    function f() {
        return scope;
    }

    return f;
}

checkscope()();
复制代码

上面两段代码的结果都输出: local scope ,因为 JS 采用的是词法作用域,函数的作用域基于函数创建的位置。

欺骗词法作用域

  • 欺骗词法作用域意思是(无意地)修改了所在的作用域。
  • 欺骗词法作用域的两个方法: eval()with()
  • 缺点:欺骗词法作用域会导致性能下降。

eval() 函数可以接受一个字符串,并执行其中的的 JS 代码。

function foo(str, a) {

    eval(str) // 欺骗作用域,引擎在此声明了:var b = 2,所以能计算以下结果
    
    console.log(a + b) // 3
}
var b = 1

foo('var b = 2', 1)
复制代码

with() 语句通常被当作重复引用同一个对象中多个属性的快捷方式,可以不需要重复引用对象本身。

function foo(obj) {
    with (obj) {
        b = 4
        a = 2
    }
}

var obj = {
    b: 3
}

foo(obj)

console.log(obj.a) // undefined
console.log(obj.b) // 4
console.log(a)  // 2,a被当前全局变量泄露到全局作用域上了
复制代码

为什么最后能输出a的值为2?

原因是把 obj 对象传入函数内, obj 对象没有 a 属性,所以 obj.a 的值是 undefined ,却在 with() 语句中的 a 被当作全局变量隐式声明了,而且进行了赋值为2。

函数作用域

函数作用域内的变量或者内部函数,对外都是封闭的,从外层的作用域无法直接访问函数内部的作用域,否则会报引用错误异常。解决方法: 闭包

function f1() {
    var a = 1;
    var b = 2;
    var c = 3;
}

console.log(a, b, c) // ReferenceError: a, b, c is not defined
// 原因变量a,b,c是定义在函数内部的变量,外部作用域是无法访问的。
复制代码

全局作用域

最外层的全局作用域,任何地方都可以访问得到。在最外层作用域下使用 var 关键字会定义全局变量,也就是说会挂载在 window 对象上,或者不使用关键字 var、let、const 直接对变量名字进行赋值,JS也会自动为其创建为全局变量。

var a = 10;

function f1() {
    b = 20

    function f2() {
        c = 30
        console.log(a) // 10
    }

    f2()
}

f1()

// b和c变量被隐式声明到全局变量了,所以能访问到,这也叫变量提升机制
console.log(b) // 10
console.log(c) // 20

// 但a,b,c也被挂载在window对象(全局作用域)上面了
console.log(window.a) // 10
console.log(window.b) // 20
console.log(window.c) // 30
复制代码

块级作用域

块级作用域指在代码块 {} 里面定义的变量,只会在当前代码块有效,如果外层作用域下想访问该变量,会报引用错误异常。

使用关键字 letconst 定义块级作用域的变量。

for (let i = 0; i < 10; i++) {

}

console.log(i) // ReferenceError: i is not defined
// 因为i只能在for循环内部有效,外部作用域是访问不到的。
复制代码

变量提升机制

先声明,后赋值

JS变量的声明和赋值是2个不同的步骤,比如:

a = 10
var a
console.log(a) // 10
复制代码

JS引擎会将 var aa = 10 当作两个单独的声明,第一个是编译阶段的任务,而第二个则是执行阶段的任务:

// 编译阶段的任务
var a
// 执行阶段的任务
a = 10

console.log(a) // 10
复制代码

变量提升

function getValue(condition) {
    console.log(value)  // 不会报错

    if (condition) {
        var value = "blue";

        return value;
    } else {

        return null;
    }
}

getValue(undefined == null)
复制代码

实际上以上代码的 value 已经被变量提升了:

function getValue(condition) {
    
    // 编译阶段的任务
    var value;
    
    console.log(value)  // undefined

    if (condition) {
        // 执行阶段的任务
        value = "blue";

        return value;
    } else {

        return null;
    }
}

getValue(undefined == null)
复制代码

函数优先

  • 函数声明和变量声明都会被提升,但是出现在有多个“重复”声明的代码中,函数会首先被提升,然后才是变量。
  • 函数没有方法重载,存在两个相同的函数名,后面的函数会覆盖前面的函数。
foo() // 3

function foo() {
    console.log(1)
}

var foo = function () {
    console.log(2)
}

function foo() {
    console.log(3)
}
复制代码

为什么呢?原因:

  1. 函数声明和变量声明,函数优先。
  2. 相同函数名字,后面函数覆盖前面的函数。

实际上以上代码可以看成这样:

function foo() {
    console.log(3)
}
var foo // 被忽略了

foo()
console.log(foo) // [Function: foo]
复制代码

参考链接


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

智慧社会

智慧社会

阿莱克斯·彭特兰 (Alex Pentland) / 汪小帆、汪容 / 浙江人民出版社 / 2015-4 / CNY 56.90

●如果要在大数据领域推举出一个代表性的科学家,阿莱克斯·彭特兰是一个无法令人忽略的名字。经过数年极具开创性的研究,社会物理学这个全新科学领域的根基已足够深厚。社会物理学是关于想法流的科学,正是在想法流的帮助下,我们才得以提高集体智能,促进智慧社会的形成。 ● 通过研究数以百万计的人在智能手机、GPS设备、互联网等地方留下的“数字面包屑”,大数据的应用已成为一股无法被忽视的力量。在大数据的应用......一起来看看 《智慧社会》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换