JavaScript 工作必知(九)function 说起 闭包问题
大纲
Function
Caller 返回函数调用者
Callee 调用自身
作用域
闭包
function
函数格式
function getPrototyNames(o,/*optional*/ a)
{
a = a || [];
for(var p in o)
{
a.push(p);
}
return a;
}
caller
func.caller 返回函数调用者
function callfunc()
{
if(callfunc.caller)
{
alert(callfunc.caller.toString());
}else
{
alert("没有函数调用");
}
} function handleCaller()
{
callfunc();
} handleCaller();//返回 handler
callfunc();//没有函数调用,返回null,执行了《没有函数调用》
callee
匿名方法递归调用
alert( (function (x) {
if (x <= 1) return 1;
return x * arguments.callee(x - 1);
}(10)));//362800
scope
作用域大家都不陌生,今天就来说说闭包问题,深刻吃透闭包问题。
<script> var global = "global scope";//这个是全局的作用域
function scope()
{
var scope = "local scope";//这个是局部的作用域
return scope;//返回的scope,是局部的变量
} </script>
1、:定义的全局变量也能在函数内部访问。当定义的局部变量和全局变量名字相同时,局部变量的就会隐藏全局变量,不会破坏全局变量的值。
var scope = "global scope";
function f()
{
var scope = "local scope";
return scope;
}
alert(f());//local scope
alert(scope);//global scope;
上面确实是很容易理解,对吧。
2、 全局变量可以不用var声明,但是局部变量必须使用var声明,如果局部变量不使用var声明,编译器会默认这个是个全局变量。
scope = "global scope";
function f()
{
scope = "local scope";
return scope;
}
alert(f());//local scope
alert(scope);//local scope
但是全局变量不使用var声明,也仅限非严格模式,如果使用严格模式的话,会报错误
<script>
"use strict";
scope = "global scope";
function f()
{
scope = "local scope";
return scope;
}
alert(f());//local scope
alert(scope);//local scope
</script>
所以建议大家声明变量时,千万不要省略var,可以避免不必要的麻烦。
3、 声明提前,也是可以滴。什么叫什么提前。
<script>
"use strict";
scope;
console.log(scope);
var scope = "global scope";
console.log(scope); </script>
这个可能大家看出第一个打印undefined ,是呀还没有给他赋值, 下面赋值可定打印global scope了。
这样理解并没有错,但是为什么会这样的呢,一个变量不是应该先定义才可以使用的吗?
这里给大家说下作用域链,JavaScript是基于词法作用域的语言。
1、作用域链是一个对象或者链表,这组代码中定义了这段代码"作用域中“的变量。当JavaScript需要查找变量scope时,就会从链中的第一个对象开发查找,如果第一个对象为scope,则会直接返回这个对象的值,如果不存在继续第二对象开始查找,直到找到。如果在作用域链上未查到该变量,则会抛出一个错误。
我们可以这个作用链可以这样表示:查找 scope->window(全局对象)很显然后面是有定义scope的。但是并没有做赋值操作,后面才做赋值操作,所以此时值为undefined.
4、这个比较具有迷惑性了,大家猜想下打印的值是什么?
<script>
"use strict"; var scope = "global scope";
function f() {
console.log(scope);
var scope = "local scope";
console.log(scope);
}
f(); </script>
看到这段代码:如果你粗心大意的话,很有可能会写出错误的答案:
1、global scope
2、local scope
分析: 声明了全局变量,在函数体中时,第一个表示全局变量,所以打印global,第二定义了局部的变量,覆盖掉了全局的scope,所以打印local scope。
这样的分析在C# java ,完全正确。但是这里分析确实错误的。
这说明这个问题前,我们先看一个问题。
这句话很重要:全局变量在程序中始终都是有定义的。局部变量在声明它的函数体以及其所嵌套的函数内始终是定义的。
如果你是从事高级语言工作开始接触JavaScript 有点不适应其作用域的定义。我也是这样的。我们来看一个例子:
<script>
var g = "global scope";
function f()
{
for(var i=0;i<3;i++)
{ for(var j=1;j<2;j++)
{
;
}
console.log(j);
}
console.log(i);
}
console.log(g);
f();
</script>
打印的结果是什么?
大家看到{} 表示语句块,语句块在一块作用域,所以大家可能猜想,j、i值已经在内存释放掉了,所以结果肯定是undefined.
真实的结果可能令你失望,
结果为什么会是这样,我开始的表情和你一样。
这时查看我让你记住的那句话。。。全局变量在程序中始终都是有定义的。局部变量在声明它的函数体以及其所嵌套的函数内始终是定义的。
确切的说函数的参数 也是属于局部变量的范畴。这句话也很重要!!!
那句话大概意思说,只要是在函数内部定义的变量,在整个函数内都是有效的。所以结果也就不难理解了。在回过头看我们的那个问题,你理解了吗?
作用链还有以下定义:
1、作用链是有一个全局对象组成。
2、在不包含嵌套的函数体内,作用链上有两个对象,第一个定义了函数参数和局部变量的对象,第二个是全局对象。
3、在一个嵌套的函数体内,作用链上至少包含三个对象。
当定以一个函数的时候,就会保存一个作用域链。
当调用这个函数时,它就会创建一个新的对象来存储它的局部变量,并将这个对象添加到保存的作用链上。同时创建一个新的更长的表示函数调用的作用链。
对于嵌套的函数,当调用外部函数时,内部函数又会重新定义一遍。因为每次调用外部函数的时候,作用链都是不同的。内部函数在每次定义的时候都有微妙的差别,每次调用的外部函数时,内部函数的代码都是相同的,而且关联的代码的作用域也是不同的。
闭包
搞了这么久终于要讲了,但是再将之前我们在来分析下作用域。
<script>
var nameg="global"
var g = function f() { console.log(name);
function demo0()
{
console.log("demo0="+name);
}
var name = "1";
function demo1() {
var name = "2";
console.log("demo1=" + name);
}
function demo2() {
console.log("demo2=" + nameg);
}
demo0();
demo1();
demo2();
};
g(); </script>
我们按照作用链来分析:
调用demo0, demo0()->查找name,未找到->f()查找,返回
调用demo1,demo1()->查找name,找到,返回
调用demo2,demo2()->查找nameg,未找到->f()查找nameg,未找到->window查找nameg找到,返回。
看这个例子:
<script>
function f()
{
var count = 0;
return { counter:function() {
return count++;
},reset:
function()
{
return count = 0;
}
} }
var d = f();
var c = f();
console.log("d第一次调用:"+ d.counter());//0
console.log("c第一次调用:"+ c.counter());//0 互不影响
console.log("d第一次调用:"+ d.reset());//0
console.log("c第二次调调用"+ c.counter());//1 </script>
这个例子上大家可以看到,我做了一个计数和置零的操作。
创建了两个f的对象实例d c,各有自己的作用域链,所以其值互不影响。当c第二次调用时,count值被保存了,因为c对象并未被销毁。明白这个例子后,后面的例子才比较好懂。
这个过程,大家应该十分明了了。那么现在我们来看闭包问题。我设置四个按钮,点击每个按钮就返回响应的名字。
<body>
<script>
function btnInit()
{
for(var i=1;i<5;i++)
{
var btn = document.getElementById("btn" + i);
btn.addEventListener("click",function() { alert("btn" + i);
});
}
}
window.onload= btnInit; </script>
<div>
<button id="btn1">Btn1</button>
<button id="btn2">Btn2</button>
<button id="btn3">Btn3</button>
<button id="btn4">Btn4</button>
</div>
</body>
点击运行,结果却是都是btn5;
我们用刚才的分析在坐下,首先要调用匿名函数->查找i,未找到->btnInit(),找到i在for循环中。找到。我们知道只有函数调用结束才释放,for中的i总是可见的,所以保留了最后的i值。那么如何解决呢。
解决i值在函数中不是总是可见的,那么我们就要使用函数的嵌套了,然后把i值传进去。
function btnInit()
{
for(var i=1;i<5;i++)
{
(function (data_i) { var btn = document.getElementById("btn" + data_i);
btn.addEventListener("click", function () { alert("btn" + data_i);
}); }(i)); }
}
看修改后的代码,首先执行第一次for,创建了一个对象,我们首先是执行匿名函数->data_i,未找到->function(data_i)找到,然后再次执行for有创建一个对象,闭包规则说的互不影响。所以能得出正确的结果。
JavaScript 工作必知(九)function 说起 闭包问题的更多相关文章
- javaScript 工作必知(五) eval 的使用
eval eval(parse) parse :里面跟参数字符串,我们知道执行javascript 会编译执行, 改变全局变量的值: var x = 2; //定义的全局变量 alert(x); ...
- javaScript 工作必知(三) String .的方法从何而来?
String 我们知道javascript 包括:number,string,boolean,null,undefined 基本类型和Object 类型. 在我的认知中,方法属性应该是对象才可以具有的 ...
- javaScript 工作必知(十) call apply bind
call 每个func 都会继承call apply等方法. function print(mesage) { console.log(mesage); return mesage; } print ...
- javaScript 工作必知(八) 属性的特性 值、写、枚举、可配置
属性的特性 每个对象都拥有属性,属性具有哪些特性呢? 1.属性具有值. 2.属性是否是可写的. 3.是否是可枚举的. 4.是否是可配置的. " ...
- javaScript 工作必知(七) 对象继承
对象继承inherit var o = { r: 1 }; var c = function f() { }; c.prototype = o; c.r = 3; alert(o.r);//被继承的属 ...
- javaScript 工作必知(六) delete in instanceof
in in 判断 左边 的字符串或者能转换成字符串的是否属于 右边 的属性. var data = { x: 1, y: 4 };//定义了直接对象 alert("x" in d ...
- javascript 工作必知(四) 类型转换
string和number boolean javascript 类型会根据赋值的进行转成相应的类型. var str = ""; alert(typeof (str));//st ...
- javaScript 工作必知(十一) 数组常用方法实现
大纲 Array join reverse反转 sort排序 concat 拼接 slice splice 数组 //定义数组 var a = []; //使用Array定义一个数组, var a1 ...
- javaScript 工作必知(二) null 和undefined
null null 表示个“空” , 使用typeof (null) ;//Object ; 说明他是一个特殊的对象. null 类型只自己唯一个成员.他是不包含属性和方法的. undefined u ...
随机推荐
- linux下设置ip地址 gw网关,dns的方法
本文介绍下,在linux中设置IP地址.网关.dns的方法,有需要的朋友作个参考吧. 设置linux网络的方法有两种:第一种:使用命令修改(直接即时生效) 复制代码代码示例: ip and net ...
- C#中ref和out的使用小结
ref传递的参数是变量的地址,在传入函数后,函数可以使用这些地址处的值,同时函数执行完后,这些变量被带回了调用者.ref传递的参数既可作传入值,也可作返回值. out传递的参数是变量的地址,在传入函数 ...
- python模块目录文件后续
1,新增PythonModule加载path Ruiy tip(关于python list[]数据库类型特殊你懂的!append(""),extend([""] ...
- Linux主机规划与磁盘分区
各硬件设备在Linux中的文件名 在Linux系统当中,几乎所有的硬件设备文件都在/dev这个目录内. 各硬件设备在Linux中的文件名: 设备 设备在Linux中的文件名 IDE接口的硬盘 /dev ...
- 委托-异步调用-泛型委托-匿名方法-Lambda表达式-事件【转】
1. 委托 From: http://www.cnblogs.com/daxnet/archive/2008/11/08/1687014.html 类是对象的抽象,而委托则可以看成是函数的抽象.一个委 ...
- leetcode_question_130 Surrounded Regions
Given a 2D board containing 'X' and 'O', capture all regions surrounded by 'X'. A region is captured ...
- A. Anton and Letters
time limit per test 2 seconds memory limit per test 256 megabytes input standard input output standa ...
- 运用Autoconf和Automake生成Makefile的学习之路
作为Linux下的程序开发人员,大家一定都遇到过Makefile,用make命令来编译自己写的程序确实是很方便.一般情况下,大家都是手工写一个简单Makefile,如果要想写出一个符合自由软件惯例的M ...
- C# AES,AesManaged使用学习
加密 static byte[] EncryptBytes_Aes(byte[] plainText, byte[] Key, byte[] IV) { // Check arguments. ) t ...
- C# @字符用法
1.用 @ 符号加在字符串前面表示其中的转义字符“不”被处理. 如果我们写一个文件的路径,例如"D:/文本文件"路径下的text.txt文件,不加@符号的话写法如下: string ...