手机版

深入分析JavaScript中的范围和上下文

时间:2021-11-06 来源:互联网 编辑:宝哥软件园 浏览:

javascript中的范围和上下文是这种语言的独特之处,这部分归功于它们带来的灵活性。每个函数都有不同的变量上下文和范围。这些概念是javascript中一些强大设计模式的后盾。但是,也给开发者带来了很大的困惑。下面充分揭示了javascript中上下文和范围的差异,以及如何在各种设计模式中使用它们。

上下文和范围。

首先要知道上下文和范围是两个完全不同的概念。多年来,我发现很多开发人员混淆了这两个概念(包括我自己),并错误地混淆了它们。平心而论,这些年来,许多术语被混淆使用。

函数的每个调用都有一个与其密切相关的范围和上下文。基本上,范围是基于函数的,而上下文是基于对象的。换句话说,作用域涉及被调用函数中的变量访问,不同的调用场景是不同的。Context永远是这个关键字的值,它是对拥有(控制)当前执行代码的对象的引用。

变量作用域

变量可以在局部或全局范围内定义,这在运行时建立了不同的变量可访问范围。任何定义的全局变量都意味着它需要在函数体之外声明,并且它存在于整个运行时中,可以在任何范围内访问。在ES6之前,局部变量只能存在于函数体中,函数的每次调用都有不同的作用域。局部变量只能在它们被调用的周期范围内被赋值、检索和操作。

请注意,在ES6之前,JavaScript不支持块级作用域,这意味着在if语句、switch语句、for循环和while循环中不能支持块级作用域。也就是说,ES6之前的JavaScript无法构建类似于Java中的块级作用域(变量不能在语句块之外访问)。但是,从ES6开始,您可以通过let关键字定义变量,它纠正了var关键字的缺点,使您能够像Java语言一样定义变量,并支持块级范围。看两个例子:

在ES6之前,我们使用var关键字来定义变量:

func(){ if(true){ var tmp=123;} console . log(tmp);//123}可以访问,因为var关键字声明的变量有一个变量提升的过程。在ES6场景中,建议使用let关键字来定义变量:

func(){ if(true){让tmp=123} console . log(tmp);//referenceerror : tmp未定义}这样可以避免很多错误。

这是什么语境?

上下文通常取决于如何调用函数。当函数作为对象中的方法被调用时,它被设置为调用该方法的对象:

var obj={ foo : function(){ alert(this===obj);}};obj . foo();//true也适用于调用函数时使用新运算符创建对象实例的情况。在这种情况下,该值被设置为函数范围内新创建的实例:

function foo(){ alert(this);} new foo()//foo()//window调用绑定函数时,默认情况下这是全局上下文,它指向浏览器中的window对象。需要注意的是,ES5引入了严格模式的概念。如果启用了严格模式,默认情况下上下文是未定义的。

执行上下文(执行上下文)。

JavaScript是单线程语言,这意味着一次只能执行一个任务。当JavaScript解释器初始化执行代码时,默认情况下首先进入全局执行上下文。从现在开始,函数的每次调用都会创建一个新的执行环境。

初学者在这里经常感到困惑。这里提到了一个新的术语——执行上下文,它定义了变量或函数可以访问的其他数据,并确定了它们各自的行为。它更倾向于范围的作用,而不是我们前面讨论的上下文。请仔细区分执行环境和语境两个概念(注意:英文容易造成混淆)。老实说,这是一个非常糟糕的命名约定,但是它是由ECMAScript规范制定的,所以您应该遵守它。

每个函数都有自己的执行环境。当执行流进入函数时,函数的环境被推入执行堆栈。函数执行后,栈弹出它的环境,将控制权返回给前一个执行环境。ECMAScript程序中的执行流程由这种方便的机制控制。

执行环境可以分为两个阶段:创建和执行。在创建阶段,解析器首先创建一个变量对象(也称为激活对象),它由执行环境中定义的变量、函数声明和参数组成。在这个阶段,范围链将被初始化,其值将被最终确定。在执行阶段,代码被解释和执行。

每个执行环境都有一个与之关联的变量对象,其中存储了环境中定义的所有变量和函数。你知道,我们不能手动访问这个对象,只有解析器可以访问它。

范围链(范围链)

在环境中执行代码时,会创建变量对象的范围链。范围链的目的是确保有序地访问执行环境可以访问的所有变量和函数。范围链包含对应于环境堆栈中每个执行环境的变量对象。通过范围链,您可以决定变量的访问和标识符的解析。请注意,全局执行环境的变量对象始终是范围链的最后一个对象。让我们看一个例子:

var color=' bluefunction change COlOr(){ var other COlOr=' red ';函数swapColors(){ var tempColor=other color;anotherColor=colorcolor=tempColor//可以访问Color、otherColor和tempColor}//这里}//可以在这里访问color和otherColor,但是不能访问tempcolorsswapcolors();} ChangeColor();//此处只能访问color console . log(' color now '为' color ');上面的代码包括三个执行环境:全局环境、changeColor()的本地环境和swapColors()的本地环境。上述程序的范围链如下图所示:

从上图看。内部环境可以通过范围链访问所有外部环境,但是外部环境不能访问内部环境中的任何变量和函数。这些环境之间的关系是线性有序的。

标识符的解析(搜索变量名或函数名)是沿着作用域链逐级搜索标识符的过程。搜索过程总是从作用域链的前端开始,然后逐步返回(全局执行环境),直到找到标识符。

关闭

闭包是可以访问另一个函数范围内的变量的函数。换句话说,当一个嵌套函数被定义在一个函数中时,它形成一个闭包,允许嵌套函数访问外部函数的变量。通过返回嵌套函数,可以保持对外部函数中的局部变量、参数和内部函数声明的访问。这种封装允许您隐藏和保护外部范围内的执行环境,并公开公共接口,然后通过公共接口执行进一步的操作。看一个简单的例子:

function foo(){ var localVariable=' private variable ';return function bar(){ return LocalVariable;} } var GetLocalVariable=foo();getlocalvariable()//private variable模块模式是最流行的闭包类型之一,它允许您模拟公共、私有和特权成员:

var Module=(function(){ var private property=' foo ';function private method(args){//do某物}返回{publicProperty: ' ',public method : function(args){//do某物},privilegedmethod : function(args){ return private method(args);}};})();模块类似于单例对象。在上面的代码中,我们使用了(function() {).}) ();作为一个匿名函数,它将在编译器解析它时立即执行。闭包执行上下文之外唯一可访问的对象是返回对象中的公共方法和属性。但是,由于保存了执行上下文,所有私有属性和方法将始终存在于应用程序的整个生命周期中,这意味着我们只能通过公共方法与它们进行交互。

另一种类型的闭包叫做立即执行函数表达式。事实上,它非常简单,只是一个在全局环境中执行的匿名函数:

(函数(窗口){ var foo,barfunction private(){//do某物}窗口。module={ public : function(){//do某物} };})(这个);这个表达式对于保护全局命名空间不受变量影响非常有用。它通过构造函数作用域将变量与全局命名空间隔离开来,并通过关闭使它们存在于整个运行时中。在许多应用程序和框架中,这种封装源代码的方式非常流行,它通常通过公开单个全局接口与外部进行交互。

并调用应用

这两种方法内置于所有函数中(它们是函数对象的原型方法),允许您在自定义上下文中执行函数。区别在于调用函数需要一个参数列表,而apply函数需要您提供一个参数数组。如下所示:

var o={ };函数f(a,b){返回a b;}//把函数f当成o的方法,其实就是重置函数f的上下文f.call(o,1,2);//3f.apply(o,[1,2]);//3两个结果是一样的。函数f在对象o的上下文中被调用,并提供两个相同的参数1和2。

在ES5中引入了Function.prototype.bind方法来控制函数的执行上下文,该方法将返回一个新的函数,并且这个新的函数将永久绑定到bind方法的第一个参数所指定的对象上,无论该函数是如何使用的。它通过闭包将函数引入正确的上下文。对于较低版本的浏览器,我们可以简单地实现如下(polyfill):

if(!(“Function.prototype”中的“bind”){ function . prototype . bind=function(){ var fn=this,context=args[0],args=array . prototype . slice . call(arguments,1);return function(){ return fn . apply(context,args . concat(arguments));}}}bind()方法通常用于上下文丢失场景,例如面向对象和事件处理。这样做的原因是节点的addEventListener方法总是为事件处理程序绑定到的节点的上下文执行回调函数,这是它应该显示的。但是,如果您想使用高级面向对象技术或者需要回调函数作为方法的实例,您将需要手动调整上下文。这就是bind方法的便利之处:

函数MyClass(){ this . element=document . create element(' div ');this . element . addeventlistener(' click ',this.onClick.bind(this),false);} myclass . prototype . onclick=function(e){//do某物};回顾上面bind方法的源代码,您可能会注意到有两个调用涉及Array的slice方法:

Array.prototype.slice.call(参数,1);[].slice.call(参数);我们知道,参数对象不是一个真正的数组,而是一个类数组对象。虽然它具有长度属性,并且该值可以被索引,但是它们不支持本机数组方法,例如slice和push。但是,因为它们与数组有类似的行为,数组的方法可以被调用和劫持,所以我们可以用类似于上面代码的方式来实现这个目标,其核心是使用call方法。

这种调用其他对象方法的技术也可以应用于面向对象,我们可以模拟JavaScript中的经典继承:

my CLaSS . prototype . init=function(){//在‘my CLaSS’instanceMySuperCLaSS . prototype . init . apply的上下文中调用超类init方法(this,arguments);}也就是说,在MyClass的一个实例中使用call或apply来调用MySuperClass的方法。

ES6中的箭头功能。

ES6中的箭头函数可以用来替代Function.prototype.bind()。与普通函数不同,arrow函数没有自己的这个值,它的这个值是从外围作用域继承而来的。

对于普通函数,它总是自动接收这个的值,这个的方向取决于它被调用的方式。让我们看一个例子:

var obj={//.addAll:函数(件){ var self=this_.每个(件,功能(件){self.add(件);});},//.}在上面的例子中,最直接的想法是直接使用this.add(piece),但遗憾的是,您不能在JavaScript中这样做,因为每个的回调函数都没有从外层继承这个值。在这个回调函数中,这个的值是window或者undefined,所以我们用临时变量self把这个的外部值导入到内部。我们仍然有两种方法来解决这个问题:

在ES5中使用bind()方法。

var obj={//.addAll:函数(件){_。每个(件,功能(件){this.add(件);}.绑定(这个));},//.}使用ES6中的箭头功能。

var obj={//.addAll:函数(件){_。每件。},//.}在ES6版本中,addAll方法从它的调用者那里获取这个值,内部函数是一个箭头函数,所以它集成了外部作用域的这个值。

注意:对于回调函数,在浏览器中,这个in回调函数是window或者undefined(严格模式),而在Node.js中,这个in回调函数是全局的。示例代码如下:

函数hello(a,回调){ callback(a);}hello('weiwei ',函数(a){ console . log(this===global);//true console . log(a);//Weiwei });总结

在学习高级设计模式之前,理解这些概念非常重要,因为范围和上下文在现代JavaScript中扮演着最基本的角色。无论我们谈论的是闭包、面向对象、继承还是各种本机实现,上下文和范围在其中起着至关重要的作用。如果你的目标是掌握JavaScript语言并深入了解其组件,那么范围和上下文就是你的起点。

以上内容是边肖介绍的JavaScript的范围和上下文,希望对大家有所帮助!

版权声明:深入分析JavaScript中的范围和上下文是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。

相关文章推荐