【.com原稿】前言如果你是一名JavaScript开发者,或者想成为一名JavaScript开发者,那么你必须了解JavaScript程序的内部执行机制。执行上下文和执行栈是JavaScript中的关键概念之一,也是JavaScript中的难点之一。了解执行上下文和执行堆栈还有助于理解其他JavaScript概念,例如提升、作用域和闭包。本文试图以通俗易懂的方式介绍这些概念。一、执行上下文(ExecutionContext)1、什么是执行上下文?简而言之,执行上下文是当前JavaScript代码解析和执行环境的抽象概念。在JavaScript中运行的任何代码都在执行上下文中运行。2.执行上下文的类型执行上下文分为三种类型:全局执行上下文:这是默认的和最基本的执行上下文。不在任何函数中的代码在全局执行上下文中。它做了两件事:1.创建一个全局对象,也就是浏览器中的window对象。2.将this指针指向这个全局对象。一个程序中只能存在一个全局执行上下文。函数执行上下文:每次调用函数时,都会为该函数创建一个新的执行上下文。每个函数都有自己的执行上下文,但它仅在函数被调用时创建。程序中可以存在任意数量的函数执行上下文。每当创建新的执行上下文时,它都会按特定顺序执行一系列步骤,这将在本文后面讨论。eval函数执行上下文:在eval函数中运行的代码也会得到自己的执行上下文,但是由于eval函数不是Javascript开发者常用的,这里就不展开讨论了。2.执行上下文的生命周期执行上下文的生命周期包括三个阶段:创建阶段→执行阶段→回收阶段。本文重点介绍创建阶段。1.在创建阶段,当函数被调用时,但在任何内部代码执行之前,会做以下三件事:创建变量对象:首先初始化函数的形参参数,促进函数声明和变量声明.下面将对其进行详细描述。创建作用域链(ScopeChain):在执行上下文的创建阶段,在变量对象之后创建作用域链。作用域链本身包含变量对象。作用域链用于解析变量。当要求解析一个变量时,JavaScript总是从代码嵌套的最内层开始。如果在最内层没有找到该变量,它将跳转到上一个父作用域,直到找到该变量。判断this指向什么:包括多种情况,下面会详细介绍。一个JS脚本在执行之前,首先要解析代码(所以JS是一种解释和执行的脚本语言)。解析时会先创建一个全局的执行上下文,先取出代码中要执行的变量和函数声明。先把变量临时赋值给undefined,函数先声明,准备使用。这一步做完了,接下来开始正式执行程序。另外,一个函数在执行之前,也会创建一个函数执行上下文,这个和全局上下文类似,只是函数执行上下文中会多一些this参数和函数形参。2.在执行阶段,进行变量赋值和代码执行。3.回收阶段,将执行上下文弹出栈,等待虚拟机回收执行上下文。三、变量提升和this指向的细节1、变量声明提升大多数编程语言都是在使用变量之前声明变量,但是在JS中,事情有点不同:console.log(a)//undefinedvara=10上面的代码是正常输出undefined而不是报UncaughtReferenceError:aisnotdefined,这是因为声明在提升,相当于下面的代码:vara;//声明的默认值为undefined"preparation"console.log(a);a=10;//赋值2.函数声明提升我们都知道创建函数有两种方式,一种是通过函数声明functionfoo(){},另一种是通过函数表达式varfoo=function(){},那么这两种函数提升有什么区别呢?console.log(f1)//functionf1(){}functionf1(){}//函数声明console.log(f2)//undefinedvarf2=function(){}//函数表达式接下来我们用一个例子来说明问题:functiontest(){foo();//UncaughtTypeError"fooisnotafunction"bar();//"thiswillrun!"varfoo=function(){//函数表达式分配给本地变量'foo'alert("thiswon'trun!");}functionbar(){//函数声明,给定名称'bar'alert("thiswillrun!");}}test();上面的例子,调用foo()时报错,但是bar可以正常调用。我们之前说过,变量和函数都会被引发。当遇到函数表达式varfoo=function(){}时,varfoo会先被提升到函数体的顶部。但是此时foo的值是undefined,所以执行foo()报错。对于函数bar(),改进了整个函数,可以顺利执行bar()。有一个细节必须注意:当一个函数和一个变量同名并被提升时,函数声明的优先级更高,因此变量声明会被函数声明覆盖,但可以重新赋值.alert(a);//输出:functiona(){alert('我是一个函数')}functiona(){alert('我是一个函数')}//vara='我是一个变量';alert(A);//Output:'Iamavariable'函数声明的优先级高于var声明,也就是说当两个同名变量同时被function和var声明时,函数声明会覆盖var宣言。这段代码相当于:functiona(){alert('我是一个函数')}vara;//hoistingalert(a);//输出:functiona(){alert('我是一个函数')}a='IamVariable';//Assignmentalert(a);//Output:'Iamavariable'***让我们看一个更复杂的例子:functiontest(arg){//1.形参arg为"hi"//2.由于函数声明优先于变量声明,arg为functionconsole.log(arg);vararg='hello';//3.vararg变量声明被忽略,arg='hello'被执行了hello*/这是因为当函数执行的时候,会先形成一个新的私有作用域,然后依次执行下面的步骤:如果有形参,先给形参赋值。对于私有作用域中的预解释,函数声明的优先级高于变量声明,后者会被前者覆盖,但可以重新赋值。私有范围内的代码从上到下执行。3.要确定this的方向,首先要明白一个很重要的概念——this的值只有在执行的时候才能确定,而不能在定义的时候确定!为什么-因为这是执行上下文的一部分,执行上下文需要在代码执行之前确定,而不是在定义时确定。请参见以下示例://case1functionfoo(){console.log(this.a)//1}vara=1foo()//case2functionfn(){console.log(this);}varobj={fn:fn};obj.fn();//this->obj//案例3functionCreateJsPerson(name,age){//this是当前类的一个实例p1this.name=name;//=>p1.name=命名这个。age=age;//=>p1.age=age}varp1=newCreateJsPerson("尹华志",48);//case4functionadd(c,d){returnthis.a+this.b+c+d;}varo={a:1,b:3};add.call(o,5,7);//1+3+5+7=16add.apply(o,[10,20]);//1+3+10+20=34//案例5
