原文出处,而栈顶就是当前正在执行的上下文

前端基础进阶(二):执行上下文详细图解

2017/02/21 · 基础技术 · 执行上下文

原文出处: 波同学   

图片 1

先随便放张图

我们在JS学习初期或者面试的时候常常会遇到考核变量提升的思考题。比如先来一个简单一点的。

JavaScript

console.log(a); // 这里会打印出什么? var a = 20;

1
2
console.log(a);   // 这里会打印出什么?
var a = 20;

暂时先不管这个例子,我们先引入一个JavaScript中最基础,但同时也是最重要的一个概念执行上下文(Execution Context)

每次当控制器转到可执行代码的时候,就会进入一个执行上下文。执行上下文可以理解为当前代码的执行环境,它会形成一个作用域。JavaScript中的运行环境大概包括三种情况。

  • 全局环境:JavaScript代码运行起来会首先进入该环境
  • 函数环境:当函数被调用执行时,会进入当前函数中执行代码
  • eval

因此在一个JavaScript程序中,必定会产生多个执行上下文,在我的上一篇文章中也有提到,JavaScript引擎会以堆栈的方式来处理它们,这个堆栈,我们称其为函数调用栈(call stack)。栈底永远都是全局上下文,而栈顶就是当前正在执行的上下文。

当代码在执行过程中,遇到以上三种情况,都会生成一个执行上下文,放入栈中,而处于栈顶的上下文执行完毕之后,就会自动出栈。为了更加清晰的理解这个过程,根据下面的例子,结合图示给大家展示。

JavaScript

var color = 'blue'; function changeColor() { var anotherColor = 'red'; function swapColors() { var tempColor = anotherColor; anotherColor = color; color = tempColor; } swapColors(); } changeColor();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var color = 'blue';
 
function changeColor() {
    var anotherColor = 'red';
 
    function swapColors() {
        var tempColor = anotherColor;
        anotherColor = color;
        color = tempColor;
    }
 
    swapColors();
}
 
changeColor();

我们用ECStack来表示处理执行上下文组的堆栈。我们很容易知道,第一步,首先是全局上下文入栈。

图片 2

第一步:全局上下文入栈

全局上下文入栈之后,其中的可执行代码开始执行,直到遇到了changeColor(),这一句激活函数changeColor创建它自己的执行上下文,因此第二步就是changeColor的执行上下文入栈。

图片 3

第二步:changeColor的执行上下文入栈

changeColor的上下文入栈之后,控制器开始执行其中的可执行代码,遇到swapColors()之后又激活了一个执行上下文。因此第三步是swapColors的执行上下文入栈。

图片 4

第三步:swapColors的执行上下文入栈

在swapColors的可执行代码中,再没有遇到其他能生成执行上下文的情况,因此这段代码顺利执行完毕,swapColors的上下文从栈中弹出。

图片 5

第四步:swapColors的执行上下文出栈

swapColors的执行上下文弹出之后,继续执行changeColor的可执行代码,也没有再遇到其他执行上下文,顺利执行完毕之后弹出。这样,ECStack中就只身下全局上下文了。

图片 6

第五步:changeColor的执行上下文出栈

全局上下文在浏览器窗口关闭后出栈。

注意:函数中,遇到return能直接终止可执行代码的执行,因此会直接将当前上下文弹出栈。

图片 7

整个过程

详细了解了这个过程之后,我们就可以对执行上下文总结一些结论了。

  • 单线程
  • 同步执行,只有栈顶的上下文处于执行中,其他上下文需要等待
  • 全局上下文只有唯一的一个,它在浏览器关闭时出栈
  • 函数的执行上下文的个数没有限制
  • 每次某个函数被调用,就会有个新的执行上下文为其创建,即使是调用的自身函数,也是如此。

为了巩固一下执行上下文的理解,我们再来绘制一个例子的演变过程,这是一个简单的闭包例子。

JavaScript

function f1(){ var n=999; function f2(){ alert(n); } return f2; } var result=f1(); result(); // 999

1
2
3
4
5
6
7
8
9
function f1(){
    var n=999;
    function f2(){
        alert(n);
    }
    return f2;
}
var result=f1();
result(); // 999

因为f1中的函数f2在f1的可执行代码中,并没有被调用执行,因此执行f1时,f2不会创建新的上下文,而直到result执行时,才创建了一个新的。具体演变过程如下。

图片 8

上例演变过程

下一篇文章继续总结执行上下文的创建过程与变量对象,求持续关注与点赞,谢谢大家。

前端基础进阶系列目录

前端基础进阶系列我会持续更新,欢迎大家关注我公众号isreact,新的文章更新了我会在公众号里第一时间通知大家。也欢迎大家来简书关注我。

1 赞 2 收藏 评论

图片 9

