js基础梳理-如何理解作用域和作用域链?

本文重点是要梳理执行上下文的生命周期中的建立作用域链,在此之前,先回顾下关于作用域的一些知识。

1.什么是作用域(scope)?

在《JavaScritp高级程序设计》中并没有找到确切的关于作用域的定义,只是在“4.2执行环境及作用域”中简单说了下执行环境(execution context)的概念。而执行环境其实就是之前博客:js基础梳理-究竟什么是执行上下文栈(执行栈),执行上下文(可执行代码)?中的执行上下文。

而在《JavaScript权威指南》中,对作用域的描述为:

变量作用域:一个变量的作用域(scope)是程序源代码中定义这个变量的区域

在《你不知道的Javascript·上卷》中对作用域的描述则为:

负责收集并维护由所有生命的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。

简单来讲,作用域(scope)就是变量访问规则的有效范围

作用域外,无法引用作用域内的变量;

离开作用域后,作用域的变量的内存空间会被清除,比如执行完函数或者关闭浏览器

作用域与执行上下文是完全不同的两个概念。我曾经也混淆过他们,但是一定要仔细区分。

JavaScript代码的整个执行过程,分为两个阶段,代码编译阶段与代码执行阶段。编译阶段由编译器完成,将代码翻译成可执行代码,这个阶段作用域规则会确定。执行阶段由引擎完成,主要任务是执行可执行代码,执行上下文在这个阶段创建。

说得很深奥的样子,其实上面这段话重点用函数作用域与函数执行上下文来区分是最好不过的了。函数作用域是在函数声明的时候就已经确定了,而函数执行上下文是在函数调用时创建的。假如一个函数被调用多次,那么它就会创建多个函数执行上下文,但是函数作用域显然不会跟着函数被调用的次数而发生什么变化。

1.1 全局作用域 var foo = 'foo'; console.log(window.foo); // => 'foo'

在浏览器环境中声明变量,该变量会默认成为window对象下的属性。

function foo() { name = "bar" } foo(); console.log(window.name) // bar

在函数中,如果不加 var 声明一个变量,那么这个变量会默认被声明为全局变量,如果是严格模式,则会报错。

全局变量会造成命名污染,如果在多处对同一个全局变量进行操作,那么久会覆盖全局变量的定义。同时全局变量数量过多,非常不方便管理。

这也是为什么jquery要在全局建立\(变量,其余私有方法属性挂在\)下的原因。

1.2 函数作用域

假如在函数中定义一个局部变量,那么该变量只可以在该函数作用域中被访问。

function doSomething () { var thing = '吃早餐'; } console.log(thing); // Uncaught ReferenceError: thing is not defined

嵌套函数作用域:

function outer () { var thing = '吃早餐'; function inner () { console.log(thing); } inner(); } outer(); // 吃早餐

在外层函数中,嵌套一个内层函数,那么这个内层函数可以向上访问到外层函数中的变量。

既然内层函数可以访问到外层函数的变量,那如果把内层函数return出来会怎样?

function outer () { var thing = '吃早餐'; function inner () { console.log(thing); } return inner; } var foo = outer(); foo(); // 吃早餐

前面提到,函数执行完后,函数作用域的变量就会被垃圾回收。而这段代码看出当返回了一个访问了外部函数变量的内部函数,最后外部函数的变量得以保存。

这种当变量存在的函数已经执行结束,但扔可以再次被访问到的方式就是“闭包”。后期会继续对闭包进行梳理。

1.3 块级作用域

很多书上都有一句话,javascript没有块级作用域的概念。所谓块级作用域,就是{}包裹的区域。但是在ES6出来以后,这句话并不那么正确了。因为可以用 let 或者 const 声明一个块级作用域的变量或常量。

比如:

for (let i = 0; i < 10; i++) { // ... } console.log(i); // Uncaught ReferenceError: i is not defined

发现这个例子就会和函数作用域中的第一个例子一样的错误提示。因为变量i只可以在 for循环的{ }块级作用域中被访问了。

扩散思考:

究竟什么时候该用let?什么时候该用const?

默认使用 const,只有当确实需要改变变量的值的时候才使用let。因为大部分的变量的值在初始化之后不应再改变,而预料之外的变量的修改是很多bug的源头。

1.4 词法作用域

词法作用域,也可以叫做静态作用域。意思是无论函数在哪里调用,词法作用域都只在由函数被声明时所处的位置决定。
既然有静态作用域,那么也有动态作用域。
而动态作用域的作用域则是由函数被调用时执行的位置所决定。

var a = 123; function fn1 () { console.log(a); } function fn2 () { var a = 456; fn1(); } fn2(); // 123

以上代码,最后输出结果 a 的值,来自于 fn1 声明时所在位置访问到的 a 值 123。
所以JS的作用域是静态作用域,也叫词法作用域。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpgzww.html