之前写过一篇JavaScript 闭包究竟是什么的文章理解闭包,觉得写得很清晰,可以简单理解闭包产生原因,但看评论都在说了解了作用域链和活动对象才能真正理解闭包,起初不以为然,后来在跟公司同事交流的时候发现作用域和执行环境确实很重要,又很基础,对理解JavaScript闭包很有帮助,所以在写一篇对作用域和执行环境的理解。

作用域

作用域就是变量和函数的可访问范围,控制着变量和函数的可见性与生命周期,在JavaScript中变量的作用域有全局作用域和局部作用域。

单纯的JavaScript作用域还是很好理解的,在一些类C编程语言中花括号内的每一段代码都有各自的作用域,而且变量在声明它们的代码段外是不可见的,称之为块级的作用域,JavaScript容易让初学者误会的地方也在于此,JavaScript并没有块及的作用域,只有函数级作用域:变量在声明它们的函数体及其子函数内是可见的。

变量没有在函数内声明或者声明的时候没有带var就是全局变量,拥有全局作用域,window对象的所有属性拥有全局作用域;在代码任何地方都可以访问,函数内部声明并且以var修饰的变量就是局部变量,只能在函数体内使用,函数的参数虽然没有使用var但仍然是局部变量。

var a=3; //全局变量
function fn(b){ //局部变量
c=2; //全局变量
var d=5; //局部变量
function subFn(){
var e=d; //父函数的局部变量对子函数可见
for(var i=0;i<3;i++){
console.write(i);
}
alert(i);//3, 在for循环内声明,循环外function内仍然可见,没有块作用域
}
}
alert(c); //在function内声明但不带var修饰,仍然是全局变量

只要是理解了JavaScript没有块作用域,简单的JavaScript作用域很好理解,还有一点儿容易让初学者迷惑的地方是JavaScript变量可函数的与解析或者声明提前,好多种叫法但说的是一件事情,JavaScript虽然是解释执行,但也不是按部就班逐句解释执行的,在真正解释执行之前,JavaScript解释器会预解析代码,将变量、函数声明部分提前解释,这就意味着我们可以在function声明语句之前调用function,这多数人习以为常,但是对于变量的与解析乍一看会很奇怪

console.log(a); //undefined
var a=3;
console.log(a); //
console.log(b); //Uncaught ReferenceError: b is not defined

上面代码在执行前var a=3; 的声明部分就已经得到预解析(但是不会执行赋值语句),所以第一次的时候会是undefined而不会报错,执行过赋值语句后会得到3,上段代码去掉最后一句和下面代码是一样的效果。

var a;
console.log(a); //undefined
a=3;
console.log(a); //

然而

如果只是这样那么JavaScript作用域问题就很简单了,然而由于函数子函数导致的问题使作用域不止这样简单。大人物登场——执行环境或者说运行期上下文(好土鳖):执行环境(execution context)定义了变量或函数有权访问的其它数据,决定了它们的各自行为。每个执行环境都有一个与之关联的变量对象(variable object, VO),执行环境中定义的所有变量和函数都会保存在这个对象中,解析器在处理数据的时候就会访问这个内部对象。

全局执行环境是最外层的一个执行环境,在web浏览器中全局执行环境是window对象,因此所有全局变量和函数都是作为window对象的属性和放大创建的。每个函数都有自己的执行环境,当执行流进入一个函数的时候,函数的环境会被推入一个函数栈中,而在函数执行完毕后执行环境出栈并被销毁,保存在其中的所有变量和函数定义随之销毁,控制权返回到之前的执行环境中,全局的执行环境在应用程序退出(浏览器关闭)才会被销毁。

作用域链

当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain,不简称sc)来保证对执行环境有权访问的变量和函数的有序访问。作用域第一个对象始终是当前执行代码所在环境的变量对象(VO)

function a(x,y){
var b=x+y;
return b;
}

在函数a创建的时候它的作用域链填入全局对象,全局对象中有所有全局变量

如果执行环境是函数,那么将其活动对象(activation object, AO)作为作用域链第一个对象,第二个对象是包含环境,下一个是包含环境的包含环境。。。。。

function a(x,y){
var b=x+y;
return b;
}
var tatal=a(5,10);

这时候 var total=a(5,10);语句的作用域链如下

在函数运行过程中标识符的解析是沿着作用域链一级一级搜索的过程,从第一个对象开始,逐级向后回溯,直到找到同名标识符为止,找到后不再继续遍历,找不到就报错。

再来看看闭包

之前博客曾经总结道:只要存在调用内部函数的可能,JavaScript就需要保留被引用的函数。而且JavaScript运行时需要跟踪引用这个内部函数的所有变量,直到最后一个变量废弃,JavaScript的垃圾收集器才能释放相应的内存空间。回头再看看好理解了很多,父函数定义的变量在子函数的作用域链中,子函数没有被销毁,其作用域链中所有变量和函数就会被维护,不会被销毁。

for(var i=0;i<elements.length;i++){
elements[i].onclick=function(){
alert(i);
}
}

这是上篇博客提到过的经典错误,每次element点击alert都是length,这段代码中为element绑定的click事件处理程序的作用域链是这样的

由于内部函数(click事件处理程序时刻有调用可能),所以其作用域链不能被销毁(更别说本例中i在全局作用域中,只能页面卸载是销毁),i的值一直保持for循环执行完后的length值,所以每次触发onclick的时候才会alert length。

