通俗易懂的来讲讲js的函数执行上下文
0、开场白
在平时编写JavaScript代码时,我们并不会和执行上下文直接接触,但是想要彻底搞懂JavaScript函数的话,执行上下文是我们绕不过去的一个知识点。
1、执行上下文栈
JavaScript在对一个函数的每次调用,都会创建一个执行上下文,然后基于这个执行上下文运行函数体内的代码。一个函数可能会创建无数的执行上下文,因为对函数的每次调用(即使在函数内部调用自己)都会创建一个具有新状态的上下文。
当函数a执行的时候,会创建一个函数a的执行上下文,然后执行函数a中的代码,在函数a中调用另一个函数b的时候,当前a函数的执行会暂停下来,它的执行上下文也不会消失,然后创建一个函数b的执行上下文,执行函数b中的代码。从这个例子可以看出,JavaScript需要对函数的执行上下文进行有序的管理,有一种数据结构特别符合这种场景,它就是栈,所以JavaScript中管理执行上下文的就是执行上下文栈,有时也叫它调用栈。
2、执行上下文
为什么调用函数的时候要创建一个执行上下文?因为JavaScript要保存一些函数调用时的信息,比如传入的参数、谁调用的。
执行上下文是由JavaScript的一个内部实现,类似于一个简单的对象,这个对象拥有三个属性:变量对象,作用域链,this。下图展示了一个执行上下文的结构

