本文是根据以下文章以及《Javascript高级程序设计(第三版)》第四章相关内容总结的。

1、Javascript作用域原理,地址:http://www.laruence.com/2009/05/28/863.html

2、JavaScript 开发进阶:理解 JavaScript 作用域和作用域链,地址:http://www.cnblogs.com/lhb25/archive/2011/09/06/javascript-scope-chain.html

在介绍有关作用域的内容之前,先来介绍一下Javascript中的预编译。

Javascript中的预编译

这里说的Javascript中的预编译,就是指函数声明提升和变量提升。

在JS引擎执行每一段(在html页面中,一个script元素就是一段)JS代码之前,都会首先处理var关键字和函数定义式。

先说函数定义式,有两种,一种函数声明,一种函数表达式:

  1. <script>
  2. alert(func);//function func() {alert('something');}
  3. alert(thank);//undefined
  4. //这是一个函数声明
  5. function func() {
  6. alert('something');
  7. }
  8.  
  9. //这是一个函数表达式
  10. var thank = function() {
  11. alert('nothing');
  12. };
  13. </script>

alert语句都在两个函数定义式前面,alert(func);这一句输出了完整的func函数,alert(thank);输出undefined,说明函数声明被提升了,函数表达式没有被提升,这就是两者的区别。至于为什么,下面继续说变量提升。

变量有全局变量和局部变量,不管是哪种变量都会被提升,但提升的高度不一样。全局变量提升到全局的执行环境(这里的执行环境可以理解成作用域,活动对象,还可以理解成变量对像,都一个意思,包括下面也是)中,局部变量提升到局部的执行环境中。在变量被提升后他是没有值的,即为undefined,仅仅是一个名字,只有当代码执行到给变量赋值的语句时,它才真正有了值。

对于局部变量的提升是这么一个过程,这个过程是函数作用域链创建过程中的一部分:函数在调用,但是其中代码还没有执行之前,首先会创建一个活动对象,然后搜寻这个函数中的局部变量定义和函数定义,将变量名和函数名都作为这个活动对象的同名属性。对于局部变量定义,变量的值会在真正执行到赋值语句时才会计算,此时只是简单的赋值为undefined。

不使用var关键词和直接在全局环境中声明的变量都是全局变量,全局变量会被提升到全局的window活动对象中,但此时这些变量也是没有值的,只有执行到赋值语句时,它们的值才会被计算。

此时就可以解释为什么函数表达式没有被提升了。函数表达式是把一个匿名函数赋值给一个变量,实际上是把那个变量提升了,变量提升之后它的值为undefined。

JavaScript的作用域

《Javascript高级程序设计(第三版)》第四章中在引出作用域内容前先介绍了一个执行环境的概念,然后又说变量对像,然后又是活动对象。说了一大堆把人都绕晕了。我的理解是把执行环境就理解成作用域,作用域还不够具体,把它具体到代码层面,它就是一个对象,给这个对象起个名字叫变量对象,执行环境中的所有变量声明和函数声明都是这个变量对象的同名属性。执行环境有全局和局部之分,那么这个变量对象也有全局与局部之分,把这些全局的和局部的变量对象按照顺序排列起来就形成了作用域链,作用域链的最前端就是上文讲的函数调用时创建的那个活动对象,这个活动对象也是一个变量对象。

函数的作用域链在其声明的时候就已经开始创建了,而不是被调用的时候才开始的。

具体过程是:

当一个函数创建后,它的内部属性[[Scope]]会指向创建此函数的那个执行环境的变量对象,并把这个变量对象添加到其作用域链中。(函数的的内部属性[[Scope]]会最终指向全局环境window的变量对象,换句话说,全局对象的变量对象始终是作用域链的最后一个对象。)

当这个函数被调用时,会创建一个“运行期上下文(execution context)”,运行期上下文定义了函数执行时的环境,与这个环境相对应的是一个活动对象,该活动对象包含了函数的所有局部变量、命名参数、参数集合(arguments)以及this,然后此对象会被推入作用域链的前端,当运行期上下文被销毁,活动对象也随之销毁。

