JS高程之执行环境与作用域

先开个坑坑,明天来码字。 (部分代码来自阮一峰的es6入门)

执行环境及作用域

执行环境(execution context)

执行环境决定了变量或者函数是否有权利访问其他数据和行为。每个执行环境都有一个与之关联的变量对象,环境中的定义的变量都核函数都保存在这个对象中。我们不能直接访问这个对象,但解析器在这个处理数据时会在后台使用她

  • 全局环境:是最外围的一个执行环境。在Web浏览器中全局执行环境被认为是window对象。

  • 执行环境:每个函数都有自己的执行环境,当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。
    当代码在一个执行环中执行时,会创建变量对象的一个作用域链

  • 作用域链的作用:保证对执行环境有权访问的所有变量核函数的有序访问

  • 作用域的前端始终都是当前执行的代码所在环境的变量对象。如果这个环境是函数则将其活动对象作为变量对象。活动对象最开始时是包含一个变量(arguments对象),作用域链中的下一个变量对象来自包含(外部)环境,而在下一个变量对象则来自下一个包含环境。一直延续到全局执行环境。全局执行对象始终都是作用域链中的最后一个对象。

  • 我的白话理解就是,你想知道这个人姓什么。可以直接问本人,他知道告诉你了就是相当于在执行环境。他没告诉你,你还可以问他爸爸姓什么(全局环境),还可以问他爷爷姓什么。同理这样一层层往上查。都不知道的话他可能就会说”不知道”,这时候就是我们说的变量未定义就访问了所以是undefined。

    延长作用域链接

    虽然执行环境的类型总共就两种 —— 全局和局部。但是我们还有其他办法来延长作用域链。

    try-catch

    1
    2
    3
    4
    5
    try {
    fun()
    } catch (err) {
    console.log('fun() have a error', err)
    }

这里的err就是临时增加的一个变量对象,改变量对象会在代码执行后被移除。

with (不建议使用)

不推荐使用with,在ECMAScript 5 严格模式中该标签已被禁止。推荐的代替方案是声明一个临时变量来承载你所需要的属性。
弊端:

  • with 语句在查找变量是,都是先在指定的对象中查找,所以本来不是在这个对象的属性变量,查找起来会很慢。
  • with 语句使得代码不易阅读
  • with 语句 无法向前兼容

    块级作用域

    在ES5之前没有块级作用域(自己的执行环境)。es6新增了let和const命令。

    let

    1
    2
    3
    4
    5
    6
    if (true) {
    var a = 1
    let b = 2
    }
    a // ReferenceError: a is not defined.
    b // 1

for 循环的计数器,就很适合使用 let。

1
2
3
4
5
6
7
8
for (let i = 0; i < 10; i++) {
// ...
}
console.log(i) // ReferenceError: i is not defined
for (var j; j < 10; j++) {
//logic code
}
console.log(j) // 10

let 不存在变量提升。var 命令会发生变量提升的现象,即:可以再声明之前使用,值是undefined。这种逻辑有点奇怪。所以 let 改变了语法行为。在let定义之前调用的话会返回一个错误“ReferenceError”(错误的引用)

暂时性死区 TDZ(temporal dead zone)

只要块级作用内有let命令,他所声明的变量就“绑定”这个作用域,不再受外部影响
下面这段代码,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。

1
2
3
4
5
6
// 代码来自阮一峰 es6
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}

不允许重复声明

let 不允许在相同的作用域内声明同一个变量。var 会覆盖上一个变量,这里有本质上的区别。应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。

1
2
3
4
5
6
7
8
9
10
11
12
function a () {
var b = 1
var b =2
return b
}
a() // 2
function c () {
let d = 1
let d =2
return b
}
c() // Identifier 'd' has already been declared

const

  • const 声明一个只读变量。一旦声明,常量的值就不能改变。
  • 不能改变意味着声明必须初始化赋值。
  • 与 let 一样存在暂时性死区。
  • 不可重复声明,只在块级作用域内有效 。

本质:const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。

  • 对于简单类型的数据,值就是保存在变量指向的那个内存地址,因此等同常量。
  • 对于复合类型的数据,变量指向的内存地址,保存的只是一个指向实际数据的指针。const 只能保证这个指针是股东的,至于他只想的数据结构是不是可变的,就完全不能控制了。
    1
    2
    3
    4
    5
    const foo = {};
    // 为 foo 添加一个属性,可以成功
    foo.prop = 123
    foo.prop // 123
    foo = {}; // TypeError: "foo" is read-only

感谢您的阅读。 🙏