JavaScript的作用域链还是很有味道的,搞懂了这个知识点,闭包的问题也就迎刃而解咯

1、JavaScript的全局变量和局部变量

  首先,先来看看js的全局变量和局部变量,js不是块级作用域,所以不能把你学过的C/C++作用域的知识用在js中!

(1)全局变量

  js的全局变量也可以看做window对象的属性,这句话怎么理解,请看以下代码:

var x = 10;
alert(window.x);//弹出10

  也就是说var x = 10;等价于window.x=10;

  再来看一段代码

function foo(){
x = 10;
}
foo();
alert(window.x);

  这会弹出什么呢?answer is 10!

  如果在函数中定义变量时没有用关键字var,那么实际上定义的就是全局变量。你经常会看到前端优化的一个点:尽量少定义全局变量!如果不可避免的用到全局变量,那么就在局部变量中保存。像这样:

function foo(){
var doc = document;
var divObj = doc.getElementByTagName('div');
}

  把document对象保存在局部变量doc中,然后对doc进行操作。

  但是,问题来了,为什么这样能提高效率呢?这个问题我们先留着,等讲完作用域链再来看。

(2)局部变量

  要说块级作用域,那么在js中就只有函数块,函数中定义的变量就是局部变量,当然必须有关键字var!(没有关键字var定义的都是全局变量)

也就是说if else语句和for循环中创建的变量在外部都可以访问的到

function foo(){
var x = 1;
}
for(var i = 0;i<10;i++){ }
if(i){
var y = 10;
}
foo();
alert(i);//
alert(y);//
alert(x);//error x is not defined

2、作用域链

  这是重点咯,什么是作用域链,还是通过代码来解释

var x = 1;
function foo(){
var y = 2; function bar(){
var z = 3;
alert(x+y+z);
}
bar();
}
foo();

答案是几不用说吧,在bar函数中没有y和z,执行x+y+z时,js搜索x,y,z变量的一种机制就是作用域链,这个例子的搜索顺序:bar->foo->window

前面讲的太简单,可能已经有人看不下去了,来点干货吧

bar的作用域链是:

barScopeChain = [
bar.AO,
foo.AO,
global.VO
];

foo的作用域链是:

fooScopeChain = [
foo.AO,
global.VO
];

可能各位看官都会迷糊,可能会问,这个AO,VO,是个什么玩意儿?我们慢慢来,先来看看变量bar函数变量搜寻过程

例如:找x变量;bar函数在搜寻变量x的过程中,先从自身AO对象上找,如果bar.AO存在这个属性,那么会直接使用这个属性的值,如果不存在,则会转到父级函数的AO对象,也就是foo.AO

如果找到x属性则使用,找不到继续 在global.VO对象查找,找到x的属性,返回属性值。如果在global.VO中没有找到,则会抛出异常ReferenceError。

  在函数执行过程中,每遇到一个变量,都会检索从哪里获取和存储数据,该过程从作用域链头部,也就是从活动对象开始搜索,查找同名的标识符,如果找到了就使用这个标识符对应的变量,如果没有则继续搜索作用域链中的下一个对象,如果搜索完所有对象都未找到,则认为该标识符未定义,函数执行过程中,每个标识符都要经历这样的搜索过程。

知道了这一点,回过头看刚开始的那个问题,为什么要少定义全局变量,从而进行优化?

因为作用域链是栈的结构,全局变量在栈底,每次访问全局变量都会遍历一次栈,这样肯定会影响效率。

在函数创建时,每个函数都会创建一个活动对象Active Object(AO),全局对象为Global Object(VO),创建函数的过程也就是为这个对象添加属性的过程,作用域链就是由这些绑定了属性的活动对象构成的。

  在函数执行的过程中,会创建函数的执行上下文,这里就不做过多解释,可以去看Tom大叔的深入理解JavaScript系列

  执行上下文是一个动态的概念,当函数运行的时候创建,活动对象 Active Object 也是一个动态的概念,它是被执行上下文的作用域链引用的,可以得出结论:执行上下文和活动对象都是动态概念,并且执行上下文的作用域链是由函数作用域链初始化的。PS:这些概念都是js引擎解析代码的内部机制,外部是无法访问的!

  

  还是刚才那段代码,我们来看看js引擎编译的过程,进一步了解具体是怎么创建作用域链的

  函数进入全局,创建VO对象,绑定x属性<入栈>(这里只是预解析,为ao对象绑定声明的属性,函数执行时才会执行赋值语句,所以值是underfind)

global.VO = {
x:underfind;
foo:reference of function
}

  遇到foo函数,创建foo.VO,绑定y属性<入栈>

foo.AO = {
y:undefined;
fbar:reference of function
}

  接下来是bar函数,z属性<入栈>

bar.AO = {
z:undefined;
}

作用域链和执行上下文都会保存在堆栈中,所以

bar函数的scope chain为[0]bar.AO-->[1]foo.AO-->[2]global.VO

foo函数的scope chain为[0]foo.AO-->[1]global.VO

这里有一个等式:scope=AO|VO + [[scope]]

函数scope等于自身的AO对象加上父级的scope,也可以理解为一个函数的作用域等于自身活动对象加上父级作用域。

3、来看一个闭包的例子

我的本意是给每个li标签绑定一个点击事件,点击后弹出对应的索引,单实际上每次都会弹出“这是第四个li标签”

分析:在创建foo函数时,foo.AO={liObj:undefined,i:undefined,onclick:refeerence of function}

在函数执行中,这个匿名函数自身没有i这个变量,所以会到foo的活动对象中找i变量,此时for循环已执行,变量i的值已经改变,所以总是会输出4

那怎么解决这个问题呢?也很简单

第一种解决方法:

用一个函数来保存变量i即可