函数中执行到return语句会终止可执行代码的执行,将当前上下文弹出栈

我们在JS学习初期或者面试的时候常常会遇到考核变量提升的思考题。比如先来一个简单一点的。

这里函数对象本身是匿名的,是作为person对象的一个属性,当作为对象属性调用时,this指向了对象,当把这个函数赋给另一个函数然后调用时,是作为一般函数调用的,this指向了全局对象。这个例子充分说明了“函数作为对象的方法调用时内部属性this指向这个调用对象,函数作为一般函数调用时内部属性this指向全局对象”,也说明了this的指定是动态的,是在调用时指定的,而不管函数是单独定义的还是作为对象方法定义的。也正是因为函数作为对象的方法调用时this指向这个调用对象,所以在函数内部返回this时才能够延续调用对象的下一个方法——也就是链式操作(jQuery的一大特色)。

// 全局上下文进栈
var color = 'blue';
function changeColor(){
var anotherColor = 'red';
function swapColors(){
var tempColor = anotherColor;
antherColor = color;
color = tempColor;
}
// swapColors进栈
swapColors();
// swapColors出栈
}
// changeColor进栈
changeColor();
// changeColor出栈

整个过程

(1)进入全局环境,创建全局变量对象,将全局环境压入栈顶(这里也是栈底)。根据前面的关于声明提升的结论,这里创建全局变量对象可能的一个过程是,先创建全局变量对象,然后处理函数声明设置属性changeColor为相应函数,再处理变量声明设置属性color为undefined。

js中的运行环境简单分类
全局环境,执行js代码就会进入该环境
函数环境,函数被调用就会进入该环境
eval谁调用此方法,this就指向该对象

注意:函数中,遇到return能直接终止可执行代码的执行,因此会直接将当前上下文弹出栈。

3、caller属性主要和函数的caller相区分,值永远都是undefined

在栈中,栈底永远都是全局上下文,栈顶是正在执行的上下文

图片 10

(6)执行swapColors()函数代码。先给tempColor初始化赋值,然后实现值交换功能(这里color和anotherColor的值都是沿着作用域链才读取到的)。

单线程中,
同步执行,栈顶上下文处于执行,其他等待
全局上下文只有一个,在浏览器关闭时出栈。
执行上下文个数没限制
函数被调用就有新上下文创建

  • 全局环境:JavaScript代码运行起来会首先进入该环境
  • 函数环境:当函数被调用执行时,会进入当前函数中执行代码
  • eval

bind

原文参考

swapColors的执行上下文弹出之后,继续执行changeColor的可执行代码,也没有再遇到其他执行上下文,顺利执行完毕之后弹出。这样,ECStack中就只身下全局上下文了。

类别

因为f1中的函数f2在f1的可执行代码中,并没有被调用执行,因此执行f1时,f2不会创建新的上下文,而直到result执行时,才创建了一个新的。具体演变过程如下。

为了巩固一下执行上下文的理解,我们再来绘制一个例子的演变过程,这是一个简单的闭包例子。

**7、函数内部对象与this

第五步:changeColor的执行上下文出栈

需要注意区分一下函数执行环境中定义的属性(也即活动对象的属性)和this对象的属性,在使用数组元素方式调用函数时,函数内部this指向数组本身,因此上例最后输出undefined。

每次当控制器转到可执行代码的时候,就会进入一个执行上下文。执行上下文可以理解为当前代码的执行环境,它会形成一个作用域。JavaScript中的运行环境大概包括三种情况。

和一般面向对象语言有很大区别

我们用ECStack来表示处理执行上下文组的堆栈。我们很容易知道,第一步,首先是全局上下文入栈。

changeColor();
/*
* 全局作用域内可以访问的对象有:
* 全局变量对象的color,changeColor
*/

图片 11

第三步:swapColors的执行上下文入栈

对于作用域链,还可以使用with、try-catch语句的catch块来延长:

var color = 'blue';

function changeColor() {
    var anotherColor = 'red';

    function swapColors() {
        var tempColor = anotherColor;
        anotherColor = color;
        color = tempColor;
    }

    swapColors();
}

changeColor();

图片 12

值永远是Function,也就是内置的函数Function()

因此在一个JavaScript程序中,必定会产生多个执行上下文,在我的上一篇文章中也有提到,JavaScript引擎会以堆栈的方式来处理它们,这个堆栈,我们称其为函数调用栈(call stack)。栈底永远都是全局上下文,而栈顶就是当前正在执行的上下文。

第1个是正常输出,第2个(object.getName)与object.getName的效果是相同的,而第3个(object.getName=object.getName)最终返回的是函数对象本身,也就是说第3个会作为一般函数来调用,第4个则先是调用getNameFunc这个方法,返回一个函数,然后再调用这个函数,也是作为一般函数来调用。