下面通过例子分析作用域链。

  1. <script>
  2. var a = 1;
  3. function func() {
  4. var b = 2;
  5. function test() {
  6. var c = b;
  7. b = a;
  8. a = c;
  9. }
  10. test();
  11. }
  12. func();
  13. alert(a);//
  14. </script>

当函数func创建完成时,他的作用域链:

  1. [[scope chain]] = [
  2. {
  3. window call object
  4. }
  5. ]

当函数func调用时,他的作用域链:

  1. [[scope chain]] = [
  2. {
  3. b: undefined,
  4. test: function test() {
  5. var c = b;
  6. b = a;
  7. a = c;
  8. }
  9. },
  10. {
  11. a: 1
  12. }
  13. ]

当test函数创建时,他的作用域链:

  1. [[scope chain]] = [
  2. {
  3. b: undefined,
  4. test: function test() {
  5. var c = b;
  6. b = a;
  7. a = c;
  8. }
  9. },
  10. {
  1. a: 1
  1. } ]

当test函数调用时,他的作用域链:

  1. [[scope chain]] = [
  2. {
  3. c: undefined
  4. },
  5. {
  6. b: 2,
  7. test: function test() {
  8. var c = b;
  9. b = a;
  10. a = c;
  11. }
  12. },
  13. {
  14. a: 1
  15. }
  16. ]

不知道这个例子的作用域链分析的正确不正确,如有不正确请看到的大侠们批评指正。

理解了函数声明提升和变量声明提升,以及知道作用域链的创建过程,对于理解JavaScript中函数的作用域有很大帮助,反正我是这么觉得。

补充:

今天看了一天的闭包。

我的理解,实际上闭包还是和作用域及作用域链密切相关的,真正理解了作用域链,理解闭包就不是问题。

举个闭包的例子(是司徒正美大触《javascript的闭包》中的例子),并用作用域链分析它。

引子例子

  1. <body>
  2. <ul>
  3. <li id="a1">aa</li>
  4. <li id="a2">aa</li>
  5. <li id="a3">aa</li>
  6. </ul>
  7. <script type="text/javascript">
  8. for(var i=1; i < 4; i++){
  9. var id = document.getElementById("a" + i);
  10. id.onclick = function(){
  11. alert(i);//现在都是返回4
  12. }
  13. }
  14. </script>
  15.  
  16. </body>

这个例子的代码执行完毕后,会给每个li元素添加鼠标点击事件(注意只是添加了事件,事件还没有触发,alert语句就没有执行)。点击页面中的3个li元素,触发事件都会输出相同的数字4。

事件添加完成后,事件函数的作用域链(全局作用域的变量对象):

  1. [[scope chain]] = [
  2. {
  3. i: 4,
  4. id: element node//id为a3的li节点
  5. }
  6. ]

事件函数执行时,他的作用域链:

  1. [[scope chain]] = [
  2. {
  3. this
  4. },
  5. {
  6. i: 4,
  7. id: element node//id为a3的li节点
  8. }
  9. ]

点击事件触发时,事件函数在其作用域链中查找i的值并输出,此时就查到i的值为4。

下面使用闭包使输出的值是li元素的序号。

真正的闭包例子

  1. <ul>
  2. <li id="a1">aa</li>
  3. <li id="a2">aa</li>
  4. <li id="a3">aa</li>
  5. </ul>
  6. <script type="text/javascript">
  7. var lists = document.getElementsByTagName("li");
  8. for(var i=0,l=lists.length; i < l; i++){
  9. lists[i].onclick = (function(i){//保存于外部函函数
  10. return function(){
  11. alert(i+1);
  12. }
  13. })(i);
  14. }
  15. </script>
  16.  
  17. </body>

用上面的代码,点击li元素时会输出其序号,而不再是一样的值。

这次给li元素添加的事件函数是从一个函数中返回的函数。

这说着有一点麻烦,给onclick后边那个立即执行的函数起个名字,就叫立即执行函数,先分析一下他的作用域链。

在立即执行函数没有执行时,其作用域链:

  1. [[scope chain]] = [
  2. //这个变量对象是全局环境的
  3. {
  4. lists: nodeLists,//节点数组
  5. i: 0
  6. }
  7. ]

