Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

js没那么简单(2)--作用域链 #15

Open
Labels
@wython

Description

前言

作用域链是js的一个概念,和执行上下文相关。关于执行上下文的描述,在上一篇文章《js没那么简单(1)-- 执行上下文》已经说了。

讨论作用域链的意义在于:

  1. js的作用域关系和作用域链息息相关
  2. 作用域链是闭包的基础
  3. 作用域链对后面理解垃圾回收也有一定关系

静态vs动态

讨论作用域链之前,不妨先来搞几个头脑风暴。关于静态和动态,我们经常会听到,js是一门动态语言,我们也经常会听到,js是一门静态作用域语言,我们还经常听到,CMD是一种动态模块规范。于是乎,我们发现,静态和动态,似乎,在不同语义下,对同个语言描述也是不同的。

  1. 我们之所以说js是动态编程语言,是基于js的变量定义描述的,意思是,js可以在运行时变量类型是允许改变的。噢,很好理解,因为js的var确实支持运行时不同类型的改变。如一下代码:
var a = 1
a = 'string'

这是可以的。

  1. 我们之所以说js是静态作用域,是因为我们发现js的作用域好像不是在运行时定义。仿佛是在我们写好代码就定义好了。如以下代码:
var a = 2;
function back() {
	console.log(a);
}
function next() {
	var a = 3;
 back(); // 2 but not 3
}
next()

确实是:有点闭包那味,但是却和闭包不同。

  1. 我们之所以说CMD是动态模块规范,是因为require导入的模块,竟然能运行时改变。如以下代码:
let moduleAorB = null;
if () {
	moduleAorB = require('a')
} else {
	moduleAorB = require('b')
}

相反,举一反三,我们知道import必须在一开始导入,且不给更改,所以import是静态模块。

这时候。我们渐渐懂了静态和动态的含义了,他讲的是在一开始就定义,或者在运行时才确定的一个边界。那这个和我们这次要聊的作用域链有关系吗?是有关系的,因为js是静态作用域。

词法作用域

词法作用域的意思是:指在词法阶段确定的作用域,叫词法作用域。通过前面文章描述。我们知道词法阶段是编译原理一开始解析代码的阶段,所以词法解析意味着代码写完就确定了作用域,所以我们说词法作用域就是静态作用域。

所以我们前面的例子:

var a = 2;
function back() {
 console.log(a);
}
function next() {
 var a = 3;
 back(); // 2 but not 3
}
next()

结果之所以是2,而不是3 的原因。是因为back函数,在声明时就确定了其所在上级作用域是全局作用域。所以在执行时候,即使执行作用域是在next里面,依然先访问到自己作用域的上级作用域。这是因为他在词法阶段就确定了其上级作用域是全局作用域。

这是词法作用域的基本特性。

作用域链

再一次提到作用域链,那这一次。我们直接认为,作用域链是执行上下文里面的一个变量[[scope]]。而我们前面提到,执行上下文是在词法阶段确定的结构。所以作用域链也是在词法阶段确定的关系。

在v8源码中,scope链变量在词法解析被描述为如链表一般的结构,可以通过out_scope拿到该环境下的上一层作用域链。

在简单情况下。我们可以将其抽象的看成是执行上下文中的一个变量[[scope]],scope可以通过outer拿到上一层的作用域链。

那么假如一个声明在全局环境下的函数,其上下文环境应该如图所示:

作用域链最大的意义在于,定义环境所能访问的上级环境。进而会有后续闭包的说法。

var a = 2;
function back() {
 console.log(a);
}
function next() {
 var a = 3;
 back(); // 2 but not 3
}
next()

对于上面这段代码执行时的上下文顺序跟作用域顺序是不一样的,在next运行后,back也进入执行上下文,他们的上下文关系是这样的:

但是他们的作用域链关系是这样的:

也就是说,两个函数的直接上级作用域都是全局上下文,原因是因为他们都是声明在全局环境中。假如你希望next函数访问到back函数的上下文,你应该在back函数中声明next函数。那这个时候next称做back的闭包。关于闭包的细节,不仅仅在于我们这里简单去在函数中声明另一个函数。更在于在垃圾回收中,如何让闭包不被回收,是更值得探讨的问题。

另外,在浏览器控制台,可以通过打断点,看到当前所处点作用域链集合:

关于作用链,我的认识就是这样。the end

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

        AltStyle によって変換されたページ (->オリジナル) /