一段代码带你理解js执行上下文的工作流程

原文链接,欢迎关注我的博客 我相信很多前端初学者一开始都会被执行上下文这个概念弄晕,或者说似懂非懂。对于工作两年的我来说,说来实在惭愧,虽然知道它大概是什么,但总觉得没有一个更为清晰的认识(无法把它的工作过程描述清楚),因此最近特意温习了一遍,写下了这篇文章 执行上下文要说清它的大体工作流程,需要提前说明三个基本概念,分别是thread of exection(线程)、variable envirnoment(变量环境)、call Stack(调用栈),这些概念我们或多或少接触过,接下来我会通过一段示例代码,和一系列图片,进一步解释这三个概念在执行上下文的运作流程。 一段代码const num = 2;function addOne(input) { const output = input + 1; return output;}const result = addOne(2);这段代码做了什么在运行上面这些代码前,js 引擎做的第一件是就是创建一个global execution context,也就是全局执行上下文: 先看图中的黑色箭头,它表示线程thread的执行顺序,众所周知 js 是单线程的,它会一行行、从上往下去执行代码;而右边的global memory,它用于存储当前上下文中的数据,由于线程目前处于全局上下文环境,故加了个global的前缀。 在这段代码中,第一行我们声明了一个名为num的不可变变量,并赋值为4, 因此global memory中就会分配内存,存储这个变量: 接着继续,当线程执行到第二行时,问题就来了:我们创建了一个addOne的变量,并把一个函数赋值于它,那在global memory里,到底存的是个啥?为了解答这个问题,我特意打印了一下: function addOne(input) { const output = input + 1; return output;}console.log(addOne);看,我们竟然把函数里的内容完完整整打印出来了,很明显,它存的是一个函数内部的“文本信息”。 其实很容易理解,当执行第二行的时候,该函数并没有被调用,因此线程不会立刻解析里面的内容,而是把它内部的信息以“文本内容”的形式保存下来,当需要执行的时候,才去解析变量里的函数内容,这也很好地解析了为什么函数内的异常仅会在函数被调用时才抛出来。 因此这时global execution context长这样: 由于addOne里保存的是函数内容,目前对于线程而言它是未知的,因此我们这里特意用一个带有输入输出箭头的函数图标,代表它是一个未被解析的函数。 我们继续执行第三步:还是创建了一个变量result,但此时它被赋予undefined,因为线程暂时无法从addOne这个函数里获知它的返回值。 由于addOne函数被调用了,线程会从刚刚保存的addOne变量中取出内容,去解析、执行它。这时 js 就创建了一个新的执行上下文——local execution context,即当前的执行上下文,这是一个船新的上下文,因此我特意用一个新图片去描述它: 首先这个memory我加了个local前缀,表面当前存储的都是此上下文中的变量数据。无论是上述的global memory,亦或是现在、或未来的local memory,我们可以用一个更为专业的术语variable envirnoment去描述这种存储环境。 此外,这个黑箭头我特意画了个拐角,意味它是从全局上下文进来的,local memory首先会分配内存给变量input,它在调用时就被2赋值了,紧接着又创建了一个output标签并把计算结果3赋值给它。最后,当线程遇到离开上下文的标识——return,便离开上下文,并把ouput的结果一并返回出去。 此时,这个上下文就没用了(被执行完了),于是乎垃圾回收便盯上了它,选择一个恰当的时机把里面的local memory删光光。 ...

July 13, 2019 · 1 min · jiezi

js深入二函数的执行与上下文