for(var i=0;i<elements.length;i++){
(function(n){
elements[n].onclick=function(){
alert(n);
}
})(i);
}

为什么这样就行了呢,这时候onclick引用的变量变成了n,而由于立即执行函数的原因,每个onclick函数在作用域链中分别保持着对应的n(0~length-1),这时候就可以了。

最后

其实理解了执行环境和作用域链后,闭包翻了变成显而易见的东西,但是也不能滥用闭包,从上面例子可以看出,闭包会使子函数保持其作用域链的所有变量及函数与内存中,内存消耗很大,在使用的时候尽量销毁父函数不再使用的变量。

JavaScript作用域链的更多相关文章

  1. JavaScript作用域链的理解

    前言 作用域是JavaScript一个很重要的概念,想要学好JavaScript就需要理解javascript作用域和作用域链的工作原理.这篇文章对JavaScript作用域链和作用域链做一个简单的介 ...

  2. JavaScript 作用域链图具体解释

    <script type="text/javascript"> /** * 作用域链: */ var a = "a"; function hao94 ...

  3. 个人理解的javascript作用域链与闭包

    闭包引入的前提个人理解是为从外部读取局部变量,正常情况下,这是办不到的.简单的闭包举例如下: function f1(){ n=100; function f2(){ alert(n); } retu ...

  4. javascript作用域链学习笔记

    作用域链 "JavaScript中的函数运行在它们被定义的作用域里,而不是它们被执行的作用域里." --权威指南 在JavaScript中,一切皆对象,包括函数.函数对象和其它对象 ...

  5. (好文推荐)一篇文章看懂JavaScript作用域链

    闭包和作用域链是JavaScript中比较重要的概念,首先,看看几段简单的代码. 代码1: var name = "stephenchan"; var age = 23; func ...

  6. 深入javascript作用域链到闭包

    我之前用过闭包,用过this,虽然很多时候知道是这么一回事,但是确实理解上还不够深入.再一次看javascript高级程序设计这本书时,发现一起很多疑难问题竟然都懂了,所以总结一下一些理解,难免有错, ...

  7. 图解 javascript 作用域链

    还是之前那一段简单的javascript代码: window.onload=function(){ function sub(a,b){ return a-b; } var result=sub(10 ...

  8. JavaScript作用域(链)学习笔记

    作用域是javascript老生常谈的问题,在面试题中也经常出现.此文记录本人对js作用域的理解.从以下三个方面深入探讨js作用域和js作用域链. 1.什么是作用域? 2.什么是作用域链? 3.常见面 ...

  9. JavaScript作用域链详解

    JavaScript的作用域链还是很有味道的,搞懂了这个知识点,闭包的问题也就迎刃而解咯 1.JavaScript的全局变量和局部变量 首先,先来看看js的全局变量和局部变量,js不是块级作用域,所以 ...

随机推荐

  1. 看看Maple T.A.的详细作用

    Maple T.A.是一个基于互联网的在线考试和智能评分系统,是Maplesoft公司 与美国数学协会(MAA)合作开发的成果,在全球拥有大量的院校用户.Maple T.A.提供了用户数据库.所见即所 ...

  2. int (*p)[10] 与*p[10]的区别

    定义指向具有10个整型元素的一维数组的指针格式为:int (*p)[10] ,而起初我一直以为int (*p)[10] 是定义二维数组的方法 ][],(*p)[]; p=a; /*有了这个定义后,指针 ...

  3. 使用EntityFramework6连接MySql数据库(db first方式)

    准备工具: VS2013.MySQL For VisualStudio 1.1.4.Connector/Net 6.8.3(百度网盘里) 程序包管理器执行命令: Install-Package Ent ...

  4. 挡不住的好奇心:ASP.NET 5是如何通过XRE实现跨平台的

    .NET程序员也有自己的幸福,.NET的跨平台是一种幸福,.NET的开源也是一种幸福,而更幸福的是可以通过开源的.NET了解.NET是如何一步步走向跨平台的,所以幸福是一种过程. 在.NET跨平台的进 ...

  5. 介绍.NET 开发必备工具 .NET Portability Analyzer

    随着.NET的原来越开放,不仅仅是开源这么简单了,也意味着.NET程序员要关注越来越多的平台,涵盖.NET Mic Framework, Xamarin,Mono,.NET等等,从windows到li ...

  6. SQL Server 使用全文索引进行页面搜索

    标签:SQL SERVER/MSSQL SERVER/数据库/DBA/全文索引 概述 全文引擎使用全文索引中的信息来编译可快速搜索表中的特定词或词组的全文查询.全文索引将有关重要的词及其位置的信息存储 ...

  7. ASP.NET MVC 过滤器(一)

    ASP.NET MVC 过滤器(一) 前言 前面的篇幅中,了解到了控制器的生成的过程以及在生成的过程中的各种注入点,按照常理来说篇幅应该到了讲解控制器内部的执行过程以及模型绑定.验证这些知识了.但是呢 ...

  8. 干货!表达式树解析"框架"(1)

    最新设计请移步 轻量级表达式树解析框架Faller http://www.cnblogs.com/blqw/p/Faller.html 关于我和表达式树 其实我也没有深入了解表达式树一些内在实现的原理 ...

  9. Functional Programming without Lambda - Part 1 Functional Composition

    Functions in Java Prior to the introduction of Lambda Expressions feature in version 8, Java had lon ...

  10. JSON数据的定义

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...