3、变量对象
变量对象是与执行上下文相关的数据作用域。它是一个与上下文相关的特殊对象,其中存储了在上下文中定义的变量和函数声明。变量对象是一个抽象概念。对于不同的上下文类型,使用不同的对象。比如,在全局上下文中变量对象就是全局对象本身;当函数被调用的时候,则会创建一个活动对象来作为变量对象使用。函数调用创建的活动对象,会比全局对象多一些属性,例如:形参、arguments。
4、作用域链
作用域链的作用就是用来查找自由变量的,如果一个变量在函数自身的作用域(活动对象)中没有找到,那么将会查找它外层函数的作用域(活动对象),以此类推。在函数体内没有定义,需要搜索作用域链的变量叫做自由变量。
关于作用域链最关键的一条信息就是,在创建函数的时候会保存外层函数的作用域链,这个保存下来的作用域链会在将来函数调用时用来查找变量。在函数调用时,将当前函数的活动对象拼接在创建函数时保存的外层函数作用域链开头,然后保存在执行上下文的作用域链属性中,方便函数体内的自由变量进行变量查找。
5、闭包
在计算机科学中,闭包,又称词法闭包或函数闭包,是引用了自由变量的函数。
在JavaScript中,函数是第一级,就是说函数可以作为函数的参数、可以作为函数的返回值、可以赋值给变量。这是一个很强大的语言特性,不过它会引起两个问题:
- 当函数作为返回值时,这个函数内部的自由变量的解析问题。
- 当函数作为参数传递时,这个函数内部的自由变量的解析问题。
接下来通过两段代码来说清楚这两个问题,先来看第一段代码,在全局环境中声明一个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的值一共有四种情况:
- 作为构造函数调用时,this指向构造函数创建的对象
- 作为对象的方法调用时,指向该对象
- 作为函数调用时,指向全局对象,严格模式下为undefined
- call、apply、bind调用时,指向传入的第一个参数
到此为止,和JavaScript函数执行上下文有关的知识点我都介绍完了,希望这篇文章能够帮你更深刻的认识JavaScript的函数。
参考资料:
https://www.ecma-international.org/publications/standards/Ecma-262.htm
通俗易懂的来讲讲js的函数执行上下文的更多相关文章
- 进阶学习js中的执行上下文
在js中的执行上下文,菜鸟入门基础 这篇文章中我们简单的讲解了js中的上下文,今天我们就更进一步的讲解js中的执行上下文. 1.当遇到变量名和函数名相同的问题. var a = 10; functio ...
- JS高阶---执行上下文
1.代码分类 2.全局执行上下文 3.函数执行上下文 .
- javascript 函数执行上下文
在js里,每个函数都有一个执行的上下文,我们可以通过this来访问. 如: 全局函数 function test(){ var local = this; } 我们发现local等于window(do ...
- JS进阶之---执行上下文,变量对象,变量提升
一.结构顺序大体介绍 JavaScript代码的整个执行过程,分为两个阶段,代码编译阶段与代码执行阶段. 编译阶段由编译器完成,将代码翻译成可执行代码,这个阶段作用域规则会确定. 执行阶段由引擎完成, ...
- JS高阶---执行上下文栈
大纲: 主体: 注意:*******函数调用时才会产生上下文栈,声明时不会产生********** 顺序: 概念图: 执行上下文栈的顺序---→后进先出 其他概念图: 当前执行的上下文总是在顶部 全局 ...
- JS中函数执行顺序的问题?
作者:知乎用户链接:https://www.zhihu.com/question/23564807/answer/82996422来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注 ...
- js中的执行上下文,菜鸟入门基础。
console.log(a); //Uncaught ReferenceError: a is not defined 因为没有定义a所以报错了. var a = 52; console.log(a) ...
- 一篇文章看懂JS执行上下文
壹 ❀ 引 我们都知道,JS代码的执行顺序总是与代码先后顺序有所差异,当先抛开异步问题你会发现就算是同步代码,它的执行也与你的预期不一致,比如: function f1() { console.lo ...
- 深入学习JS执行--创建执行上下文(变量对象,作用域链,this)
一.介绍 本篇继上一篇深入理解js执行--单线程的JS,这次我们来深入了解js执行过程中的执行上下文. 本篇涉及到的名词:预执行,执行上下文,变量对象,活动对象,作用域链,this等 二.预执行 在上 ...
随机推荐
- mysql 主从设置
方法: 1.主服务器建立二进制日志,每产生语句或磁盘变化,写进日志 2.从服务器建立 relaylog日志 3.主服务器授权复制账号 4.从服务器利用复制账号来监听主服务器的日志 5.注意:所以的my ...
- 将选中的物体写入XML文件
using System.Collections;using System.Collections.Generic;using System.Xml.Linq;using UnityEditor;us ...
- navicat实现Mysql数据备份
方法/步骤 使用navicat工具连接mysql数据库,这里以navicat for Mysql工具为例.如果数据库在本机,那么连接ip处写localhost即可,如果数据库在其他机器,那需要 ...
- 高级Java面试总结3
1,java堆,分新生代老年代,新生代有Eden,from surviver,to surviver三个空间,堆被所有线程共.eden内存不足时,发生一次minor GC,会把from survivo ...
- 【Nim游戏】高僧斗法
先来看看Nim定理: // 若干堆硬币,二人轮流取,从一堆硬币中取几个 直到某个人不能取硬币 那这个人就输了 // 3 4 5 // 3 3 把硬币变成相同的 那么你就赢了 因为你可以跟着另一个人一样 ...
- Android OpenGL ES 开发(四): OpenGL ES 绘制形状
在上文中,我们使用OpenGL定义了能够被绘制出来的形状了,现在我们想绘制出来它们.使用OpenGLES 2.0来绘制形状会比你想象的需要更多的代码.因为OpenGL的API提供了大量的对渲染管线的控 ...
- [Swift]LeetCode817. 链表组件 | Linked List Components
We are given head, the head node of a linked list containing unique integer values. We are also give ...
- [Swift]LeetCode862. 和至少为 K 的最短子数组 | Shortest Subarray with Sum at Least K
Return the length of the shortest, non-empty, contiguous subarray of A with sum at least K. If there ...
- Node.js 种子下载器
Node.js 种子下载器 庆祝 2018 国庆,制作了一个 Node.js 的种子下载器.爬取页面,根据页面的链接,破解另外一个网站,下载种子文件.项目比较简单,爬取页面没有使用任何爬虫框架.项目源 ...
- Qt中OpenGL模块下将图片转化为纹理,并传入shader中
QImage texture, buffer; buffer.load("C:/Users/wukesong/Pictures/flower.jpg"); texture = QG ...