这一篇简单的说一说js的函数执行和js的执行上下文的概念,之前在我的博客里边也提到过js的堆栈队列,这一篇打算单独的拿出来说一说 是什么是js的执行上下文一段可以执行的代码在被执行的时候,会创建一个函数的执行上下文执行上下文里边有三个重要的属性分别是 变量对象作用域链this指向执行上下文是跟函数相关的,执行上下文分为两个阶段 创建阶段执行阶段首先创建阶段 扫描变量和函数(确定变量环境)确定this指向确定词法环境简单说一下词法环境和变量环境的区别,我个人理解的就是说词法环境是包含变量环境的 在js里边原型链大家都不陌生 ,js在当前的对象里边找不到所使用的属性的话会去他的上一级去找直到Object,再找不到就会undefined ,这里边 当前对象的作用域就是他的变量环境,而词法环境则是与之关联的的执行上下文中声明的变量 在创建阶段 函数的声明会被储存在当前的变量环境之中,var的变量的话则会被设置成undefined,所以我们在声明之前就可以访问到var声明的变量 ,but他是一个undfined 然后就是执行阶段了 这个时候已经完成了对所有变量的分配,开始执行代码 变量对象什么是变量对象,变量对象就是与执行上下文相关得数据得一个作用域,这里边存储了在这个上下文里边定义的而变量和函数声明 函数声明的时候会在创建阶段的时候声明一个函数指针全局上下文和函数上下文全局上下文故名思与函数上下文,在函数被执行的时候创建,那么全局上下文是什么时候被创建的呢,首先我们要知道一个概念就是,全局对象 在一般的函数里边全局对象可以用this引用其属性,客户端的话是window对象console.log(this); 可以尝试着去打印一下this,如下图可以看到,window对象里边有很多我们常用或者常见的属性和方法 当然也有一些其他的情况this并不是指向的window对象,this指向 这个东西,我们会在下边说 当然js 万物皆对象不只是说说用 instanceof 输出一下this 会发现也是一个object console.log(this instanceof Object);所以说全局对象是一个object的实例化对象 所以说说白了全局上下文就是在一开始就被创建的,全局上下文是由在浏览器关闭或者刷新的时候才会销毁, 然后这个window对象其实就是全局上下文的变量对象 函数上下文函数上下文和上边所说的意思差不多,但是,啊,但是,在函数上下文中的时候,函数里边的变量对象是活动对象,他的英文名字叫做 actiation object ,大面上看的意思就是说只有进入了这个执行上下文中的时候,也就是说执行到了这个函数的时候,其中的变量对象才会被激活,才会能被访问,在没有执行到这个函数的时候,其属性是不能访问的,这里是和全局上下文不同的。 函数执行过程function foo(a) { var b = 2; function c() {} var d = function() {}; b = 3;}foo(1);首先看这段代码,上边也说了,函数执行有两个阶段创建阶段和执行阶段 首先进如执行上下文,进入创建阶段 首先形参,也就是调用的方法的时候传进来的值,如果你传了就是有值得,如果没传就是一个undefined,相应得函数里边会有一个arguments对象,存放着形参得值,然后函数里边声明得变量,在创建阶段得时候是没有值得,会被赋值一个undefined。函数声明会由名称和对应值组成一个变量对象的属性被创建,也就是上边说的函数得指针,我是这么理解得,因为这个变量指向了这个函数,如果变量对象已经存在相同名称的属性,则完全替换这个属性上边这段代码在创建阶段是这样得 { arguments: { 0: 1, length: 1 }, a: 1, b: undefined, c: reference to function c(){}, d: undefined}然后函数的而执行阶段,这个时候就该干嘛干嘛了,函数里边声明的变量,赋值等等的会在这里根据代码,修改变量的值 ...

July 9, 2019 · 1 min · jiezi

7JavaScript-函数高级作用域与作用域链