当它执行的时候:

  1. [[scope chain]] = [
  2. //这个变量对象是他自己的活动对象
  3. {
  4. arguments//这里面有参数i
  5. },
  6. //这个变量对象是全局环境的
  7. {
  8. lists: nodeLists,//节点数组
  9. i: 0
  10. }
  11. ]

上面的过程发生了三次,每次立即执行函数的作用域链中的i值都不一样,这三条作用域链都保存在内存中,为了给他返回的内部函数在执行时调用。

假如我点击了第一个li元素,就触发事件,事件函数就要执行,此时事件函数的作用域链:

  1. [[scope chain]] = [
  2. //事件函数的活动对象
  3. {
  4. this
  5. },
  6. //立即执行函数的变量对象
  7. {
  8. i: 0
  9. },
  10. //这个变量对象是全局环境的
  11. {
  12. lists: nodeLists,//节点数组
  13. i: 0
  14. }
  15. ]

事件函数顺着他的作用域链查找i的值,此时i的值为0,输出时再加上1,最后输出1。

就这样吧,上面的内容就是我关于作用域链及闭包的理解,如有错误,请看到的大侠指正。

10月6号补充:

先加一个链接:艾伦的文章《执行环境,作用域》 http://www.cnblogs.com/aaronjs/articles/2167431.html

《Javascript高级程序设计(第三版)》第四章相关内容要与第七章闭包配合着看。

关于this,书上说,“this对象是在运行时基于函数的执行环境绑定的。”“每个函数在被调用时都会自动取得两个特殊变量:this和arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量。”(可以通过复制,间接访问)

根据上面这两句话,我的理解是:

函数作用域链的最前端,即该函数的活动对象,不管你怎么自定义,系统(浏览器也行,就是个名字,表示至高无上)默认他有两个变量this和arguments,他俩的值也是系统根据你的代码自己分配的,可能有时候你不明白系统的分配原则,导致你不理解为什么输出结果会和自己预想的不一样。

  1. var a = 'ok';
  2. var obj = {
  3. a: 'no',
  4. name: 'jack',
  5. say: function(){
  6. var that = this;
  7. return function() {
  8. return this.a + that.name + that.a + a;
  9.  
  10. };
  11.  
  12. }
  13. };
  14. var b = obj.say();
  15. console.log(b());//okjacknook
  1. var a = 'ok';
  2. var obj = {
  3. a: 'no',
  4. name: 'jack',
  5. say: function(){
  6. var that = this;
  7. var a = 'nimei';
  8. return function() {
  9. return this.a + that.name + that.a + a;
  10. };
  11. }
  12. };
  13. var b = obj.say();
  14. console.log(b());//okjacknonimei

再补充一个例子:

  1. var a = 'ok';
  2. var obj = {
  3. a: 'no',
  4. name: 'jack',
  5. say: function(){
  6. var that = this;
  7. var a = 'nimei';
  8. function test() {
  9. return this.a + that.name + that.a + a;
  10. }
  11. var b = test();
  12. return b;
  13. }
  14. };
  15. console.log(obj.say());//okjacknonimei
  16.  
  17. var m = 'tubie';
  18. function func() {
  19. var m = 'sb';
  20. function two() {
  21. alert(this.m);
  22. }
  23. two();
  24.  
  25. }
  26. func();//tubie

根据上面的例子,我得出的结论:抛开把this保存在变量中、使用apply方法以及call方法,不管是在全局环境中还是在局部环境(比如另一个函数内),func(); 这种直接调用函数的方式,其活动对象中的this都指向window。

