摘要:正文执行环境也称为环境是中最为重要的一个概念。执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。简而言之,执行环境是基于对象的,而作用域是基于函数的。
前述
在我们学习Javascript过程中,常常会遇到作用域(Scope)和执行上下文(Context)等概念。其中,执行上下文与this关键字的关系密切。
有面向对象编程经验的各位,对于this关键字再熟悉不过了,因此我们很容易地把它和面向对象的编程方式联系在一起,它指向利用构造器新创建出来的对象;在ECMAScript中,也支持this,然而, 正如大家所熟知的,this不仅仅只用来表示创建出来的对象。
在接下来的博文我们讲介绍Javascript的作用域和执行上下文,以及它们的异同之处。
正文
执行环境(Execution context)也称为“环境”是Javascript中最为重要的一个概念。执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。
看到了执行环境的定义有点头昏了,简而言之“每个执行环境都有一个与之关联的变量对象”;这里我们有一个疑问就是这个变量对象是怎样定义的呢?
接下来,让我们看一下变量对象的定义,具体实现如下:
/** * Execution context skeleton. */ activeExecutionContext = { // variable object. VO: {...}, this: thisValue };
通过上面的伪代码我们知道对象字面量activeExecutionContext,它包含一个变量对象VO和this属性。
这说明了this与上下文的可执行代码类型有关,其值在进入上下文阶段就确定了,并且在执行代码阶段是不能改变的(关于this使用可以阅读《Javascript this 的一些学习总结》)。
作用域(Scope)控制着变量和参数的可见性及生命周期。
简而言之,执行环境是基于对象的,而作用域是基于函数的。
作用域
我们将通过一个例子介绍作用域的使用,首先,我们定义了一个函数FooA()和FooB,示例代码如下:
/** * Defines a function. */ var FooA = function(){ var a = 1; var FooB = function(){ var b = 2; console.log(a, b); // outputs: 1, 2 } console.log(a, b); // Error! b is not defined } FooA();
在示例中,第二个log输出变量为未定义,这是由于在Javascript中定义在函数里面的参数和变量在函数外部是不可见的,而在一个函数内部任何位置定义的参数和变量,在该函数内部任何地方都是可见的。
执行环境
首先,我们定义了对象字面量o,它包含一个属性x和方法m(),示例代码如下:
/** * Defines a literal object. * @type {Object} */ var o = { x:23, m: function(){ var x = 1; console.log(x, this.x); // outputs 1, 23 } } o.m();
示例中的两个变量和属性x都能被访问,但它们被访问的方式是截然不同,在log中访问第一个x是通过作用域方式访问了本地变量x,而this.x是通过执行上下文方式访问对象o的属性x,因此输出值也不尽相同。
上下文问题
接下来,我们修改一下前面的例子,在方法m()中添加一个函数f(),示例代码如下:
/** * Defines a literal object. * @type {Object} */ var o = { x:23, m: function(){ var x = 1; var f = function(){ console.log(x, this.x); // outputs 1, undefined } f(); } } o.m();
上面,我们通过调用方法m()来输出x的值,由于方法m()的具体实现是通过调用函数f()来实现。
当我们调用对象o的方法m()时,发现this.x是未定义的。
这究竟是什么原因呢?回忆前面的例子,由于方法m()获取了对象o的上下文,所以this是指向对象o的,难道是函数f()没有获取对象o的上下文,因此它不清楚this指向哪个对象?
首先让我们回顾一下函数和方法以及属性和变量的区别:方法和对象关联,如:object.myMethod = function() {},而函数非对象关联:var myFunc = function {};同样属性也是对象关系的,如:object.myProperty = 23,而变量:var myProperty = 23。
因为我们提到上下文是基于对象的,所以函数f()不能获取对象o的执行上下文。
我们是否可以让函数f()获取对象o的执行上下文呢?我们仔细地想一下,既然函数f()不清楚this指向的对象,那么可以直接调用对象的属性就OK了。
/** * Fixs broken context issue. * @type {Object} */ var o = { x:23, m: function(){ var x = 1; var f = function(){ console.log(x, o.x); // outputs 1, 23 } f(); } } o.m();
我们在函数f()中直接调用对象o的属性x,这样函数f()就无需获取执行上下文直接调用对象的属性了。
现在,我们又遇到一个新的问题了,如果对象不是o而是p,那么我们就需要修改函数f()中的对象了,更严重的情况就是我们没有办法确定具体是哪个对象,示例代码如下:
/** * Defines a literal object. * @constructor */ var C = function(){} C.prototype = { x:23, m: function(){ var x = 1; var f = function(){ console.log(x, this.x); // outputs 1, undefined } f(); } } var instance1 = new C(); instance1.m();
上下文实例问题
上面,我们定义了函数C和它的原型对象,而且我们可以通过new方式创建C对象实例instance1,按照前面的方法解决Broken Context问题,具体实现如下:
/** * Defines a literal object. * @constructor */ var C = function(){} C.prototype = { x:23, m: function(){ var x = 1; var f = function(){ console.log(x, instance1.x); // outputs 1, undefined } f(); } } var instance1 = new C(); instance1.m();
如果我们在创建一个C的对象实例instance2,那么我们就不能指定函数f()中的对象了。
其实,this是对象实例的抽象,当实例有多个甚至成千上百个的时候,我们需要通过this引用这些对象实例。
因此,指定对象方法不能有效解决Broken Context问题,我们还是需要使用this来引用对象,前面我们讲到由于函数f()没有获取对象o的执行上下文,因此它不清楚this指向哪个对象,所以输出this.x未定义,那么我们是否可以让函数f()获取对象的执行上下文。
跨作用域的上下文
我们想想既然方法是基于对象的,而且可以获取对象的执行上下文,那么我们直接把f()定义为方法好了。
现在,我们在C对象原型中定义方法f(),示例代码如下:
/** * Defines a literal object. * @constructor */ var C = function(){} C.prototype = { x:10, m: function(){ var x = 1; this.f(); }, f: function(){ console.log(x, this.x); // Reference ERROR!! } } var instance1 = new C(); instance1.m();
好啦,我们在C对象原型中定义方法f(),那么方法f()就可以获取对象的执行上下文。
现在,我们在Firefox运行以上代码,结果输出Reference ERROR,这究竟是什么原因呢?我们想了一下问题出于变量x中,由于方法f()不能获取方法m()的作用域,所以变量x不在方法f()中。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/88099.html
摘要:总结本博文通过介绍执行上下文和作用域的异同的使用以及变量对象,让我们加深对语言特性的理解。首先,我们介绍了执行上下文和的的关系,并且执行上下文是具有对象的然后,介绍了作用域使变量在作用域范围内可见,并且作用域是基于函数的。 接上一篇Javascript Context和Scope的学习总结01【转自cnblogs的JKhuang】(可能是segmentfault对单篇文章发布字数有限制...
摘要:函数上下文中的值是函数调用者提供并且由当前调用表达式的形式而定的。然而,由于对于来说没有任何意义,因此会隐式转换为全局对象。这里注意到四个表达式中,只有第一个表达式是指向对象的,而其他三个表达式则执行。 摘要 相信有C++、C#或Java等编程经验的各位,对于this关键字再熟悉不过了。由于Javascript是一种面向对象的编程语言,它和C++、C#或Java一样都包含this关键字...
摘要:发生这种情况的条件是当引用类型值的对象恰好为活跃对象。总结本文介绍中的使用,更重要的是帮助我们能更好地理解值在全局函数构造函数以及一些特例的情况中值的变化。然而,由于对于来说没有任何意义,因此会隐式转换为全局对象。 接上一篇Javascript this 的一些学习总结02【转自cnblogs的JKhuang】 引用类型以及this的null值 对于前面提及的情形,还有例外的情况,当调...
摘要:正式由于作用域链的这种关系,我们就不难理解,为什么和不能通过作用域链向上搜索,因为对和的搜索在当前执行函数的活动对象就停止了。 对于Javascript程序员来说,闭包总会让你觉得既熟悉又陌生,然而它对于开发人员来说却非常重要,javascript里的许多设计模式中都用到了闭包,此处以函数作用域为例。 //示例代码 var a=1; function foo(){ ...
摘要:此时的作用域链包含了两个对象的活动对象和对象。闭包的应用场景保护函数内的变量安全。依然如前例,由于闭包,函数中的一直存在于内存中,因此每次执行,都会给自加。 引子 JS的闭包一直是很多人不理解,也是在使用过程中经常出现问题的地方。每次看文章都会有所了解闭包,但是,用起来还是不对,而且错误百出,其关键问题还是出在对其不理解,不了解。此文章会不定期更新以及完善,希望在我学习的时候,让大家也...
阅读 2506·2023-04-25 17:37
阅读 1184·2021-11-24 10:29
阅读 3695·2021-09-09 11:57
阅读 689·2021-08-10 09:41
阅读 2240·2019-08-30 15:55
阅读 2806·2019-08-30 15:54
阅读 1936·2019-08-30 15:53
阅读 893·2019-08-30 15:43