第二种解决方法:

细心的人绝对会发现其实是一种方法,都是把全局变量放在局部保存

最后一个点:变量查找时原型链是优先于作用域链的。js引擎先在函数AO对象查找,再到原型链查找,接着是作用域链。

还没写完,今天实在没什么感觉,脑子很乱,还是出去走走,透透气吧,有不对的地方还希望好心人指出。

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

  1. JavaScript作用域及作用域链详解、声明提升

    相信大家在入门JavaScript这门语言时对作用域.作用域链.变量声明提升这些概念肯定会稀里糊涂,下面就来说说这几个 Javascript 作用域 在 Javascript 中,只有局部作用域和全局 ...

  2. Js作用域与作用域链详解

    一直对Js的作用域有点迷糊,今天偶然读到Javascript权威指南,立马被吸引住了,写的真不错.我看的是第六版本,相当的厚,大概1000多页,Js博大精深,要熟悉精通需要大毅力大功夫. 一:函数作用 ...

  3. Js作用域与作用域链详解[转]

     一直对Js的作用域有点迷糊,今天偶然读到JavaScript权威指南,立马被吸引住了,写的真不错.我看的是第六版本,相当的厚,大概1000多页,Js博大精深,要熟悉精通需要大毅力大功夫. 一:函数作 ...

  4. js javascript 原型链详解

    看了许多大神的博文,才少许明白了js 中原型链的概念,下面给大家浅谈一下,顺便也是为了巩固自己 首先看原型链之前先来了解一下new关键字的作用,在许多高级语言中,new是必不可少的关键字,其作用是为了 ...

  5. 你不知道的JavaScript--Item15 prototype原型和原型链详解

    用过JavaScript的同学们肯定都对prototype如雷贯耳,但是这究竟是个什么东西却让初学者莫衷一是,只知道函数都会有一个prototype属性,可以为其添加函数供实例访问,其它的就不清楚了, ...

  6. 从mixin到new和prototype:Javascript原型机制详解

    从mixin到new和prototype:Javascript原型机制详解   这是一篇markdown格式的文章,更好的阅读体验请访问我的github,移动端请访问我的博客 继承是为了实现方法的复用 ...

  7. JavaScript严格模式详解

    转载自阮一峰的博客 Javascript 严格模式详解   作者: 阮一峰 一.概述 除了正常运行模式,ECMAscript 5添加了第二种运行模式:"严格模式"(strict m ...

  8. [转]javascript console 函数详解 js开发调试的利器

    javascript console 函数详解 js开发调试的利器   分步阅读 Console 是用于显示 JS和 DOM 对象信息的单独窗口.并且向 JS 中注入1个 console 对象,使用该 ...

  9. javascript 节点属性详解

    javascript 节点属性详解 根据 DOM,html 文档中的每个成分都是一个节点 DOM 是这样规定的:整个文档是一个文档节点每个 html 标签是一个元素节点包含在于 html 元素中的文本 ...

随机推荐

  1. Ubuntu 14.04 Remmina远程桌面连接Windows计算机

    可以在/usr/share/applications/目录下启动Remmina远程桌面客户端或者直接在Dash中搜索Remmina即可 启动后如下图,可以新建,复制,编辑,删除远程桌面客户端 选择新建 ...

  2. HDU-1565 方格取数(1)

    http://acm.hdu.edu.cn/showproblem.php?pid=1565 方格取数(1) Time Limit: 10000/5000 MS (Java/Others)    Me ...

  3. 差别不在英语水平,而在汉语水平If you do not leave me, we will die together.

    为什么高考语文要提高到180分,英语降到100,差别不在英语水平,而在汉语水平.看下面例句的译法: If you do not leave me, we will die together. 你如果不 ...

  4. 第一个嵌入汇编的C程序

    最近两天开始学习linux内核,看了赵炯博士的<linux内核完全注释>感觉受益匪浅.今天看到第三章 嵌入汇编部分,于是写了一个小程序试验了一下,用gcc编译通过.代码如下: #inclu ...

  5. Android网络:开发浏览器(一)——基本的浏览网页功能开发

    我们定义这个版本为1.0版本. 首先,因为要制作一个浏览器,那么就不能通过调用内置浏览器来实现网页的浏览功能,但是可以使用WebView组件来进行. 在此之前,我们可以来看看两种网页显示方式:     ...

  6. Python中 os._exit() sys.exit() exit()区别

    Python退出程序的方式有两种:os._exit(), sys.exit() 1)os._exit() 直接退出 Python程序,其后的代码也不会继续执行. 2)sys.exit() 引发一个 S ...

  7. Android 通过反射让SQlite建表如此简单

    我们通常使用SQlite的时候,假设我们有10张表,我们要写10个建表语句.而建表语句中仅仅有一些字段的名字须要改而已,这样既费时又费力,还easy出错.我们知道写sql语句的时候常常会写错,假设写错 ...

  8. 再回首,Java温故知新(六):Java基础之变量

    变量在所有的编程语言中都有,属于最最基础的部分,学习这部分基本不分语言,变量可分为成员变量和局部变量 Java中变量的声明格式如下,修饰符如果不加,默认是default,关于修饰符,我们会在后续讲到, ...

  9. iOS---多线程实现方案一 (pthread、NSThread)

    在iOS开发中,多线程是我们在开发中经常使用的一门技术.那么本文章将和大家探讨一下针对于多线程的技术实现.本文主要分为如下几个部分: iOS开发中实现多线程的方式 单线程 pthread NSThre ...

  10. Android Studio下打jar包

    在我们使用Eclipse时,我们常常使用的第三方类库文件大多都是jar包形式,用起来很方便.但是jar包只能打包class文件,对于Android UI类库而言,我们常常需要打包资源文件,对于界面不多 ...