深化浅析JavaScript中的作用域和上下文_

上传人:x** 文档编号:24979850 上传时间:2021-07-18 格式:DOCX 页数:16 大小:18.45KB
收藏 版权申诉 举报 下载
深化浅析JavaScript中的作用域和上下文__第1页
第1页 / 共16页
深化浅析JavaScript中的作用域和上下文__第2页
第2页 / 共16页
深化浅析JavaScript中的作用域和上下文__第3页
第3页 / 共16页
资源描述:

《深化浅析JavaScript中的作用域和上下文_》由会员分享,可在线阅读,更多相关《深化浅析JavaScript中的作用域和上下文_(16页珍藏版)》请在装配图网上搜索。

1、深化浅析JavaScript中的作用域和上下文_ javascript中的作用域(scope)和上下文(context)是这门语言的独到之处,这部分归功于他们带来的敏捷性。每个函数有不同的变量上下文和作用域。这些概念是javascript中一些强大的设计模式的后盾。然而这也给开发人员带来很大困惑。下面全面揭示了javascript中的上下文和作用域的不同,以及各种设计模式如何用法他们。 上下文(Context)和作用域(Scope) 首先需要知道的是,上下文和作用域是两个完全不同的概念。多年来,我发觉许多开发者会混淆这两个概念(包括我自己), 错误的将两个概念混淆了。平心而论,这些年来许多术语

2、都被混乱的用法了。 函数的每次调用都有与之紧密相关的作用域和上下文。从根本上来说,作用域是基于函数的,而上下文是基于对象的。 换句话说,作用域涉及到所被调用函数中的变量访问,并且不同的调用场景是不一样的。上下文始终是this关键字的值, 它是拥有(掌握)当前所执行代码的对象的引用。 变量作用域 一个变量可以被定义在局部或者全局作用域中,这建立了在运行时(runtime)期间变量的访问性的不同作用域范围。 任何被定义的全局变量,意味着它需要在函数体的外部被声明,并且存活于整个运行时(runtime),并且在任何作用域中都可以被访问到。 在ES6之前,局部变量只能存在于函数体中,并且函数的每次调用

3、它们都拥有不同的作用域范围。 局部变量只能在其被调用期的作用域范围内被赋值、检索、操纵。 需要留意,在ES6之前,JavaScript不支持块级作用域,这意味着在if语句、switch语句、for循环、while循环中无法支持块级作用域。 也就是说,ES6之前的JavaScript并不能构建类似于Java中的那样的块级作用域(变量不能在语句块外被访问到)。但是, 从ES6开头,你可以通过let关键字来定义变量,它修正了var关键字的缺点,能够让你像Java语言那样定义变量,并且支持块级作用域。看两个例子: ES6之前,我们用法var关键字定义变量: function func() if (tr

4、ue) var tmp = 123; console.log(tmp); / 123 之所以能够访问,是由于var关键字声明的变量有一个变量提升的过程。而在ES6场景,推举用法let关键字定义变量: function func() if (true) let tmp = 123; console.log(tmp); / ReferenceError: tmp is not defined 这种方式,能够避开许多错误。 什么是this上下文 上下文通常取决于函数是如何被调用的。当一个函数被作为对象中的一个方法被调用的时候,this被设置为调用该方法的对象上: var obj = foo: fun

5、ction() alert(this = obj); ; obj.foo(); / true 这个准则也适用于当调用函数时用法new操作符来创建对象的实例的状况下。在这种状况下,在函数的作用域内部this的值被设置为新创建的实例: function foo() alert(this); new foo() / foo foo() / window 当调用一个为绑定函数时,this默认状况下是全局上下文,在扫瞄器中它指向window对象。需要留意的是,ES5引入了严格模式的概念, 假如启用了严格模式,此时上下文默认为undefined。 执行环境(execution context) JavaS

6、cript是一个单线程语言,意味着同一时间只能执行一个任务。当JavaScript说明器初始化执行代码时, 它首先默认进入全局执行环境(execution context),从今刻开头,函数的每次调用都会创建一个新的执行环境。 这里会常常引起新手的困惑,这里提到了一个新的术语执行环境(execution context),它定义了变量或函数有权访问的其他数据,决定了它们各自的行为。 它更偏向于作用域的作用,而不是我们前面商量的上下文(Context)。请务必认真的区分执行环境和上下文这两个概念(注:英文简单造成混淆)。 说实话,这是个特别糟糕的命名商定,但是它是ECMAScript规范制定的,

