图文详解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自动切换tabs选项卡的具体实现
Dec 24 Javascript
利用javascript数组长度循环数组内所有元素
Dec 27 Javascript
JavaScript保留关键字汇总
Dec 01 Javascript
使用jquery.qrcode.min.js实现中文转化二维码
Mar 11 Javascript
简单的JS轮播图代码
Jul 18 Javascript
React中ES5与ES6写法的区别总结
Apr 21 Javascript
使用vue中的v-for遍历二维数组的方法
Mar 07 Javascript
JavaScript深入V8引擎以及编写优化代码的5个技巧
Jun 24 Javascript
浅谈Vue3.0之前你必须知道的TypeScript实战技巧
Sep 11 Javascript
20多个小事例带你重温ES10新特性(小结)
Sep 29 Javascript
jquery css实现流程进度条
Mar 26 jQuery
javascript canvas时钟模拟器
Jul 13 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
改造一台复古桌面收音机
2021/03/02 无线电
AJAX for PHP简单表数据查询实例
2007/01/02 PHP
zend framework多模块多布局配置
2011/02/26 PHP
yii2中LinkPager增加总页数和总记录数的实例
2017/08/28 PHP
JavaScript 无符号右移赋值操作
2009/04/17 Javascript
Jquery Ajax.ashx 高效分页实现代码
2009/10/20 Javascript
Ext grid 添加右击菜单
2009/11/26 Javascript
jQuery AJAX回调函数this指向问题
2010/02/08 Javascript
JavaScript 判断指定字符串是否为有效数字
2010/05/11 Javascript
javascript模拟地球旋转效果代码实例
2013/12/02 Javascript
JQuery显示隐藏DIV的方法及代码实例
2015/04/16 Javascript
jQuery实现按钮点击遮罩加载及处理完后恢复的效果
2016/06/07 Javascript
学习Node.js模块机制
2016/10/17 Javascript
javascript中的replace函数(带注释demo)
2018/01/07 Javascript
一些你可能不熟悉的JS知识点总结
2019/03/15 Javascript
Vue页面切换和a链接的本质区别详解
2019/11/12 Javascript
Javascript执行流程细节原理解析
2020/05/14 Javascript
vue2.0 watch里面的 deep和immediate用法说明
2020/10/30 Javascript
nuxt.js 在middleware(中间件)中实现路由鉴权操作
2020/11/06 Javascript
通过python下载FTP上的文件夹的实现代码
2013/02/10 Python
详解Django中的权限和组以及消息
2015/07/23 Python
使用Python的Tornado框架实现一个Web端图书展示页面
2016/07/11 Python
Python实现多并发访问网站功能示例
2017/06/19 Python
Python 实现数据库(SQL)更新脚本的生成方法
2017/07/09 Python
Python闭包函数定义与用法分析
2018/07/20 Python
Python模拟自动存取款机的查询、存取款、修改密码等操作
2018/09/02 Python
解决运行django程序出错问题 'str'object has no attribute'_meta'
2020/07/15 Python
python help函数实例用法
2020/12/06 Python
意大利婴儿产品网上商店:Mukako
2018/10/14 全球购物
Calphalon美国官网:美国顶级锅具品牌
2020/02/05 全球购物
如何通过jdbc调用存储过程
2012/04/19 面试题
学习党的群众路线教育实践活动心得体会范文
2014/11/03 职场文书
儿子满月酒致辞
2015/07/29 职场文书
深入理解java.lang.String类的不可变性
2021/06/27 Java/Android
关于vue-router-link选择样式设置
2022/04/30 Vue.js
Li list-style-image 图片垂直居中实现方法
2023/05/21 HTML / CSS