本文尝试阐述Javascript中的上下文与作用域背后的机制,主要涉及到执行上下文(execution context)、作用域链(scope chain)、闭包(closure)、this等概念。

Execution context

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

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

this

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

 
 
 
 
 

JavaScript

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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内部的作用域链被保护起来,从而形成“闭包”。看下面的例子:

 
 
 
 
 

JavaScript

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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,即使后来赋值给inc1inc2之后,直接在global context下调用,它们的作用域链仍然是由定义中所处的上下文环境决定,而且由于x是在function inc中定义的,无法被外层的global context所改变,从而实现了闭包的效果:

this in closure

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

 
 
 
 
 

JavaScript

 
1
2
3
4
5
6
7
8
var name = "global";  
var o = {  
  name: "o",
  getName: function(){
    return this.name
  }
};
o.getName();  // -> "o"

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

 
 
 
 
 

JavaScript

 
1
2
3
4
5
6
7
8
9
10
var name = "global";  
var oo = {  
  name: "oo",
  getNameFunc: function(){
    return function(){
      return this.name;
    };
  }
}
oo.getNameFunc()();  // -> "global"

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

 
 
 
 
 

JavaScript

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

换一个更明显的例子:

 
 
 
 
 

JavaScript

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

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

 
 
 
 
 

JavaScript

 
1
2
3
4
5
6
7
8
9
10
11
var name = "global";  
var oooo = {  
  name: "ox4",
  getNameFunc: function(){
    var self = this;
    return function(){
       return self.name;
    };
  }
};
oooo.getNameFunc()(); // -> "ox4"

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

 
 
 
 
 

JavaScript

 
1
2
3
4
5
6
7
8
9
10
11
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开发人员来说,都很容易被混淆或误解,希望本文可以有所帮助。

写这篇总结的原因是我在Github上分享的Learn javascript in one picture,刚开始有人质疑这只能算是一张语法表(syntax cheat sheet),根本不会涉及更深层的闭包、作用域等内容,但是出乎意料的是这个项目竟然获得3000多个star,所以不能虎头蛇尾,以上。

References

  1. Understanding Scope and Context in JavaScript
  2. this – JavaScript | MDN
  3. 闭包 – JavaScript | MDN
如果有人让你推荐前端技术书,请让他看这个列表 ->《经典前端技术书籍
From: http://web.jobbole.com/83031/
 

理解上下文与作用域

 

一直以来没有对上下文(context)的定义,总有些模糊,没有认真地总结过,听到这个词,会想到看过的“上下文是函数执行环境”,“上下文是this”,但真的让自己去解释还真说不上来,故趁今日有空总结温故一番,有什么错误的,还望大家提出!

《javascript权威指南》里是第8章函数导读页里有这样介绍到:

函数使用它们实参的值 来计算返回值,成为该函数调用表达式的值。除了实参之外,每次调用还会拥有另外一个值——本次调用的上下文——这就是this关键字的传值。

如果函数挂载在一个对象上,作为对象的一个属性,就称它为对象的方法。当通过这个对象来调用函数时,该对象就是此次调用上下文(context),也就是该函数的this的值 。用于初始化一个新创建的对象的函数称为构造函数。

写一段简单的代码理解这段话:

//定义一个对象
var context = {
foo: "bar",
func : function(){
console.log(this);//打印上下文
return this.foo;
}
};
//当通过这个对象来调用函数时,该对象就是此次调用上下文(context),也就是该函数的this的值
context.func();

控制台输出结果:

this代码了该函数的上下文(context),即对象context本身;

作用域:函数的执行依赖于变量作用域,这个作用域是在函数定义时决定的,而不是函数调用时决定的。

因为没理解好这条规则,故前两天晚上看了篇文章的示例没能理解,一直想不通 T T

示例:( 一直在想为什么第二个bar输出的是1,不是2 )

function foo(){
var x = 1;
return function() { alert(x); }
}; var bar = foo(); bar(); // 1
var x = 2 ;
bar(); // 1

好吧,作用域在函数定义时决定的,现在明白了,原谅我的无知......

接下来,来把头脑内混乱的 “上下文?作用域?上下文?作用域?” 再度区分下吧。

上下文(context)->this->挂载着变量与函数的对象(object-base)

作用域->函数定义时决定->作用于函数(function-base)

两者一个基于对象一个基于函数。

 
分类: javascript