7、你还是遵守吧。 每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中(execution stack)。在函数执行完后,栈将其环境弹出, 把掌握权返回给之前的执行环境。ECMAScript程序中的执行流正是由这个便利的机制掌握着。 执行环境可以分为创建和执行两个阶段。在创建阶段,解析器首先会创建一个变量对象(variable object,也称为活动对象 activation object), 它由定义在执行环境中的变量、函数声明、和参数组成。在这个阶段,作用域链会被初始化,this的值也会被最终确定。 在执行阶段,代码被说明执行。 每个执行环境都有一个与之关联

8、的变量对象(variable object),环境中定义的全部变量和函数都保存在这个对象中。 需要知道,我们无法手动访问这个对象,只有解析器才能访问它。 作用域链(The Scope Chain) 当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain)。作用域链的用途是保证对执行环境有权访问的全部变量和函数的有序访问。 作用域链包含了在环境栈中的每个执行环境对应的变量对象。通过作用域链,可以决定变量的访问和标识符的解析。 留意,全局执行环境的变量对象始终都是作用域链的最终一个对象。我们来看一个例子: var color = blue; function changeC

9、olor() var anotherColor = red; function swapColors() var tempColor = anotherColor; anotherColor = color; color = tempColor; / 这里可以访问color, anotherColor, 和 tempColor / 这里可以访问color 和 anotherColor,但是不能访问 tempColor swapColors(); changeColor(); / 这里只能访问color console.log(Color is now + color); 上述代码一共包括三个执

10、行环境:全局环境、changeColor()的局部环境、swapColors()的局部环境。 上述程序的作用域链如下图所示: 从上图发觉。内部环境可以通过作用域链访问全部的外部环境,但是外部环境不能访问内部环境中的任何变量和函数。 这些环境之间的联系是线性的、有次序的。 对于标识符解析(变量名或函数名搜索)是沿着作用域链一级一级地搜索标识符的过程。搜索过程始终从作用域链的前端开头, 然后逐级地向后(全局执行环境)回溯,直到找到标识符为止。 闭包 闭包是指有权访问另一函数作用域中的变量的函数。换句话说,在函数内定义一个嵌套的函数时,就构成了一个闭包, 它允许嵌套函数访问外层函数的变量。通过返回嵌

11、套函数,允许你维护对外部函数中局部变量、参数、和内函数声明的访问。 这种封装允许你在外部作用域中隐蔽和爱护执行环境,并且暴露公共接口,进而通过公共接口执行进一步的操作。可以看个简洁的例子: function foo() var localVariable = private variable; return function bar() return localVariable; var getLocalVariable = foo(); getLocalVariable() / private variable 模块模式最流行的闭包类型之一,它允许你模拟公共的、私有的、和特权成员: var

12、Module = (function() var privateProperty = foo; function privateMethod(args) / do something return publicProperty: , publicMethod: function(args) / do something , privilegedMethod: function(args) return privateMethod(args); ; )(); 模块类似于一个单例对象。由于在上面的代码中我们利用了(function() . )();的匿名函数形式,因此当编译器解析它的时候会立刻执行

13、。 在闭包的执行上下文的外部唯一可以访问的对象是位于返回对象中的公共方法和属性。然而,由于执行上下文被保存的原因, 全部的私有属性和方法将始终存在于应用的整个生命周期,这意味着我们只有通过公共方法才可以与它们交互。 另一种类型的闭包被称为立刻执行的函数表达式(IIFE)。其实它很简洁,只不过是一个在全局环境中自执行的匿名函数而已: (function(window) var foo, bar; function private() / do something window.Module = public: function() / do something ; )(this); 对于爱护全局

14、命名空间免受变量污染而言,这种表达式特别有用,它通过构建函数作用域的形式将变量与全局命名空间隔离, 并通过闭包的形式让它们存在于整个运行时(runtime)。在许多的应用和框架中,这种封装源代码的方式用处特别的流行, 通常都是通过暴露一个单一的全局接口的方式与外部进行交互。 Call和Apply 这两个方法内建在全部的函数中(它们是Function对象的原型方法),允许你在自定义上下文中执行函数。 不同点在于,call函数需要参数列表,而apply函数需要你供应一个参数数组。如下: var o = ; function f(a, b) return a + b; / 将函数f作为o的方法,事实

