当前位置: 首页 > Web前端 > HTML5

一篇厘清Javascript中“语境”

时间:2023-04-05 21:42:17 HTML5

背景的文章,希望能给大家带来一些启发和帮助。如需转载,请联系作者获得许可。文本上下文是Javascript中一个比较重要的概念。很多朋友可能对这个概念不是很熟悉。用“scope”和“closure”代替它怎么样?是不是很亲切。“作用域”和“闭包”是与“执行上下文”密切相关的两个概念。在解释什么是“执行上下文”之前,让我们先回顾一下“作用域”和“闭包”。范围首先,什么是范围?域是一个范围。范围实际上是一个变量或函数的可访问范围。它控制变量和函数的可见性和生命周期。作用域也分为:“全局作用域”和“局部作用域”。全局作用域:如果一个对象在任何地方都可以被访问,那么这个对象就是一个全局对象,具有全局作用域。具有全局作用域的对象可以分为以下几种情况:在最外层变量全局对象的属性中任意位置定义的变量都是隐式定义的(即:没有定义而直接赋值的变量)。隐式定义的变量是在全局范围内定义的。本地范围:JavaScript的范围由函数定义。函数中定义的变量仅在该函数内部可见。这样的范围称为本地范围。还有一个与作用域密切相关的概念,就是作用域链。作用域链作用域链是一个包含一系列对象的集合,这些对象可用于检索出现在上下文中的各种类型的标识符(变量、参数、函数声明等)。函数定义时,会将父变量对象AO/VO的集合存放在内部属性[[scope]]中,称为作用域链。AO:ActivationObjectVO:Variableobject变量对象Javascript使用词法作用域(静态作用域),函数运行在它们被定义的作用域,而不是它们被执行的作用域。看一个简单的例子:vara=3;functionfoo(){console.log(a)}functionbar(){vara=6foo()}bar()如果js使用动态作用域,打印它应该是6而不是3。这个例子表明javasript是一个静态作用域。此函数作用域链的伪代码:functionbar(){functionfoo(){//...}}bar.[[scope]]=[globalContext.VO];foo.[[scope]]=[barContext.AO,globalContext.VO];当函数被激活时,它会先复制[[scope]]属性创建一个作用域链,然后创建一个变量对象VO,然后将其添加到作用域链中。executionContextObj:{VO:{},scopeChain:[VO,[[scope]]]}一般来说,VO的作用域要比AO大很多,VO负责串联调用的函数。VO是外部的,而AO是函数本身的内部。与AO和VO密切相关的概念包括GO和EC。有兴趣的朋友可以参考:https://blog.nixiaolei.com/20...说说闭包。ClosureClosure也是面试中经常被问到的问题,调查的形式也很灵活。比如:描述什么是闭包,写一段闭包代码。闭包有什么用?给你一个闭包的例子,让你修改,或者看看输出,闭包到底是什么?说白了,闭包其实就是一个函数,一个可以访问自由变量的函数。自由变量:未在函数内部声明的变量。许多所谓的代码规范说,不要滥用闭包,那样会导致性能问题。当然,我并不认同这种说法,但提出这种说法是有一定原因的。毕竟闭包中的自由变量会绑定到代码块上,离开创建它的环境后仍然会生效,使用代码块的人可能意识不到。闭包中的自由变量有多种形式,我们先举一个简单的例子。函数添加(p1){返回函数(p2){返回p1+p2;}}vara=add(1);varb=add(2);a(1)//2b(1)//3在上面的例子中,两个函数a和b的代码块是相同,但是执行a(1)和b(1)的结果是不同的,因为它们绑定的自由变量不同,这里的自由变量实际上是函数体中的p1。自由变量的引入可以起到OOP中封装一样的作用。我们可以在一层函数中封装一些外界不知道的自由变量,从而达到同样的效果。很多模块的封装也是利用了这个特性。那我就说说我遇到的一个真实案例。是去年腾讯QQ音乐面试的笔试题:for(vari=1;i<=5;i++){setTimeout(functiontimer(){console.log(i)},i*1000)}这段代码会输出一堆6,让你改成输出1,2,3,4,5。解决方案还是有很多的,就两种常见的。用闭包解决(vari=1;i<=5;i++){;(function(j){setTimeout(functiontimer(){console.log(j)},j*1000)})(i)}使用立即执行函数将i传递给函数。此时,该值固定在参数j上,不会改变。下次执行定时器闭包时,可以使用外部函数的变量j来达到目的。【推荐】使用letfor(leti=1;i<=5;i++){setTimeout(functiontimer(){console.log(i)},i*1000)}const,let的原理和相关细节可以参考tomy另一篇文章:【第13期】掌握前端面试基础系列一:ES6解释完这两个概念,我们再回到我们的话题,语境。执行上下文首先,什么是执行上下文?简单的说,执行上下文就是Javascript的执行环境。当javascript执行一段可执行代码时,会创建相应的执行上下文。组成如下:executionContextObj={this,VO,scopeChain:作用域链,与闭包相关}由于Javavscript是单线程的,一次只能处理一件事,其他任务会在指定的上下文栈中排队.当Javascript解释器最初执行代码时,它会在堆栈上创建一个全局执行上下文,然后在每次函数调用时创建并压入一个新的执行上下文堆栈。函数执行完毕后,弹出执行上下文。建立执行上下文的步骤:创建阶段初始化作用域链创建变量对象创建参数扫描函数声明扫描变量声明求本执行阶段初始化变量和函数引用执行代码一个容易混淆的概念。今天就来说说吧。首先,这个只能在运行时确认,不能在定义时确认。当一个函数被执行时,this总是指向调用该函数的对象。判断this的方向其实就是判断this属于哪个函数。this的执行会有不同的指向情况,大致可以分为:指向调用对象指向全局对象和使用new构造指向新对象apply/call/bind,我们来看箭头函数一一个。1.指向调用对象functionfoo(){console.log(this.a);}varobj={a:2,foo:foo};obj.foo();//22.指向全局对象的情况是最容易测试的,也是最容易混淆的。我们先看一个简单的例子:vara=2;functionfoo(){console.log(this.a);}foo();//2毫无疑问。让我们看一个稍微复杂一点的:functionfoo(){console.log(this.a);}functiondoFoo(fn){this.a=4fn();}varobj={a:2,foo:foo};vara=3doFoo(obj.foo);//4contrast:functionfoo(){this.a=1console.log(this.a);}functiondoFoo(fn){this.a=4fn();}varobj={a:2,foo:foo};vara=3doFoo(obj.foo);//1发现差异?你可能会问,为什么下面的a不是doFoo的a?难道是先读了一个infoo?打印foo和doFoo的this,可以知道他们的this都是指向window的。他们的操作会修改一个inwindow的值。读取foo中的集合不是优先事项。简单验证:functionfoo(){setTimeout(()=>this.a=1,0)console.log(this.a);}functiondoFoo(fn){this.a=4fn();}varobj={a:2,foo:foo};vara=3doFoo(obj.foo);//4setTimeout(obj.foo,0)//1结果证实了我们上面的结论,没有什么优先级。3.使用new构造指向新对象vara=4functionA(){this.a=3this.callA=function(){console.log(this.a)}}A()//returnundefined,A().callA会报错。callA存放在windowa=newA()a.callA()//3、callA在newA返回的对象中4.apply/call/bind大家应该不陌生吧。让this指向传递的第一个参数,如果第一个参数为null、undefined或未传递,则指向全局变量。vara=3functionfoo(){console.log(this.a);}varobj={a:2};foo.call(obj);//2foo.call(null);//3foo.call(undefined);//3foo.call();//3varobj2={a:5,foo}obj2.foo.call()//3,not5//bind返回一个新函数functionfoo(something){console.log(this.a,something);returnthis.a+something;}varobj=a:2};varbar=foo.bind(obj);varb=bar(3);//23console.log(b);//55.箭头函数箭头函数比较特殊,它没有自己的this。它使用封闭执行上下文(函数或全局)的this值:varx=11;varobj={x:22,say:()=>{console.log(this.x);}}obj.say();//11obj.say.call({x:13})//11x=14obj.say()//14//比较varobj2={x:22,say(){console.日志(这个.x);}}obj2.say();//22obj2.say.call({x:13})//13综上所述,我们的系统引入了context,以及相关的scope,closedpackage,this等相关概念。介绍了它们的作用、使用场景、区别和联系。希望对大家有所帮助。文中如有错误,请指正,谢谢。最后,如果觉得内容有帮助,可以关注我的公众号《前端e进阶》,了解最新动态。也可以联系我加入微信群。群里有很多大佬,可以一起讨论技术,一起钓鱼。让我们一起学习,一起成长!