关于Javascript作用域及作用域链的总结的更多相关文章

  1. 深入理解 JavaScript 变量的作用域和作用域链

    一个变量的作用域(scope)是程序源代码中定义这个变量的区域.简单的说,作用域就是变量与函数的可访问范围.全局变量拥有全局作用域,在JavaScript代码中的任何地方都有定义.局部变量是在函数体内 ...

  2. 一步步学习javascript基础篇(2):作用域和作用域链

    作用域和作用域链 js的语法用法非常的灵活,且稍不注意就踩坑.这集来分析下作用域和作用域链.我们且从几道题目入手,您可以试着在心里猜想着答案. 问题一. if (true) { var str = & ...

  3. javascript篇-----函数作用域,函数作用域链和声明提前

    在一些类似C语言的编程语言中,花括号内的每一段代码都具有各自的作用域,而且变量在声明它们的代码段之外是不可见的(也就是我们不能在代码段外直接访问代码段内声明的变量),我们称之为块级作用域,然而,不同于 ...

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

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

  5. 浅谈JavaScript中的变量、参数、作用域和作用域链

    基本类型和引用类型 在JavaScript中有两种数据类型值.基本类型值和引用类型值.基本类型值指的是简单的数据段,而引用类型值指的是可能由多个值构成的对象.在JavaScript中有5种基本数据类型 ...

  6. javascript作用域和作用域链摘录

    作用域是JavaScript最重要的概念之一,想要学好JavaScript就需要理解JavaScript作用域和作用域链的工作原理.今天这篇文章对JavaScript作用域和作用域链作简单的介绍,希望 ...

  7. JavaScript高级之词法作用域和作用域链

    主要内容: 分析JavaScript的词法作用域的含义 解析变量的作用域链 变量名提升时什么 一.关于块级作用域         说到JavaScript的变量作用域,与咱们平时使用的类C语言不同. ...

  8. JavaScript 开发进阶:理解 JavaScript 作用域和作用域链

    作用域是JavaScript最重要的概念之一,想要学好JavaScript就需要理解JavaScript作用域和作用域链的工作原理.今天这篇文章对JavaScript作用域和作用域链作简单的介绍,希望 ...

  9. JavaScript 开发进阶:理解 JavaScript 作用域和作用域链(转载 学习中。。。)

    作用域是JavaScript最重要的概念之一,想要学好JavaScript就需要理解JavaScript作用域和作用域链的工作原理.今天这篇文章对JavaScript作用域和作用域链作简单的介绍,希望 ...

随机推荐

  1. Java学习笔记1

    学习一个Coursera的Data-structures-optimizing-performance. Working with String in Java Flesh score Flesh s ...

  2. webserver几个例子

    刚刚学习了web服务,实现了发布和调用电话号码归属地查询,下面我简单的说一下 第一个方法利用网页实现号码查询: 首先进入http://www.webxml.com.cn/网站 然后点这个 输入手机号码 ...

  3. PHP基础知识之逻辑运算符

    与(and,&&)和或(or,||)有两种形式,两种形式的区别是:优先级不一样,and.or的优先级低于&&.||

  4. 初探SQL注入

    1.1注入语句(通过时间注入函数) 数据库名称 localhost:8080/ScriptTest/userServlet?username='union SELECT IF(SUBSTRING(cu ...

  5. VB.Net 2010中 ./和../的含义

    文件路径 文件路径就是文件在电脑(服务器)中的位置,表示文件路径的方式有两种:相对路径和绝对路径. Windows由于使用 斜杆/ 作为DOS命令提示符的参数标志了,为了不混淆,所以采用 反斜杠\ 作 ...

  6. ASP.net之策略模式

    设计思路: 用ASP.net设计,调用策略模式.在第一个数和第二个数的文本框中输入数值,单击录题按钮,数值保存在n1,n2文档中,把要做的题都保存完后,单击开始按钮,开始做题,做完单击判断按钮,进行判 ...

  7. IIS 8:IIS 入门

    深埋在您的 Microsoft 服务器 (2008年. 2008 R2 和 2012年的版本) 的范围内是最强大的 Web 服务器可用. 它只等待你来发挥其全部潜力. 您的目标是要从家里运行一个 Wo ...

  8. Cisco VPN can't work in Win8

    Goto HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\CvirtA  Change DisplayName to "Cisco ...

  9. python自动化测试(4)-使用第三方python库技术实现

    python自动化测试(4)-使用第三方python库技术实现 1   概述 关于测试的方法论,都是建立在之前的文章里面提到的观点: 功能测试不建议做自动化 接口测试性价比最高 接口测试可以做自动化 ...

  10. Python黑帽编程2.1 Python编程哲学

    Python黑帽编程2.1  Python编程哲学 本节的内容有些趣味性,涉及到很多人为什么会选择Python,为什么会喜欢这门语言.我带大家膜拜下Python作者的Python之禅,然后再来了解下P ...