图文详解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 相关文章推荐
在IE下:float属性会影响offsetTop的取值
Dec 22 Javascript
防止网站内容被拷贝的一些方法与优缺点好处与坏处分析
Nov 30 Javascript
JS幻灯片可循环播放可平滑旋转带滚动导航(自写)
Aug 05 Javascript
IE浏览器IFrame对象内存不释放问题解决方法
Aug 22 Javascript
AngularJS手动表单验证
Feb 01 Javascript
使用mint-ui实现省市区三级联动效果的示例代码
Feb 09 Javascript
详解Vue中watch的高级用法
May 02 Javascript
p5.js绘制旋转的正方形
Oct 23 Javascript
JavaScript实现京东放大镜效果
Dec 03 Javascript
vue 使用插槽分发内容操作示例【单个插槽、具名插槽、作用域插槽】
Mar 06 Javascript
js 解析 JSON 数据简单示例
Apr 21 Javascript
Element Alert警告的具体使用方法
Jul 27 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
PHP网站开发中常用的8个小技巧
2015/02/13 PHP
php图片上传类 附调用方法
2016/05/15 PHP
thinkphp整合微信支付代码分享
2016/11/24 PHP
关于JS中的闭包浅谈
2013/08/23 Javascript
js showModalDialog参数的使用详解
2014/01/07 Javascript
把jQuery的类、插件封装成seajs的模块的方法
2014/03/12 Javascript
javascript中String对象的slice()方法分析
2014/12/20 Javascript
浅析javascript 定时器
2014/12/23 Javascript
Javascript无参数和有参数类继承问题解决方法
2015/03/02 Javascript
AngularJS 简单应用实例
2016/07/28 Javascript
实现JavaScript高性能的数据存储
2016/12/11 Javascript
jQuery点击弹出层弹出模态框点击模态框消失代码分享
2017/01/21 Javascript
微信小程序tabBar底部导航中文注解api详解
2017/08/16 Javascript
weui框架实现上传、预览和删除图片功能代码
2017/08/24 Javascript
[36:33]完美世界DOTA2联赛PWL S2 LBZS vs Forest 第二场 11.29
2020/12/02 DOTA
解决Python出现_warn_unsafe_extraction问题的方法
2016/03/24 Python
python利用正则表达式搜索单词示例代码
2017/09/24 Python
Python中的延迟绑定原理详解
2019/10/11 Python
对tensorflow中cifar-10文档的Read操作详解
2020/02/10 Python
Python getsizeof()和getsize()区分详解
2020/11/20 Python
使用 css3 transform 属性来变换背景图的方法
2019/05/07 HTML / CSS
关于HTML5+ API plusready的兼容问题
2020/11/20 HTML / CSS
澳大利亚拥有最好的家具和家居用品在线目的地:Nestz
2019/02/23 全球购物
可持续木材、生态和铝制太阳镜:Proof Eyewear
2019/07/24 全球购物
既然说Ruby中一切都是对象,那么Ruby中类也是对象吗
2013/01/26 面试题
高一地理教学反思
2014/01/18 职场文书
仓库规划计划书
2014/04/28 职场文书
企业总经理任命书
2014/06/05 职场文书
北京离婚协议书范文2014
2014/09/29 职场文书
乡镇群众路线专项整治方案
2014/11/03 职场文书
二审答辩状范文
2015/05/22 职场文书
《普罗米修斯》教学反思
2016/02/22 职场文书
使用canvas实现雪花飘动效果的示例代码
2021/03/30 HTML / CSS
在CSS中映射鼠标位置并实现通过鼠标移动控制页面元素效果(实例代码)
2021/04/22 HTML / CSS
python通配符之glob模块的使用详解
2021/04/24 Python
简单总结SpringMVC拦截器的使用方法
2021/06/28 Java/Android