图文详解Javascript中的上下文和作用域


Posted in Javascript onFebruary 15, 2017

执行上下文(Execution context)

执行上下文(简称上下文)决定了Js执行过程中可以获取哪些变量、函数、数据,一段程序可能被分割成许多不同的上下文,每一个上下文都会绑定一个变量对象(variable object),它就像一个容器,用来存储当前上下文中所有已定义或可获取的变量、函数等。位于最顶端或最外层的上下文称为全局上下文(global context),全局上下文取决于执行环境,如Node中的global和Browser中的window:

图文详解Javascript中的上下文和作用域

需要注意的是,上下文与作用域(scope)是不同的概念。Js本身是单线程的,每当有function被执行时,就会产生一个新的上下文,这一上下文会被压入Js的上下文堆栈(context stack)中,function执行结束后则被弹出,因此Js解释器总是在栈顶上下文中执行。在生成新的上下文时,首先会绑定该上下文的变量对象,其中包括arguments和该函数中定义的变量;之后会创建属于该上下文的作用域链(scope chain),最后将this赋予这一function所属的Object,这一过程可以通过下图表示:

图文详解Javascript中的上下文和作用域

this

上文提到this被赋予function所属的Object,具体来说,当function是定义在global对中时,this指向global;当function作为Object的方法时,this指向该Object:

var x = 1; 
var f = function(){ 
 console.log(this.x);
}
f(); // -> 1

var ff = function(){ 
 this.x = 2;
 console.log(this.x);
}
ff(); // -> 2 
x  // -> 2

var o = {x: "o's x", f: f}; 
o.f(); // "o's x"

Scope chain

上文提到,在function被执行时生成新的上下文时会先绑定当前上下文的变量对象,再创建作用域链。我们知道function的定义是可以嵌套在其他function所创建的上下文中,也可以并列地定义在同一个上下文中(如global)。作用域链实际上就是自下而上地将所有嵌套定义的上下文所绑定的变量对象串接到一起,使嵌套的function可以“继承”上层上下文的变量,而并列的function之间互不干扰:

图文详解Javascript中的上下文和作用域

var x = 'global'; 
function a(){ 
 var x = "a's x";
 function b(){
 var y = "b's y";
 console.log(x);
 };
 b();
}
function c(){ 
 var x = "c's x";
 function d(){
 console.log(y);
 };
 d();
}
a(); // -> "a's x" 
c(); // -> ReferenceError: y is not defined 
x  // -> "global" 
y  // -> ReferenceError: y is not defined

Closure

如果理解了上文中提到的上下文与作用域链的机制,再来看闭包的概念就很清楚了。每个function在调用时会创建新的上下文及作用域链,而作用域链就是将外层(上层)上下文所绑定的变量对象逐一串连起来,使当前function可以获取外层上下文的变量、数据等。如果我们在function中定义新的function,同时将内层function作为值返回,那么内层function所包含的作用域链将会一起返回,即使内层function在其他上下文中执行,其内部的作用域链仍然保持着原有的数据,而当前的上下文可能无法获取原先外层function中的数据,使得function内部的作用域链被保护起来,从而形成“闭包”。

看下面的例子:

var x = 100; 
var inc = function(){ 
 var x = 0;
 return function(){
 console.log(x++);
 };
};

var inc1 = inc(); 
var inc2 = inc();

inc1(); // -> 0 
inc1(); // -> 1 
inc2(); // -> 0 
inc1(); // -> 2 
inc2(); // -> 1 
x;  // -> 100

执行过程如下图所示,inc内部返回的匿名function在创建时生成的作用域链包括了inc中的x,即使后来赋值给inc1和inc2之后,直接在global context下调用,它们的作用域链仍然是由定义中所处的上下文环境决定,而且由于x是在function inc中定义的,无法被外层的global context所改变,从而实现了闭包的效果:

图文详解Javascript中的上下文和作用域

this in closure

我们已经反复提到执行上下文和作用域实际上是通过function创建、分割的,而function中的this与作用域链不同,它是由执行该function时当前所处的Object环境所决定的,这也是this最容易被混淆用错的一点。一般情况下的例子如下:

var name = "global"; 
var o = { 
 name: "o",
 getName: function(){
 return this.name
 }
};
o.getName(); // -> "o"

由于执行o.getName()时getName所绑定的this是调用它的o,所以此时this == o;更容易搞混的是在closure条件下:

var name = "global"; 
var oo = { 
 name: "oo",
 getNameFunc: function(){
 return function(){
  return this.name;
 };
 }
}
oo.getNameFunc()(); // -> "global"

此时闭包函数被return后调用相当于:

getName = oo.getNameFunc(); 
getName(); // -> "global"

换一个更明显的例子:

var ooo = { 
 name: "ooo",
 getName: oo.getNameFunc() // 此时闭包函数的this被绑定到新的Object
};
ooo.getName(); // -> "ooo"

当然,有时候为了避免闭包中的this在执行时被替换,可以采取下面的方法:

var name = "global"; 
var oooo = { 
 name: "ox4",
 getNameFunc: function(){
 var self = this;
 return function(){
  return self.name;
 };
 }
};
oooo.getNameFunc()(); // -> "ox4"

或者是在调用时强行定义执行的Object:

var name = "global"; 
var oo = { 
 name: "oo",
 getNameFunc: function(){
 return function(){
  return this.name;
 };
 }
}
oo.getNameFunc()(); // -> "global" 
oo.getNameFunc().bind(oo)(); // -> "oo"

总结

Js是一门很有趣的语言,由于它的很多特性是针对HTML中DOM的操作,因而显得随意而略失严谨,但随着前端的不断繁荣发展和Node的兴起,Js已经不再是"toy language"或是jQuery时代的"CSS扩展",本文提到的这些概念无论是对新手还是从传统Web开发中过度过来的Js开发人员来说,都很容易被混淆或误解,希望本文对大家能有所帮助。

Javascript 相关文章推荐
jQuery 名称冲突的解决方法
Apr 08 Javascript
js select option对象小结
Dec 20 Javascript
Node.js安装教程和NPM包管理器使用详解
Aug 16 Javascript
jQuery中parents()方法用法实例
Jan 07 Javascript
jQuery实现瀑布流的取巧做法分享
Jan 12 Javascript
javascript控制层显示或隐藏的方法
Jul 22 Javascript
利用React-router+Webpack快速构建react程序
Oct 27 Javascript
JavaScript实现经典排序算法之冒泡排序
Dec 28 Javascript
vue2.0构建单页应用最佳实战
Apr 01 Javascript
JS判断微信扫码的方法
Aug 07 Javascript
使用jQuery实现页面定时弹出广告效果
Aug 24 jQuery
js异步接口并发数量控制的方法示例
Nov 22 Javascript
js实现省份下拉菜单效果
Feb 15 #Javascript
JavaScript函数节流和函数防抖之间的区别
Feb 15 #Javascript
javascript滚轮事件基础实例讲解(37)
Feb 14 #Javascript
基于canvas的二维码邀请函生成插件
Feb 14 #Javascript
javascript事件的绑定基础实例讲解(34)
Feb 14 #Javascript
javascript深拷贝和浅拷贝详解
Feb 14 #Javascript
javascript事件的传播基础实例讲解(35)
Feb 14 #Javascript
You might like
ThinkPHP模板自定义标签使用方法
2014/06/26 PHP
PHP实现一维数组与二维数组去重功能示例
2018/05/24 PHP
PHP实现用session来实现记录用户登陆信息
2018/10/15 PHP
用脚本调用样式的几种方法
2006/12/09 Javascript
javascript 操作文件 实现方法小结
2009/07/02 Javascript
javascript中的array数组使用技巧
2010/01/31 Javascript
JQuery 自定义CircleAnimation,Animate方法学习笔记
2011/07/10 Javascript
jquery Mobile入门—多页面切换示例学习
2013/01/08 Javascript
使用jQuery快速解决input中placeholder值在ie中无法支持的问题
2014/01/02 Javascript
悬浮数字的实现案例
2014/02/19 Javascript
IE中document.createElement的iframe无法设置属性name的解决方法
2015/09/14 Javascript
vue.js绑定class和style样式(6)
2016/12/09 Javascript
Node.JS更改Windows注册表Regedit的方法小结
2017/08/18 Javascript
JavaScript面向对象精要(上部)
2017/09/12 Javascript
利用JS hash制作单页Web应用的方法详解
2017/10/10 Javascript
js实现简易计算器功能
2019/10/18 Javascript
Taro UI框架开发小程序实现左滑喜欢右滑不喜欢效果的示例代码
2020/05/18 Javascript
浅谈vue 二级路由嵌套和二级路由高亮问题
2020/08/06 Javascript
JQuery使用数组遍历跳出each循环
2020/09/01 jQuery
vue-router懒加载的3种方式汇总
2021/02/28 Vue.js
[51:36]EG vs VP 2018国际邀请赛淘汰赛BO3 第一场 8.24
2018/08/25 DOTA
在Linux中通过Python脚本访问mdb数据库的方法
2015/05/06 Python
python实现飞机大战
2018/09/11 Python
Django restframework 源码分析之认证详解
2019/02/22 Python
Python绘制三角函数图(sin\cos\tan)并标注特定范围的例子
2019/12/04 Python
python+flask编写一个简单的登录接口
2020/11/13 Python
python UIAutomator2使用超详细教程
2021/02/19 Python
python利用后缀表达式实现计算器功能
2021/02/22 Python
英国领先的狗和宠物美容专家:Christies Direct
2017/04/03 全球购物
加拿大领先的优质厨具产品在线购物网站:Golda’s Kitchen
2017/11/17 全球购物
会计与审计毕业生自荐信范文
2013/12/30 职场文书
三年大学自我鉴定
2014/01/16 职场文书
护士检查书
2014/01/17 职场文书
给老婆的婚前保证书
2014/02/01 职场文书
工作时间上网检讨书
2014/02/03 职场文书
企业业务员岗位职责
2014/03/14 职场文书