JavaScript函数高级——作用域与作用域链一、作用域作用域个数 = n(定义的函数个数) + 1(全局作用域)(1)理解 就是一块"地盘", 一个代码段所在的区域。它是静态的(相对于上下文对象), 在编写代码时就确定了。(2)分类 全局作用域函数作用域ES6中新增了块级作用域(3)作用 隔离变量,不同作用域下同名变量不会有冲突。 二、作用域与执行上下文(1)区别1 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时。全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建。函数执行上下文环境是在调用函数时, 函数体代码执行之前创建。(2)区别2 作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化。上下文环境是动态的, 调用函数时创建, 函数调用结束时上下文环境就会被释放。(3)联系 上下文环境(对象)是从属于所在的作用域。全局上下文环境==>全局作用域函数上下文环境==>对应的函数作用域 三、作用域链(1)理解 多个上下级关系的作用域形成的链, 它的方向是从下向上的(从内到外)查找变量时就是沿着作用域链来查找的。(2)查找一个变量的查找规则 1)在当前作用域下的执行上下文中查找对应的属性, 如果有直接返回, 否则进入2)。2)在上一级作用域的执行上下文中查找对应的属性, 如果有直接返回, 否则进入3)。3)再次执行2)的相同操作, 直到全局作用域, 如果还找不到就抛出找不到的异常。var a = 2;function fn1() { var b = 3; function fn2() { var c = 4; console.log(c); console.log(b); console.log(a); console.log(d); } fn2();}fn1(); 四、作用域_面试题面试题1 var x = 10;function fn() { console.log(x);}function show(f) { var x = 20; f();}show(fn); // 结果 10// 由于fn()的作用域中没有找到属性x,则会去fn()的上一级作用域也就是全局作用域中找,找到x=10,因此打印10.面试题2 ...

June 27, 2019 · 1 min · jiezi

结合作用域执行上下文图解闭包

一 作用域相关      作用域是一套规则,用来管理引擎如何查找变量。在es5之前,js只有全局作用域及函数作用域。es6引入了块级作用域。但是这个块级别作用域需要注意的是不是{}的作用域,而是let,const关键字的块作用域。 1作用域1.1 全局作用域      在全局环境下定义的变量,是挂载在window下的。如下代码所示: 1.2 函数作用域       在函数内定义的变量,值在函数内部才生效,在函数外引用会报RefrenceError的错误       注意区分RefrenceError及TypeError。RefrenceError是在作用域内找不到,而TypeError则是类型错误。如果只是定义了变量a 直接调用便会报TypeError的错误。 1.3 块作用域       es新增的关键字let,const是作用在块级作用域。但是在js内{}形成的块,是不具有作用域的概念的。如下所示,虽然for循环有一个{}包裹的块,但是在块外面还是可以访问i的。 2 作用域链       所谓作用域链,是由当前环境与上层环境的一系列变量对象组成,它保证当前执行环境对符合访问权限的变量和函数的有序访问。而作用域的最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。       如上图所示,会形成一个inner作用域到outer作用域到全局作用域的作用域链。当我们在执行inner函数的时候,需要outName的变量,在自己的作用域内找不到,便会顺着作用域链往上找,直到找到全局作用域。在这个例子中,往上查找到outer作用域的时候便找到了。       简单测试1:如下图所示的代码,大家觉得会输出什么呢?      虽然fn的调用是在show内调用的,但是因为fn所在的作用域是全局作用域,它的x的值会顺着作用域链去全局作用域中啊,即x会输出10。这里需要注意的一点是,变量的确定是在函数定义时候确定的,而不是函数运行时。 二 执行上下文相关       函数每次被调用时,都会产生一个新的执行上下文环境。全局上下文是存在栈中的。而处于栈顶的全局上下文一旦执行完就会自动出栈。如下图所示的代码。       首先是全局上下文入栈,然后开始执行可执行代码。遇到outer(),激活outer()的上下文;       第二步,outer的上下文入栈。开始执行outer内的可执行代码,直到遇到inner()。激活inner()的上下文;       第三步,inner的上下文入栈。开始执行inner内的可执行代码。执行完毕之后inner出栈。       第四步,inner的上下文出栈。outer内继续执行可执行代码。如果一直没有其他的执行上下文,执行完毕即可出栈; ...

June 15, 2019 · 2 min · jiezi

6JavaScript-函数高级执行上下文与执行上下文栈变量提升图解典型实例分析

