【转】javascript变量作用域、匿名函数及闭包
下面这段话为摘抄,看到网上大多数人使用的是变量在使用的时候声明而不是在顶端声明,也可能考虑到js查找变量影响性能的问题,哪里用就在哪里声明,也很好。
在Javascript中,我们在写函数的时候往往需要在函数内部定义一些临时变量。有些人喜欢在用到某个临时变量的时候再声明,而有的人喜欢在函数一开始就声明。一开始我以为这只是个人风格问题,后来细细想了一下,发觉在函数开始即声明是有一定的好处的。看下面这个例子。
<script>
var name = "Kevin";
function callName(){
alert(name);
var name = "marry";
}
callName();
</script>
我们定义了一个函数叫做callName,里面我们打算是用一个全局变量name的,可是我们在alert下面又声明了一个name的变量。那么这个时候弹出对话框中显示的name到底是什么呢?
运行一下,我们发现是undefined。为什么呢?
在C#等强类型语言里面,变量是不能被重复定义的,同时变量也必须先声明后使用。如果按类似上面这样的写法来写的话,那么编译是不会通过的。但是Javascript不会帮你做这样的检测,Javascript在进入一个函数域时,就已经声明好了函数里面需要用到的所有临时变量,注意,仅仅是声明,并没有执行赋值,每个变量的初始值都是undefined。那么上面这个例子就很好理解了。
因此为了避免出现这种问题,推荐的编程习惯是在函数开头就声明好函数内部所要用到的临时变量,这样你在函数里面某个地方用到变量的时候,你只需要查一下开头,就可以很清楚的知道,这个变量到底引用的是临时变量还是全局变量
一、JavaScript变量作用域(scope)
首先需要明白的几个要点:
1.JavaScript的变量作用域是基于其特有的作用域链的。
2.JavaScript没有块级作用域。
3.函数中声明的变量在整个函数中都有定义。(就后面第三点的说明)
4 .所有在最外层定义(非函数体内定义)的变量都拥有全局作用域
5. 所有末定义直接赋值的变量,系统会自动声明为拥有全局作用域的变量
6. 所有window对象的属性拥有全局作用域
(下面作者还提到全局变量都可以通过window.*来访问,明白这一点很重要,但是否准确?此外目前存在的疑问还有:
即使可以以这中方式来访问全局变量那是否能认为全局对象由window来统一管理,还是window只是保存了一个拷贝而已?)
1.JavaScript的作用域链
首先看一个很简单的例子,下面的代码中,定义一个全局变量n,n的作用域是全局的。
var n=1;
function show(){
var m=2;
alert(n); //显示结果为1
}
show();
</script>
运行代码后,结果为1。原理就是JavaScript作用域是层层包含的,外层的作用域在内层有效,执行的时候,从内层向外层查找。当函数show执行的时候,首先在函数show内部查找变量n,没有找到,再向外层查找,找到了全局变量n,alert显示结果为1。
2.局部变量优先级高于全局变量
var n=1;
function show(){
var m=2;
var n=3;
alert(n); //显示结果为3
}
show();
</script>
稍改一下:
var n=1;
function show(){
var m=2;
var n=3;
alert(window.n); //显示结果为1
}
show();
</script>
为什么会是1,而不是3呢,只是另外一个问题:全局变量都是window对象的属性,任何的全局变量都是window对象的属性,都可以用window.*来引用。(*代表一个变量)
var n=1;
function show(){
alert(n); //undefined
alert(this.n); //显示为1
var n=3; //声明局部变量n
alert(n); //显示为3
}
show();
</script>
第一个问什么会显示undefined呢,这个问题的原因就是在函数后面声明了局部变量n=3,在函数show执行的时候,会先从内层查找变量n,结果找到了,但在第一个alert的时候,n还没有赋值,故结果为undefined。
4.JavaScript没有块级作用域
var n=1;
function show(){
for(var i=0;i<3;i++){
var n=6;
alert(i); //显示结果为0,1,2
}
alert("for循环外:"+i); //显示结果为:“for循环外:3”
alert("n="+n); //显示结果为:n=6
}
//alert(i); 外部引用一个未声明变量会提示出错
show();
</script>
变量i虽然是在for循环内定义的,但for循环块外部仍然可以引用,i的作用域是整个函数show。如果在show函数外部引用i变量,会出现JavaScript报错。
5.没有使用var声明的变量属于全局变量
<script language="javascript" type="text/javascript">
var n=1;
function show(){
m=9; //声明变量m
var s=6;
}
show(); //不可少,alert之前需要先执行m=9,否则m未声明
alert(m); //显示9
alert(window.m); //显示9
alert(s); //报错"s is not defined"
</script>
函数show执行的过程中,引用了变量m,但未使用var声明,m就变成了全局变量。
二、JavaScript匿名函数
所谓匿名函数就是没有命名的函数,看起来有点难以理解,其实很常见,下面举一个小例子。
这样就创建了一个匿名函数,这个function没有函数名,但可以被调用和执行,当网页加载完毕时,会执行alert()。
(function(x,y){alert(x+y)})(2,3); //显示结果为5
</script>
上面的方法仍然是创建了一个匿名函数,并使用()优先表达式强制执行函数。
三、JavaScript闭包(closure)
闭包其实就是利用了函数作用域和匿名函数的知识,当函数A执行结束时,一部分变量变量被B引用,被引用的变量不能释放,形成了所谓的闭包。这里有篇很好的文章,可以参考一下。
下面看一个小例子:
<script language="javascript" type="text/javascript">
function show(){
var n=3;
setTimeout(function(){alert("first:"+n);},3000); //3秒后显示first:3
alert("second:"+n); //显示second:3
}
show();
function show2(){alert("第一个函数执行结束");}
show2();
</script>
运行上面的函数后,首先alert显示"second:3",然后显示"第一个函数执行结束",3秒后显示“first:3”。
仔细看一下,函数show执行结束的时候,变量n应该被释放了,内存里也就不存在了,怎么过了3秒还能显示出"first:3"呢?闭包恰恰就在此产生了,当函数show执行结束的时候,想要释放内存里的n,但发现变量n还在被一个匿名函数引用,要在3秒后调用,根据JavaScript闭包原理,内存里的n被保留了下来,于是在三秒以后仍然可以调用n。
下面看几种写法的区别:
alert(content); //3秒后alert显示"test"
}
function show(content,delay_time){
setTimeout("show2('"+content+"')",delay_time);
}
show("test",3000);
仔细看一下上面这种写法不是闭包,只是普通的函数调用而已。
function show(content,delay_time){
setTimeout(function(){alert(content)},delay_time); //3秒后alert显示"test"
}
show("test",3000);
这种写法就是闭包,虽然结果是一样的,但原理不一样。
function show(content){
function show2(){alert(content);}
return show2;
}
var newshow=show("test");
setTimeout(newshow,3000); //3秒后alert显示"test"
上面这种写法也是闭包,虽然结果都是一样的,但原理却不一样。这里面看到了函数也可以当作对象也传递,函数show执行后return show2,把函数show2赋值给newshow,然后3秒后,调用newshow对象,执行函数show2。
仔细比较上面3种写法,如果能悟出一些道理就好,闭包有些时候的确难理解。
稍复杂一点的例子:
<script language="javascript" type="text/javascript">
var show=null;
(function(){
var n=1;
var show2=function(){
alert(n++);
}
show=show2;
})();
show(); //显示1
show(); //显示2
show(); //显示3
</script>
上面代码中,又一次用了函数作为对象传递,n是匿名函数里的局部变量,外部是访问不到的。show是一个全局变量,应该访问不了变量n,但在函数show执行前,首先把函数匿名函数内部show2赋值给show,当后面函数show运行的时候,会调用show2的代码,而show2是可以访问n的,故结果显示为1、2、3。
另外一点值得注意的,也是很经典的一个例子。
CSS代码:
ul li{ margin:1px 0px; background-color:#999999;}
HTML代码:
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
JavaScript代码:
<script language="javascript" type="text/javascript">
var li=document.getElementsByTagName("li");
for(var i=0;i<li.length;i++){
li[i].onclick=function(){alert(i);}
}
</script>
运行后,不论点击哪一个li,都是alert提示“4”。这就是一个需要注意的地方:闭包允许内层函数引用父函数中的变量,但是该变量是最终值。闭包引用的变量i,是循环结束后的值,其实这是一个很常见的问题,解决方法也有很多。
比较常见的就是给li[i]添加属性值,比如li[i].n=i;还有就是用闭包方法解决,这个函数本身就是闭包,所谓用闭包来解决闭包的问题。代码如下:
var li=document.getElementsByTagName("li");
for(var i=0;i<li.length;i++){
(function(index){
li[index].onclick=function(){alert(index);}
})(i);
}
</script>
【转】javascript变量作用域、匿名函数及闭包的更多相关文章
- JavaScript变量作用域(Variable Scope)和闭包(closure)的基础知识
在这篇文章中,我会试图讲解JavaScript变量的作用域和声明提升,以及许多隐隐藏的陷阱.为了确保我们不会碰到不可预见的问题,我们必须真正理解这些概念. 基本定义 作用范围是个“木桶”,里面装着变量 ...
- Javascript设计模式之匿名函数与闭包
匿名函数 (function () { var foo = 10; var bar = 2; console.log(foo*bar); })(); // 20 带参数的匿名函数 (function ...
- 第一百一十节,JavaScript匿名函数和闭包
JavaScript匿名函数和闭包 学习要点: 1.匿名函数 2.闭包 匿名函数就是没有名字的函数,闭包是可访问一个函数作用域里变量的函数.声明:本节内容需要有面向对象和少量设计模式基础,否则无法听懂 ...
- JavaScript(第十五天)【匿名函数和闭包】
学习要点: 1.匿名函数 2.闭包 匿名函数就是没有名字的函数,闭包是可访问一个函数作用域里变量的函数.声明:本节内容需要有面向对象和少量设计模式基础,否则无法听懂.(所需基础15章的时候已经声明 ...
- [转]JavaScript中的匿名函数及函数的闭包
JavaScript中的匿名函数及函数的闭包 原文地址:http://www.cnblogs.com/wl0000-03/p/6050108.html 1.匿名函数 函数是JavaScript中最灵 ...
- JavaScript中的匿名函数及函数的闭包(转)
JavaScript中的匿名函数及函数的闭包 https://www.cnblogs.com/wl0000-03/p/6050108.html 1.匿名函数 函数是JavaScript中最灵活的一种 ...
- javascript进阶课程--第三章--匿名函数和闭包
javascript进阶课程--第三章--匿名函数和闭包 一.总结 二.学习要点 掌握匿名函数和闭包的应用 三.匿名函数和闭包 匿名函数 没有函数名字的函数 单独的匿名函数是无法运行和调用的 可以把匿 ...
- JavaScript笔记 第十六章 匿名函数和闭包
1.匿名函数 2.闭包 匿名函数就是没有名字的函数,闭包是可访问一个函数作用域里变量的函数. 一.匿名函数 //普通函数 function getName() { return 'name'; } a ...
- Go语言 - 函数 | 作用域 | 匿名函数 | 闭包 | 内置函数
函数是组织好的.可重复使用的.用于执行指定任务的代码块.本文介绍了Go语言中函数的相关内容. 介绍 Go语言中支持函数.匿名函数和闭包,并且函数在Go语言中属于“一等公民”. 函数可以赋值给变量 函数 ...
随机推荐
- Google Picasa
本博文的主要内容有 .Google Picasa的下载 .Google Picasa的安装 .Google Picasa的使用 Google 的免费图片管理工具Picasa,数秒钟内就可找到并欣赏计算 ...
- 西安Uber优步司机奖励政策(1月25日~1月31日)
滴快车单单2.5倍,注册地址:http://www.udache.com/ 如何注册Uber司机(全国版最新最详细注册流程)/月入2万/不用抢单:http://www.cnblogs.com/mfry ...
- hdoj 1072 Nightmare
Nightmare Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Total S ...
- Maven浅析-1
Maven的配置 1.首先从http://maven.apache.org/download.cgi下载当前最新版本的maven. 如果不想自己安装maven的话,建议直接下binary zip文件, ...
- CMDLINE的解析
在linux的config文件中有一个特殊的宏定义CMDLINE,以前也一直在使用这个宏的参数,但是真正这个宏的解析和使用却不怎么明确.这次有机会多对它有些了解,不妨把这个浅显的认识说出来,记下来. ...
- C++ 标准时间线
Herb Sutter在他的博客上贴出了一个C++的timeline,如下所示:
- C:\WINDOWS\system32\drivers\etc\hosts host文件夹里面的内容是什么?
这个文件是根据TCP/IP for Windows 的标准来工作的,它的作用是包含IP地址和Host name(主机名)的映射关系,是一个映射IP地址和Host name(主机名)的规定,规定要求每段 ...
- php 判断数组相等 数组运算符介绍
如何判断两个数组相等呢?其实很简单,用 == 或者 === 就可以了 php手册里说明如下, 例子 名称 结果 $a + $b 联合 $a 和 $b 的联合. $a == $b 相等 如果 $a 和 ...
- Qt 学习之路:绘制设备
绘图设备是继承QPainterDevice的类.QPaintDevice就是能够进行绘制的类,也就是说,QPainter可以在任何QPaintDevice的子类上进行绘制.现在,Qt 提供了若干这样的 ...
- Android开发报错系列(一),java.lang.NullPointerException,at android.widget.ListView.setupChild
问题描述:运行代码是报空指针错误,java.lang.NullPointerException,at Android.widget.ListView.setupChild 问题定位:listview控 ...