【repost】图解Javascript上下文与作用域的更多相关文章

  1. 图解Javascript上下文与作用域

    原文网址:http://blog.rainy.im/2015/07/04/scope-chain-and-prototype-chain-in-js/ 本文尝试阐述Javascript中的上下文与作用 ...

  2. 图解Javascript原型链

    本文尝试阐述Js中原型(prototype).原型链(prototype chain)等概念及其作用机制.上一篇文章(图解Javascript上下文与作用域)介绍了Js中变量作用域的相关概念,实际上关 ...

  3. JavaScript高级内容:原型链、继承、执行上下文、作用域链、闭包

    了解这些问题,我先一步步来看,先从基础说起,然后引出这些概念. 本文只用实例验证结果,并做简要说明,给大家增加些印象,因为单独一项拿出来都需要大篇幅讲解. 1.值类型 & 引用类型 funct ...

  4. JavaScript高级内容笔记:原型链、继承、执行上下文、作用域链、闭包

    最近在系统的学习JS深层次内容,并稍微整理了一下,作为备忘和后期复习,这里分享给大家,希望对大家有所帮助.如有错误请留言指正,tks. 了解这些问题,我先一步步来看,先从稍微浅显内容说起,然后引出这些 ...

  5. javascript笔记:javascript的关键所在---作用域链

    javascript里的作用域是理解javascript语言的关键所在,正确使用作用域原理才能写出高效的javascript代码,很多javascript技巧也是围绕作用域进行的,今天我要总结一下关于 ...

  6. [译]JavaScript:函数的作用域链

    原文:http://blogs.msdn.com/b/jscript/archive/2007/07/26/scope-chain-of-jscript-functions.aspx 在JavaScr ...

  7. 认识javascript范围和作用域链

    范围 作用域就是变量和函数的可訪问范围.控制着变量和函数的可见性与生命周期,在JavaScript中变量的作用域有全局作用域和局部作用域. 全局和局部作用域以下用一张图来解释: 单纯的JavaScri ...

  8. javascript的关键所在---作用域链

    javascript的关键所在---作用域链 javascript里的作用域是理解javascript语言的关键所在,正确使用作用域原理才能写出高效的javascript代码,很多javascript ...

  9. JavaScript深入之作用域链

    前言 在 <javascript深入之执行上下文栈> 中讲到,当javascript代码执行一段可执行代码(executable code)时,会创建对应的执行上下文(execution ...

随机推荐

  1. IIS启动应用程序池报错"服务无法在此时接受控制信息"

    用管理员方式打开命令行 输入命令netsh winsock reset 这个命令在百科上的解释是 netsh winsock reset命令,作用是重置 Winsock 目录.如果一台机器上的Wins ...

  2. 我的第一篇java笔记

    什么是程序 程序通常指完成某些事物的一种既定方式和过程. 在日常生活中,可以把程序看成一系列动作的执行过程和描述. Java介绍 Java是Sun Microystems于1995年推出的高级编程语言 ...

  3. java打包发布程序.jar(Eclipse)

    1.程序运行无错误后,右击项目名称,选择Export 2.next后,选择启动时的运行项目,选择压缩包的存储路径 3.finishi即可. 4.运行 cmd存储路径下>java -jar XXX ...

  4. ISE初始化

    step1:输入setup进行初始化,如下图: step2:输入setup进行初始化,如下图:

  5. 【c】多级指针

    一.一级指针 1.int *p,*p2; p是变量名,*表明是指针,指针指向地址. 在定义时初始化,如int *p_2 = &b; //定义一个指针,指针指向一个地址 先定义再初始化,如int ...

  6. 如何看iOS崩溃日志

    重点:Triggered by Thread这句话后边的线程号,快速定位问题出现在那个线程,是否是你的锅:Triggered by Thread所指的线程表示导致异常.崩溃的线程 下边内容转自简书 简 ...

  7. java的post请求

    public static String sendPost(String url,Map<String, String> packageParams){ //packageParams={ ...

  8. java代码实现简单的认证——图片验证码形式

    前言:认证在互联网中会经常见到,其主要作用是安全问题中防止恶意破解.刷票等.在系统开发中认证的实现也有很多种,什么手机短信认证,邮箱认证等.在这里我使用最基本的验证码的形式进行认证,下面例出过程. 验 ...

  9. ASP.NET Core使用NLog记录日志到Microsoft Sql Server

    在之前的文章中介绍了如何在ASP.NET Core使用NLog,本文为您介绍在ASP.NET Core使用NLog记录到Microsoft Sql Server 1.我们需要添加依赖: NLog.We ...

  10. git 一些实用的api

    v3 获取原始文件内容: curl -H "PRIVATE-TOKEN: <token>" -H "Content-Type: application/jso ...