变量提升:变量提升。我讨厌的var关键字:看完下面的内容你就明白标题的意思了,先来一段超简单的代码:这段代码出奇的简单,我们已经达到了预期的效果,并在控制台打印出:HelloJavaScripthoisting。现在,我将更改这段代码,将调用放在前面,将语句放在后面。C或C++等很多语言是不允许的,但是javaScript是允许的。您尝试猜测的结果:你会觉得很奇怪,在我们调用之前,为什么我们的str=undefined而不是报错:undefined???在我删除varstr='HelloJavaScript提升'之后,尝试考虑这段代码的结果:现在我们得到了我们想要的,错误:undefined。实际上,我们的浏览器首先会解析我们的脚本,完成一个初始化步骤。当它遇到一个var变量时,它会先将该变量初始化为undefined。这就是变量提升,就是说当浏览器遇到JS执行环境的初始化时,提前定义了由此引起的变量。在上面的代码中,我们没有接触到函数,因为,我想让代码更简洁更明显,显然我们应该测试函数。这里,我们并没有调用函数,但是这个函数已经被初始化了。其实初始化的内容比我们看到的要多。如何避免变量提升:使用let和const关键字,尽量使用const关键字,尽量避免使用var关键字;但是,如果为了兼容,那也没办法,哈哈哈,致命一击!!!Executioncontext:Executioncontext,又称执行上下文(executioncontext),听起来很厉害对吧,其实也没那么难。作用域链:其实我们知道JS是使用词法作用域的。对其他scope不了解的童鞋请移步我的《谈谈 JavaScript 的作用域》,或者百度。每个javaScript函数都表示为一个对象,更准确地说,是Function对象的一个??实例。与其他对象一样,函数对象具有可以通过编程方式访问的属性。以及一系列不能通过代码访问的属性,但是这些属性是提供给JavaScript引擎访问的内部属性。其中一个属性是[[Scope]],由ECMA-262标准的第三版定义。内部属性[[Scope]]包含创建函数的范围内的对象集合。这个集合称为函数的作用域链,它决定了可以访问哪些数据。资料来源:《 高性能JavaScript 》;我很好奇,我怎么能看到这个不能通过代码访问的属性呢???经过我的研究,我发现看到这个东西的方法是打开谷歌浏览器的控制台并输入代码:functionadd(x,y){returnx+y;}console.log(add.prototype);//从原型链上的构造函数可以看出,add函数的隐藏属性。可能还有其他方法,但我只找到了这一种。你需要这个:然后这个:嗯,正如你看到的,[[Scope]]属性下有一个数组,里面存放的是作用域链,此时只有一个global。考虑下面的代码,复习词法作用域,结合[[Scope]]属性,可以理解词法作用域的原理,vartestValue='outer';functionfoo(){console.log(testValue);//“外部”console.log(foo.prototype)//number1}functionbar(){vartestValue='inner';console.log(bar.prototype)//number2foo();}bar();下面是,执行结果:number1的[[Scope]]属性:Scopes[1]:[[Scope]]属性number2:Scopes[1]因为,在初始化的时候,[[Scope]]已经确定了,两个这个函数不管是谁,如果在自己的作用域内没有找到,就会去全局作用域中寻找变量。参考前端高级面试题的详细答案,想想另外一段代码:vartestValue='outer';函数栏(){vartestValue='inner';富();console.log(bar.prototype)//1号函数foo(){console.log(testValue);//“内部”console.log(foo.prototype);//数字2}}bar();[[Scope]]propertyofnumber1:Scopes[1]:The[[Scope]]attributeofnumber1:Scopes[2]:这解释了为什么结果是,testValue="inner"。当需要调用testValue变量时;首先寻找它自己的作用域,如果没有,JS引擎将在作用域链中查找[0]=>[1]=>[2]=>[...]。在这里,找到bar函数的范围。另一个有趣的是,Closure是关闭的意思。证明了全局作用域链在全局执行上下文初始化的时候就确定了:我们来做一个有趣的实验,刚才按照我描述的方法,可以找到[[Scope]]属性。这个属性是什么时候确定的???显然,我们需要从三个方面进行测试:函数声明前、函数执行时、函数执行后:console.log(add.prototype);//Number1beforedeclarationfunctionadd(x,y){console.log(add.prototype);//当数字2运行时返回x+y;}add(1,2);console.log(add.prototype);//number3执行后,number1在声明前:number2运行When:Number3执行后:可以按照我的方法,多做实验,尝试嵌套几个函数,观察作用域链再调用。作用域链在JS引擎中完成,初始化执行上下文环境,已经确定,这和我们在变量提升部分描述的一样。保证了我们需要的变量在JS内部可以正常查询!.我的小疑点说明:在这里,我无法证明一个问题。全局执行上下文初始化后,它确定所有函数作用域链。尽管如此,一个执行上下文被初始化以确定这个作用域的函数作用域链。这是我的疑惑,我无法证明这个问题,但我更倾向于2的观点,如果你知道如何证明,请联系我。至少,《高性能JavaScript》是这么描述的。了解作用域链有什么好处?试想,我们知道了作用域链,有什么用呢???我们知道,如果作用域链比较深,[0]=>[1]=>[2]=>[...]=>[n],我们调用全局变量,它总是在最后(这里是第n个),这样找到我们需要的变量会导致多少性能问题?JS引擎花了多少时间寻找变量?因此,这个故事告诉我们,应该尽量将全局变量局部化,避免作用域链嵌套带来的性能问题。了解执行上下文:将这段代码放在全局范围内。这段代码改编自《高性能JavaScript》。functionadd(x,y){returnx+y;}varresult=add(1,2);这段代码也很简洁,但是JavaScript引擎内部发生的事情并不简单。正如上一节VariableHoisting中所讨论的,JS引擎会初始化我们声明的函数和变量。那么在执行add(1,2)之前,我们的add函数[[Scope]]内部发生了什么???这里分为三个时期:初始化执行上下文、运行执行上下文和结束执行上下文。很明显,当varresult=add(1,2)这句执行时,程序在准备:初始化执行上下文。如上图所示,在调用函数之前,add函数的[[Scope]]属性保存的作用域链中已经有这些东西了。执行此函数时,会建立一个称为执行上下文的内部对象。执行上下文定义了函数执行的环境。每次调用一个函数,都会创建一个执行上下文;成功初始化执行上下文后,将创建一个活动对象,该对象将生成this参数和我们声明的变量。这个例子里面是x,y。RunExecutionContextPhase:EndExecutionContextPhase:好的,但是这里没有调用其他函数。其实还有,我们的JavaScript引擎是如何管理多个函数之间的执行上下文的呢???