15、上就是重新设置函数f的上下文 f.call(o, 1, 2); / 3 f.apply(o, 1, 2); / 3 两个结果是相同的,函数f在对象o的上下文中被调用,并供应了两个相同的参数1和2。 在ES5中引入了Function.prototype.bind方法,用于掌握函数的执行上下文,它会返回一个新的函数, 并且这个新函数会被永久的绑定到bind方法的第一个参数所指定的对象上,无论该函数被如何用法。 它通过闭包将函数引导到正确的上下文中。对于低版本扫瞄器,我们可以简洁的对它进行实现如下(polyfill): if(!(bind in Function.prototype) Functio

16、n.prototype.bind = function() var fn = this, context = arguments0, args = Array.prototype.slice.call(arguments, 1); return function() return fn.apply(context, args.concat(arguments); bind()方法通常被用在上下文丢失的场景下,例如面对对象和大事处理。之所以要这么做, 是由于节点的addEventListener方法总是为大事处理器所绑定的节点的上下文中执行回调函数, 这就是它应当表现的那样。但是,假如你想要用法

17、高级的面对对象技术,或需要你的回调函数成为某个方法的实例, 你将需要手动调整上下文。这就是bind方法所带来的便利之处: function MyClass() this.element = document.createElement(div); this.element.addEventListener(click, this.onClick.bind(this), false); MyClass.prototype.onClick = function(e) / do something ; 回顾上面bind方法的源代码,你可能会留意到有两次调用涉及到了Array的slice方法: Arr

18、ay.prototype.slice.call(arguments, 1); .slice.call(arguments); 我们知道,arguments对象并不是一个真正的数组,而是一个类数组对象,虽然具有length属性,并且值也能够被索引, 但是它们不支持原生的数组方法,例如slice和push。但是,由于它们具有和数组类似的行为,数组的方法能够被调用和劫持, 因此我们可以通过类似于上面代码的方式达到这个目的,其核心是利用call方法。 这种调用其他对象方法的技术也可以被应用到面对对象中,我们可以在JavaScript中模拟经典的继承方式: MyClass.prototype.init

19、= function() / call the superclass init method in the context of the MyClass instance MySuperClass.prototype.init.apply(this, arguments); 也就是利用call或apply在子类(MyClass)的实例中调用超类(MySuperClass)的方法。 ES6中的箭头函数 ES6中的箭头函数可以作为Function.prototype.bind()的替代品。和一般函数不同,箭头函数没有它自己的this值, 它的this值继承自外围作用域。 对于一般函数而言,它总会自

20、动接收一个this值,this的指向取决于它调用的方式。我们来看一个例子: var obj = / . addAll: function (pieces) var self = this; _.each(pieces, function (piece) self.add(piece); ); , / . 在上面的例子中,最挺直的想法是挺直用法this.add(piece),但不幸的是,在JavaScript中你不能这么做, 由于each的回调函数并未从外层继承this值。在该回调函数中,this的值为window或undefined, 因此,我们用法临时变量self来将外部的this值导入内部

21、。我们还有两种方法解决这个问题: 用法ES5中的bind()方法 var obj = / . addAll: function (pieces) _.each(pieces, function (piece) this.add(piece); .bind(this); , / . 用法ES6中的箭头函数 var obj = / . addAll: function (pieces) _.each(pieces, piece = this.add(piece); , / . 在ES6版本中,addAll方法从它的调用者处获得了this值,内部函数是一个箭头函数,所以它集成了外部作用域的this值

22、。 留意:对回调函数而言,在扫瞄器中,回调函数中的this为window或undefined(严格模式),而在Node.js中, 回调函数的this为global。实例代码如下: function hello(a, callback) callback(a); hello(weiwei, function(a) console.log(this = global); / true console.log(a); / weiwei ); 小结 在你学习高级的设计模式之前,理解这些概念特别的重要,由于作用域和上下文在现代JavaScript中扮演着的最基本的角色。 无论我们谈论的是闭包、面对对象、继承、或者是各种原生实现,上下文和作用域都在其中扮演着至关重要的角色。 假如你的目标是精通JavaScript语言,并且深化的理解它的各个组成,那么作用域和上下文便是你的起点。 以上内容是我给大家介绍的JavaScript中的作用域和上下文,盼望对大家有所关心 ! .

展开阅读全文
温馨提示:
1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
2: 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
3.本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 装配图网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
关于我们 - 网站声明 - 网站地图 - 资源地图 - 友情链接 - 网站客服 - 联系我们

copyright@ 2023-2025  zhuangpeitu.com 装配图网版权所有   联系电话:18123376007

备案号:ICP2024067431-1 川公网安备51140202000466号


本站为文档C2C交易模式,即用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。装配图网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知装配图网,我们立即给予删除!