0、开场白

  在平时编写JavaScript代码时,我们并不会和执行上下文直接接触,但是想要彻底搞懂JavaScript函数的话,执行上下文是我们绕不过去的一个知识点。

1、执行上下文栈

  JavaScript在对一个函数的每次调用,都会创建一个执行上下文,然后基于这个执行上下文运行函数体内的代码。一个函数可能会创建无数的执行上下文,因为对函数的每次调用(即使在函数内部调用自己)都会创建一个具有新状态的上下文。

  当函数a执行的时候,会创建一个函数a的执行上下文,然后执行函数a中的代码,在函数a中调用另一个函数b的时候,当前a函数的执行会暂停下来,它的执行上下文也不会消失,然后创建一个函数b的执行上下文,执行函数b中的代码。从这个例子可以看出,JavaScript需要对函数的执行上下文进行有序的管理,有一种数据结构特别符合这种场景,它就是栈,所以JavaScript中管理执行上下文的就是执行上下文栈,有时也叫它调用栈。

2、执行上下文

  为什么调用函数的时候要创建一个执行上下文?因为JavaScript要保存一些函数调用时的信息,比如传入的参数、谁调用的。

  执行上下文是由JavaScript的一个内部实现,类似于一个简单的对象,这个对象拥有三个属性:变量对象,作用域链,this。下图展示了一个执行上下文的结构

  

3、变量对象

  变量对象是与执行上下文相关的数据作用域。它是一个与上下文相关的特殊对象,其中存储了在上下文中定义的变量和函数声明。变量对象是一个抽象概念。对于不同的上下文类型,使用不同的对象。比如,在全局上下文中变量对象就是全局对象本身;当函数被调用的时候,则会创建一个活动对象来作为变量对象使用。函数调用创建的活动对象,会比全局对象多一些属性,例如:形参、arguments。

4、作用域链  

  作用域链的作用就是用来查找自由变量的,如果一个变量在函数自身的作用域(活动对象)中没有找到,那么将会查找它外层函数的作用域(活动对象),以此类推。在函数体内没有定义,需要搜索作用域链的变量叫做自由变量。

  关于作用域链最关键的一条信息就是,在创建函数的时候会保存外层函数的作用域链,这个保存下来的作用域链会在将来函数调用时用来查找变量。在函数调用时,将当前函数的活动对象拼接在创建函数时保存的外层函数作用域链开头,然后保存在执行上下文的作用域链属性中,方便函数体内的自由变量进行变量查找。

5、闭包

  在计算机科学中,闭包,又称词法闭包或函数闭包,是引用了自由变量的函数。

  在JavaScript中,函数是第一级,就是说函数可以作为函数的参数、可以作为函数的返回值、可以赋值给变量。这是一个很强大的语言特性,不过它会引起两个问题:

  1. 当函数作为返回值时,这个函数内部的自由变量的解析问题。
  2. 当函数作为参数传递时,这个函数内部的自由变量的解析问题。

  接下来通过两段代码来说清楚这两个问题,先来看第一段代码,在全局环境中声明一个foo函数,foo函数里声明一个变量x等于10,然后返回一个函数,这个函数内部打印了x,执行foo函数,把返回值赋值给了back,在全局环境中声明一个变量x=20,此时执行back函数,会打印出来多少?

function foo() {
var x = 10;
return function() {
console.log(x);
};
}
var back = foo();
var x = 20;
back(); 

  要想知道打印出来的x等于多少,只需要知道变量x的查找顺序就行。back函数是定义在foo函数内部的,在定义的时候,会保存foo函数的作用域链,然后在back函数执行的时候,会把自己的活动对象拼接在foo函数作用域链的开头,所以变量x的查找顺序是back函数的活动对象,这是个空对象,没找到x,然后接着查找第二级,也就是foo函数的活动对象,里面有x,等于10,所以最后打印出来的结果是10。foo函数已经执行完了,它的执行上下文应该消失了,为什么还能找到foo函数执行时的活动对象呢?因为在JavaScript中,如果有外部引用使用了活动对象里的属性,这个活动对象并不会被回收掉。我画了张图帮助大家理解一下这个执行过程:

 

  第二段代码描述的是函数作为参数传递时的场景:

var x = 10;
function foo() {
console.log(x);
}
var b = function (foo) {
var x = 20;
foo();
}
b(foo);

  原理和上面一样,我也画了张图帮助大家理解一下:

  在JavaScript中,所有函数的执行都是基于这两种基础的模型的,无非就是变量多一些,嵌套深一些,查找的过程长一点,希望大家以后能看到代码时就能在脑海中呈现出代码的执行过程,变量的查找过程。

6、this

  执行上下文中还有最后一个this属性没讲,很多JavaScript初学者被this搞的晕头转向,这里我会帮大家理清楚this的所有情况,以后不用再怕this了。

  在JavaScript中,this是关键字,它不是变量,所以它不会参与到变量的解析过程,也就是说不会去查找作用域链。当JavaScript在执行代码时遇到this时,它会直接从执行上下文中拿到this的值,不用做任何查找。

  this的值只在创建执行上下文的时候进行确定,确定之后不会更改。可能有人会反驳,this指向可以修改的呀,那是在进入函数体执行代码之前修改的,进入函数体之后就不能修改了。

  在全局环境中,this的值就是全局对象。

  在函数中,this的值一共有四种情况:

  1. 作为构造函数调用时,this指向构造函数创建的对象
  2. 作为对象的方法调用时,指向该对象
  3. 作为函数调用时,指向全局对象,严格模式下为undefined
  4. call、apply、bind调用时,指向传入的第一个参数

  到此为止,和JavaScript函数执行上下文有关的知识点我都介绍完了,希望这篇文章能够帮你更深刻的认识JavaScript的函数。

  

  参考资料:

  https://www.ecma-international.org/publications/standards/Ecma-262.htm

  中文版ECMA规范