第四步:swapColors的执行上下文出栈

console.info(object.getName());//My Object
console.info((object.getName)());//My Object
console.info((object.getName = object.getName)());//The Window
console.info(object.getNameFunc()());//The Window

图片 13

var name = 'linjisong';
function fn(){
return this.name;
}
var person = {
name:'oulinhai',
getName:fn
};
var person2 = {name:'hujinxing'};
var person3 = {name:'huanglanxue'};
console.info(fn());//linjisong,一般函数调用,内部属性this指向全局对象,因此this.name返回linjisong
console.info(person.getName());//oulinhai,作为对象方法调用,this指向这个对象,因此这里返回person.name
console.info(fn.apply(person2));//hujinxing,使用apply、call或bind调用函数,执行传入的第一个参数对象,因此返回person2.name
console.info(fn.call(person2));//hujinxing
var newFn = fn.bind(person3);//ES5中新增方法,会创建一个新函数实例返回,内部this值被指定为传入的参数对象
console.info(newFn());//huanglanxue

全局上下文在浏览器窗口关闭后出栈。

直接继承自Object类型的方法,用法同Object

详细了解了这个过程之后,我们就可以对执行上下文总结一些结论了。

function fn(){
var fnScope = 'a';

全局上下文入栈之后,其中的可执行代码开始执行,直到遇到了changeColor(),这一句激活函数changeColor创建它自己的执行上下文,因此第二步就是changeColor的执行上下文入栈。

  光从概念上区分是非常抽象的,也不是那么容易理解,再把这些属性列在一起比较一下(没有列入一些非标准的属性,如name):

function f1(){
    var n=999;
    function f2(){
        alert(n); 
    }
    return f2;
}
var result=f1();
result(); // 999

上面示例中列出的都是一些常见情况,没有列出第一个参数为null或undefined的情况,有兴趣的朋友可以自行测试。关于this值的确定,在原书中还有一个例子:

学习过程中遇到什么问题或者想获取学习资源的话,欢迎加入学习交流群
343599877,我们一起学前端!

虽然函数一定义就可访问,但是不在函数体内访问时永远为null,在函数体内访问时返回调用当前函数的函数,在全局作用域中调用函数也会返回null

图片 14

调用函数自身,以列举方式接受参数

在swapColors的可执行代码中,再没有遇到其他能生成执行上下文的情况,因此这段代码顺利执行完毕,swapColors的上下文从栈中弹出。

•使用with(obj){}语句时,将obj对象添加到当前作用域链的最前端。
•使用try{}catch(error){}语句时,将error对象添加到当前作用域链的最前端。
  插了一段较为抽象的概念,希望不至于影响整个阅读的流畅,事实上,我在这里还悄悄的绕过了一个称为“闭包”的概念,关于函数与闭包,在下篇文章中再详细叙述。

第二步:changeColor的执行上下文入栈

  fn.arguments.callee === fn

console.log(a);   // 这里会打印出什么?
var a = 20;

propertyIsEnumerable

图片 15

关于作用域和声明提升,再看一个例子:

  • 单线程
  • 同步执行,只有栈顶的上下文处于执行中,其他上下文需要等待
  • 全局上下文只有唯一的一个,它在浏览器关闭时出栈
  • 函数的执行上下文的个数没有限制
  • 每次某个函数被调用,就会有个新的执行上下文为其创建,即使是调用的自身函数,也是如此。

•使用apply()、call()或bind()调用函数时,this指向第一个参数对象。如果没有传入参数或传入的是null和undefined,this指向全局对象(在ES5的严格模式下会设为null)。如果传入的第一个参数是一个简单类型,会将this设置为相应的简单类型包装对象。

暂时先不管这个例子,我们先引入一个JavaScript中最基础,但同时也是最重要的一个概念执行上下文(Execution Context)。

覆盖了Object类型中的方法,返回函数体

第一步:全局上下文入栈

arguments本身也有自己的属性:length、callee和caller

changeColor的上下文入栈之后,控制器开始执行其中的可执行代码,遇到swapColors()之后又激活了一个执行上下文。因此第三步是swapColors的执行上下文入栈。

复制代码 代码如下:

当代码在执行过程中,遇到以上三种情况,都会生成一个执行上下文,放入栈中,而处于栈顶的上下文执行完毕之后,就会自动出栈。为了更加清晰的理解这个过程,根据下面的例子,结合图示给大家展示。

this

上例演变过程

(1)函数内部属性:可以理解为函数相应的活动对象的属性,是只能从函数体内部访问的属性,函数每一次被调用,都会被重新指定,具有动态性。

图片 16

函数原型对象

本文由必威发布于必威-前端,转载请注明出处:原文出处,而栈顶就是当前正在执行的上下文

相关阅读