JavaScript 函数高级——执行上下文与执行上下文栈(图解+典型实例分析)变量提升与函数提升变量声明提升 通过 var 定义(声明)的变量,在定义语句之前就可以访问到值:undefined/* 面试题 : 输出 undefined */var a = 3function fn() { console.log(a) // undefined 调用fn()后进入函数体内部,通过 var 声明的变量提升,值为undefined var a = 4 console.log(a) // 4}fn()console.log('----')可以用开发工具的代码调试工具设置断点,清楚地看到a的值的变化。函数声明提升 通过 function 声明的函数,在之前就可以直接调用值:函数定义(对象)console.log(b) // undefined 通过 var 声明的变量b 变量声明提升fn2() // fn2() 通过 function 声明的函数fn2 函数声明提升fn3() // 报错 通过 var 声明的函数fn3 变量声明提升var b = 3function fn2() { console.log('fn2()')}var fn3 = function () { console.log('fn3()')}!!注意 var a = 3, var fn = function(){ }只要是var声明的,都是变量声明提升,值都是undefined ...

May 6, 2019 · 2 min · jiezi

深入学习js之——执行上下文

在《深入学习js之——执行上下文栈》中说过,当JavaScript代码执行一段可执行代码(executable code)时,会创建对应的执行上下文(execution context)对于每一个执行上下文,都有三个重要的属性:变量对象(Variable object VO)作用域链(Scope chain)this本文我们结合着这三个部分的内容,讲讲执行上下文的具体处理过程。思考题在《深入学习js之——词法作用域和动态作用域》中,提出这样一道思考题:// 思考题一:var scope = “global scope”;function checkscope(){ var scope = “local scope”; function f(){ return scope; } return f();}checkscope();// 思考题二:var scope = “global scope”;function checkscope(){ var scope = “local scope”; function f(){ return scope; } return f;}checkscope()();两段代码都会打印local scope,但是还是有些许差异的,本文就详细的解析执行上下文栈和执行上下文的具体变化过程。具体分析我们分析第一段代码:var scope = “global scope”;function checkscope(){ var scope = “local scope”; function f(){ return scope; } return f();}checkscope();执行过程如下:1、执行全局代码,创建全局执行上下文,全局上下文被压入执行上下文栈ECStack = [ globalContext];2、全局上下文初始化globalContext = { VO: [global], Scope: [globalContext.VO], this: globalContext.VO}2、初始化的同时,checkscope 函数被创建,保存作用域链到函数内部的属性[[scope]]checkscope.[[scope]] = [ globalContext.VO];3、执行checkScope 函数,创建checkScope 函数执行上下文,checkScope 函数执行上下文被压入执行上下文栈:ECStack = [ checkscopeContext, globalContext];4、checkscope 函数执行上下文初始化:1.复制函数 [[scope]] 属性创建作用域链,2.用 arguments 创建活动对象,3.初始化活动对象,即加入形参、函数声明、变量声明,4.将活动对象压入 checkscope 作用域链顶端,同时 f 函数被创建,保存作用域链到 f 函数的内部属性[[scope]]checkscopeContext = { AO: { arguments: { length: 0 }, scope: undefined, f: reference to function f(){} }, Scope: [AO, globalContext.VO], this: undefined}5、执行f函数,创建 f 函数执行上下文,f 函数执行上下文被压入执行上下文栈 ECStack = [ fContext, checkscopeContext, globalContext ]6、f 函数执行上下文初始化, 以下跟第 4 步相同:1.复制函数 [[scope]] 属性创建作用域链2.用 arguments 创建活动对象3.初始化活动对象,即加入形参、函数声明、变量声明4.将活动对象压入 f 作用域链顶端 fContext = { AO: { arguments: { length: 0 } }, Scope: [AO, checkscopeContext.AO, globalContext.VO], this: undefined }7、f 函数执行,沿着作用域链查找 scope 值,返回 scope 值8、f 函数执行完毕,f 函数上下文从执行上下文栈中弹出ECStack = [ checkscopeContext, globalContext]9、checkscope 函数执行完毕,checkscope 执行上下文从执行上下文栈中弹出ECStack = [ globalContext] ...

March 15, 2019 · 1 min · jiezi

深入学习js之——执行上下文和执行栈

开篇作为一个JavaScript的程序开发者,如果被问到JavaScript代码的执行顺序,你脑海中是不是有一个直观的印象 – JavaScript 是顺序执行的,可事实真的是这样的吗? 让我们首先看两个小例子:var foo = function () { console.log(‘foo1’);}foo(); // foo1var foo = function () { console.log(‘foo2’);}foo(); // foo2function foo() { console.log(‘foo1’);}foo(); // foo2function foo() { console.log(‘foo2’);}foo(); // foo2刷过面试题目的都知道:JavaScript引擎并非一行一行地分析和执行程序,而是一段一段地分析执行,当执行一段代码的时候,会进行一个准备工作。比如我们熟悉的JavaScript中的变量提升比如函数提升都是在这个准备阶段完成的。本文我们就来深入的研究一下,这一段一段中的段是如何划分的呢?到底JavaScript引擎遇到一段怎样的代码才会做"准备工作"呢?为了解答这个问题我们引入一个概念——执行上下文。执行上下文如果你做过小学的阅读理解,肯定见到过这样的题目:联系上下文解释句子,这里的上下文指的可能是这个句子所在的段落,也可能是这个句子所在段落的临近段落。实际上,这里描述的是一个句子的语境和作用范围,联系类比到程序中我们可以作如下定义:执行上下文是当前JavaScript代码被解析和执行时所在环境的抽象概念。执行上下文的类型执行上下文总共分为三种类型,有时候我们也叫做可执行代码(executable code)全局执行上下文: 只有一个,浏览器中的全局对象就是window对象,this指向这个全局对象。函数执行上下文: 存在无数个,只有在函数被调用的时候才会被创建,每次调用函数都会创建一个新的执行上下文。Eval 函数执行上下文: 指的是运行在eval函数中的代码,很少用而且不建议使用。举个例子,当执行到一个函数的时候,就会进行准备工作,这里的"准备工作",让我们用个更专业一点的说法,就叫做"执行上下文(execution context)"。执行栈接下来问题来了,我们写的函数多了去了,如何管理创建的那么多执行上下文呢?所以 JavaScript 引擎创建了执行上下文栈(Execution context stack )ECStack 来管理执行上下文。这里我们可以简单的认为 ECStack 是一个数组,类似这样:ECStack = [];执行栈,也叫做调用栈,具有 LIFO(last in first out 后进先出) 结构,用于存储在代码执行期间创建的所有执行上下文。首次运行JavaScript代码的时候,会创建一个全局执行的上下文并Push到当前的执行栈中,每当发生函数调用,引擎都会为该函数创建一个新的函数执行上下文并Push当前执行栈的栈顶。当栈顶的函数运行完成后,其对应的函数执行上下文将会从执行栈中Pop出,上下文的控制权将移动到当前执行栈的下一个执行上下文。让我们看一段代码来理解这个过程:var a = ‘Hello World!’;function first() { console.log(‘Inside first function’); second(); console.log(‘Again inside first function’); }function second() { console.log(‘Inside second function’); }first(); console.log(‘Inside Global Execution Context’);// Inside first function// Inside second function// Again inside first function// Inside Global Execution Context当上述代码在浏览器加载时,JavaScript引擎创建了一个全局执行上下文并把它压入(push) 当前的执行栈。当遇到 first() 函数调用时,JavaScript引擎为该函数创建一个新的执行上下文并把它压入当前执行栈的顶部。当从 first() 函数内部调用 second() 函数时,JavaScript引擎为 second() 函数创建了一个新的执行上下文并把它压入当前执行栈的顶部,当 second() 函数执行完毕,它的执行上下文会从当前栈弹出(pop),并且控制流程到达下一个执行上下文,即 first() 函数的执行上下文。当 first() 执行完毕,它的执行上下文从栈中弹出,控制流程到达了全局执行上下文。一旦所有的代码执行完毕,JavaScript引擎从当前栈中移出全局执行上下文。下面这张图,能够更加清晰的解释上面这个执行过程看两个思考题var scope = “global scope”;function checkscope(){ var scope = “local scope”; function f(){ return scope; } return f();}checkscope();var scope = “global scope”;function checkscope(){ var scope = “local scope”; function f(){ return scope; } return f;}checkscope()();两段代码执行的结果一样,但是两段代码究竟有哪些不同呢?答案就是执行上下文栈的变化不一样。让我们模拟第一段代码:ECStack.push(<checkscope> functionContext);ECStack.push(<f> functionContext);ECStack.pop();ECStack.pop();让我们模拟第二段代码:ECStack.push(<checkscope> functionContext);ECStack.pop();ECStack.push(<f> functionContext);ECStack.pop();为了更详细讲解两个函数执行上的区别,我们需要探究一下执行上下文到底包含了哪些内容,我们需要更加深入了解变量对象的相关内容。参考链接:《理解 JavaScript 中的执行上下文和执行栈》《JavaScript深入之执行上下文栈》 ...

February 17, 2019 · 1 min · jiezi

JavaScript中的执行上下文

翻译:疯狂的技术宅链接:http://davidshariff.com/blog/…本文首发微信公众号:jingchengyideng欢迎关注,每天都给你推送新鲜的前端技术文章在这篇文章中,我将深入探讨JavaScript的最基本部分之一,即Execution Context(执行上下文)。 在本文结束时,你应该对解释器了解得更清楚:为什么在声明它们之前可以使用某些函数或变量?以及它们的值是如何确定的?什么是执行上下文?JavaScript的执行环境非常重要,当JavaScript代码在行时,会被预处理为以下情况之一:Global code - 首次执行代码的默认环境。Function code - 每当执行流程进入函数体时。Eval code - 要在eval函数内执行的文本。你可以阅读大量涉及作用域的在线资料,不过为了使事情更容易理解,让我们将术语“执行上下文”视为当前代码的运行环境或作用域。接下来让我们看一个包含global和function / local上下文的代码示例。这里没有什么特别之处,我们有一个由紫色边框表示的全局上下文,和由绿色,蓝色和橙色边框表示的3个不同的函数上下文。 只能有1个全局上下文,可以从程序中的任何其他上下文访问。你可以拥有任意数量的函数上下文,并且每个函数调用都会创建一个新的上下文,从而创建一个私有作用域,其中无法从当前函数作用域外直接访问函数内部声明的任何内容。 在上面的示例中,函数可以访问在其当前上下文之外声明的变量,但外部上下文无法访问在其中声明的变量或函数。 为什么会这样呢? 这段代码究竟是如何处理的?Execution Context Stack(执行上下文堆栈)浏览器中的JavaScript解释器被实现为单个线程。 实际上这意味着在浏览器中一次只能做一件事,其他动作或事件在所谓的执行堆栈中排队。 下图是单线程堆栈的抽象视图:我们已经知道,当浏览器首次加载脚本时,它默认进入全局上下文执行。 如果在全局代码中调用函数,程序的顺序流进入被调用的函数,创建新的执行上下文并将其推送到执行堆栈的顶部。如果在当前函数中调用另一个函数,则会发生同样的事情。 代码的执行流程进入内部函数,该函数创建一个新的执行上下文,该上下文被推送到现有堆栈的顶部。 浏览器将始终执行位于堆栈顶部的当前执行上下文,并且一旦函数执行完当前执行上下文后,它将从栈顶部弹出,把控制权返回到当前栈中的下一个上下文。 下面的示例显示了递归函数和程序的执行堆栈:(function foo(i) { if (i === 3) { return; } else { foo(++i); }}(0));代码简单地调用自身3次,并将i的值递增1。每次调用函数foo时,都会创建一个新的执行上下文。 一旦上下文完成执行,它就会弹出堆栈并且讲控制返回到它下面的上下文,直到再次达到全局上下文。关于执行堆栈execution stack有5个关键要点:单线程。同步执行。一个全局上下文。任意多个函数上下文。每个函数调用都会创建一个新的执行上下文execution context,甚至是对自身的调用。执行上下文的细节所以我们现在知道每次调用一个函数时,都会创建一个新的执行上下文。 但是,在JavaScript解释器中,对执行上下文的每次调用都有两个阶段:创建阶段 [调用函数时,但在执行任何代码之前]:创建作用域链。创建变量,函数和参数。确定“this”的值。激活/代码执行阶段:分配值,引用函数和解释/执行代码。可以将每个执行上下文在概念上表示为具有3个属性的对象:executionContextObj = { ‘scopeChain’: { /* variableObject + 所有父执行上下文的variableObject / }, ‘variableObject’: { / 函数实参/形参,内部变量和函数声明 */ }, ’this’: {}}激活对象/变量对象 [AO/VO]在调用该函数,并且在实际执行函数之前,会创建这个executionContextObj。 这被称为第1阶段,即创造阶段。 这时解释器通过扫描函数传递的实参或形参、本地函数声明和局部变量声明来创建executionContextObj。 此扫描的结果将成为executionContextObj中的variableObject。以下是解释器如何预处理代码的伪代码概述:找一些代码来调用一个函数。在执行功能代码之前,创建执行上下文。进入创建阶段:初始化作用域链。创建variable object:创建arguments object,检查参数的上下文,初始化名称和值并创建引用副本。扫描上下文以获取函数声明:对于找到的每个函数,在variable object中创建一个属性,该属性是函数的确切名称,该属性存在指向内存中函数的引用指针。如果函数名已存在,则将覆盖引用指针值。扫描上下文以获取变量声明:对于找到的每个变量声明,在variable object中创建一个属性作为变量名称,并将该值初始化为undefined。如果变量名称已存在于variable object中,则不执行任何操作并继续扫描。确定上下文中“this”的值。激活/执行阶段:在上下文中运行/解释函数代码,并在代码逐行执行时分配变量值。我们来看一个例子:function foo(i) { var a = ‘hello’; var b = function privateB() { }; function c() { }}foo(22);在调用foo(22)时,创建阶段如下所示:fooExecutionContext = { scopeChain: { … }, variableObject: { arguments: { 0: 22, length: 1 }, i: 22, c: pointer to function c() a: undefined, b: undefined }, this: { … }}如你所见,创建阶段处理定义属性的名称,而不是为它们赋值,但正式的形参/实参除外。创建阶段完成后,执行流程进入函数,激活/代码执行阶段在函数执行完毕后如下所示:fooExecutionContext = { scopeChain: { … }, variableObject: { arguments: { 0: 22, length: 1 }, i: 22, c: pointer to function c() a: ‘hello’, b: pointer to function privateB() }, this: { … }}关于hoisting你可以找到许多使用JavaScript定义术语hoisting的在线资源,解释变量和函数声明被hoisting到其函数范围的顶部。 但是没有人能够详细解释为什么会发生这种情况,掌握了关于解释器如何创建激活对象的新知识,很容易理解为什么。 请看下面的代码示例:(function() { console.log(typeof foo); // function pointer console.log(typeof bar); // undefined var foo = ‘hello’, bar = function() { return ‘world’; }; function foo() { return ‘hello’; }}());我们现在可以回答的问题是:为什么我们可以在声明foo之前就能访问?如果我们理解了创建阶段,就知道在激活/代码执行阶段之前已经创建了变量。因此,当函数流开始执行时,已经在激活对象中定义了foo。Foo被声明两次,为什么foo显示为function而不是undefined或string?即使foo被声明两次,我们通过创建阶段知道函数在变量之前就被创建在激活对象上了,而且如果激活对象上已经存在了属性名称,我们只是绕过了声明这一步骤。因此,首先在激活对象上创建对函数foo()的引用,并且当解释器到达var foo时,我们已经看到属性名称foo存在,因此代码不执行任何操作并继续处理。为什么bar未定义?bar实际上是一个具有函数赋值的变量,我们知道变量是在创建阶段被创建的,但它们是使用undefined值初始化的。总结希望到这里你已经能够很好地掌握了JavaScript解释器如何预处理你的代码。 理解执行上下文和堆栈可以让你了解背后的原因:为什么代码预处理后的值和你预期的不一样。你认为学习解释器的内部工作原理是多此一举还是非常必要的呢? 了解执行上下文阶段是否能够帮你你写出更好的JavaScript呢?进一步阅读ECMA-262 5th Edition (http://www.ecma-international…ECMA-262-3 in detail. Chapter 2. Variable object (http://dmitrysoshnikov.com/ec...Identifier Resolution, Execution Contexts and scope chains (http://jibbering.com/faq/note…本文首发微信公众号:jingchengyideng欢迎关注,每天都给你推送新鲜的前端技术文章 ...

January 24, 2019 · 1 min · jiezi