通俗易懂的来讲讲js的函数执行上下文的更多相关文章

  1. 进阶学习js中的执行上下文

    在js中的执行上下文,菜鸟入门基础 这篇文章中我们简单的讲解了js中的上下文,今天我们就更进一步的讲解js中的执行上下文. 1.当遇到变量名和函数名相同的问题. var a = 10; functio ...

  2. JS高阶---执行上下文

    1.代码分类 2.全局执行上下文 3.函数执行上下文 .

  3. javascript 函数执行上下文

    在js里,每个函数都有一个执行的上下文,我们可以通过this来访问. 如: 全局函数 function test(){ var local = this; } 我们发现local等于window(do ...

  4. JS进阶之---执行上下文,变量对象,变量提升

    一.结构顺序大体介绍 JavaScript代码的整个执行过程,分为两个阶段,代码编译阶段与代码执行阶段. 编译阶段由编译器完成,将代码翻译成可执行代码,这个阶段作用域规则会确定. 执行阶段由引擎完成, ...

  5. JS高阶---执行上下文栈

    大纲: 主体: 注意:*******函数调用时才会产生上下文栈,声明时不会产生********** 顺序: 概念图: 执行上下文栈的顺序---→后进先出 其他概念图: 当前执行的上下文总是在顶部 全局 ...

  6. JS中函数执行顺序的问题?

    作者:知乎用户链接:https://www.zhihu.com/question/23564807/answer/82996422来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注 ...

  7. js中的执行上下文,菜鸟入门基础。

    console.log(a); //Uncaught ReferenceError: a is not defined 因为没有定义a所以报错了. var a = 52; console.log(a) ...

  8. 一篇文章看懂JS执行上下文

     壹 ❀ 引 我们都知道,JS代码的执行顺序总是与代码先后顺序有所差异,当先抛开异步问题你会发现就算是同步代码,它的执行也与你的预期不一致,比如: function f1() { console.lo ...

  9. 深入学习JS执行--创建执行上下文(变量对象,作用域链,this)

    一.介绍 本篇继上一篇深入理解js执行--单线程的JS,这次我们来深入了解js执行过程中的执行上下文. 本篇涉及到的名词:预执行,执行上下文,变量对象,活动对象,作用域链,this等 二.预执行 在上 ...

随机推荐

  1. vscode设置

    通过ctrl+滚轮实现字体大小改变:在文件-->首选项-->设置-->用户设置中添加如下代码:"editor.mouseWheelZoom": true,

  2. css3 js 做一个旋转音乐播放开关

    我们经常会看到一些旋转音乐播放开关,今天我也写了一个分享出来,大家需要的话可以参考一下: <!DOCTYPE html> <html lang="en"> ...

  3. 【RL-TCPnet网络教程】第34章 RL-TCPnet之SMTP客户端

    第34章      RL-TCPnet之SMTP客户端 本章节为大家讲解RL-TCPnet的SMTP应用,学习本章节前,务必要优先学习第33章的SMTP基础知识.有了这些基础知识之后,再搞本章节会有事 ...

  4. [WEB]绕过安全狗与360PHP一句话的编写

    00x01安全狗的确是让人很头痛,尤其是在上传一句话或者写入一句话的时候,会被安全狗拦截从而拿不下shell.当然,安全狗是最简单的一款waf,很容易就进行一个绕过.00x02对于绕过安全狗跟360, ...

  5. [Swift]LeetCode424. 替换后的最长重复字符 | Longest Repeating Character Replacement

    Given a string that consists of only uppercase English letters, you can replace any letter in the st ...

  6. 如何在 Linux 上复制文件/文件夹到远程系统?

    从一个服务器复制文件到另一个服务器,或者从本地到远程复制是 Linux 管理员的日常任务之一. 我觉得不会有人不同意,因为无论在哪里这都是你的日常操作之一.有很多办法都能处理这个任务,我们试着加以概括 ...

  7. 心路历程(一)-自学java两个月心得

    这是我的第一条博文,在敲这些文字的时候我已经是一名大四的"老者".说自己"老者"确实如此,因为以前每当这个时候大一新学妹有上架了,哈哈,每当这个时候我们就想了很 ...

  8. Ocelot中使用Butterfly实践

    Ocelot(https://github.com/TomPallister/Ocelot)是一个用.net core实现的API网关,Butterfly(https://github.com/But ...

  9. Spring Cloud Eureka基本概述

    记一次Eureka的进一步学习. 一.Eureka简介 百科描述:Eureka是Netflix开发的服务发现框架,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均衡 ...

  10. cobbler批量安装系统使用详解-技术流ken

    前言 cobbler是一个可以实现批量安装系统的Linux应用程序.它有别于pxe+kickstart,cobbler可以实现同个服务器批量安装不同操作系统版本. 系统环境准备及其下载cobbler ...