JavaScript难点系列(五):执行上下文

来源:互联网 发布:mysql sys refcursor 编辑:程序博客网 时间:2024/05/16 02:19

深入了解js这门语言后,才发现它有着诸多众所周知的难点(例如:闭包、原型链、内存空间等)。有的是因为js的设计缺陷导致的,而有的则是js的优点。不管如何,总需要去学会它们,在学习过程中我觉得只看别人的文章并不能做到深刻理解,所以我决定写这一系列的文章来记录我所学习到的知识点,也方便自己以后回顾,如有写错的地方欢迎指正。 废话不多说,马上进入正题!

顺序执行

每个人刚学JS时都会知道JS执行代码时是顺序执行的,即从上到下执行。但看下面这段代码你就会发现事情并不是这么简单。

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

foo() // 2

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

foo() // 2
两次执行foo函数时打印的结果都是2。这是因为JS在执行代码时是一段一段地顺序执行,而不是一行一行地执行。即当JS拿到某一段代码时,它会事先地做一些准备工作,然后再一行一行地去执行。

准备工作

所谓的准备工作有两件事,我们分别通过代码来说明。
1、

console.log(f1)  // function f1() {}function f1() {}  // 函数声明

上面的代码说明JS在准备工作阶段会提前声明函数,即把函数都声明并赋值好了再来执行代码。

2、

console.log(a)  // a is not definedconsole.log(a)  // undefinedconsole.log(f1)  // undefinedvar a = 10var f1 = function() {}

分析上面的代码,我们发现JS做的一种准备工作就是声明变量但不进行赋值操作。容易让人混淆的是函数表达式,它和函数声明不同,仅仅是像变量a一样被声明但未被赋值。

3、
JS在准备阶段还会做一件事情,就是给this赋值。至于this在不同环境下是什么值,可以参考之前的文章JavaScript难点系列(二):this。

执行上下文

执行上下文(也称执行上下文环境)就是在准备阶段产生的。在全局环境下,执行上下文有以下的数据:

  • 未赋值的变量(包括函数表达式)
  • 已赋值的this
  • 已赋值的函数声明

而在函数作用域环境下,会多出参数、arguments变量、作用域链中的变量 这三种数据内容,而且它们都是被赋值的。函数每被调用一次就会产生一个新的执行上下文环境,因为不同的调用可能会产生不同的参数。

综上,用通俗的话给执行上下文下个定义就是:执行代码前把要用到的所有变量都先声明好,并且给其中的一些变量先赋值。

理解了执行上下文后,就出现一个新的问题:在执行代码时由于函数嵌套等原因会有很多次函数调用,从而产生了很多的执行上下文环境,那么这些执行上下文环境又应该如何管理?何时去销毁(或保存)?

执行上下文栈

针对上面提到的问题,JS会维护一个执行上下文栈(也称函数调用栈)来解决。栈底永远是全局上下文环境,而栈顶就是正在执行的上下文,因此在某个时间点只能有一个上下文环境在执行。
执行上下文栈工作的机制就是:每次函数调用(包括调用函数本身)都会产生执行上下文环境,并压入栈中;当函数调用完成时,这个上下文环境会出栈并销毁(不考虑闭包的情况)。
我们通过一段代码来具体地说明:

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

首先是全局环境入栈:

global

然后调用foo函数,foo入栈

foo
global

foo函数中再调用bar函数,bar入栈

bar
foo
global

bar函数执行完毕后bar出栈

foo
global

foo函数执行完毕后foo出栈

global

最后关闭浏览器时全局环境出栈。

闭包

上面提到,函数在执行完毕后其执行上下文就会出栈且被销毁。但有一种情况是例外的,就是闭包。它能阻止它所包含的变量所在的那个执行上下文被销毁。这句话有点绕,需要多读几遍。引用JS高程里对闭包的定义:

闭包是指有权访问另一个函数作用域中的变量的函数
来看一个例子:

function foo() {    var a = 10    return function bar() {        console.log(a)    }}var fn1 = foo()var a = 100fn1()  // 10

上面代码中当 var fn1 = foo() 执行完后,按照我们前面的分析,这时候应该销毁掉foo函数的执行上下文。但这里foo函数的执行上下文只是弹出执行上下文栈,并不会被销毁,会被保存在内存中。这样调用fn1函数(即bar函数)时,可以在内存中找到变量a的值

所以使用闭包会使类似foo执行上下文这类本应该被销毁的,却没有被销毁,从而增加了内存开销。

阅读全文
0 0
原创粉丝点击