2016.3.23__ JavaScript基础_3__第十四天
2016-03-23 10:27
246 查看
最近感冒了,身体太疲惫,同时我也发现每天更新一篇确实不能保证我的文章的质量,与其每天写一些水文,不如静下心来把一些知识梳理好再呈现给大家。
所以很抱歉的通知大家,这个系列从今天起,更新日期由每日一更改为3~5日一更,实际时间只会更短,不会更长。
同时也很感谢很多小伙伴这么长时间的陪伴,谢谢大家。
我的文章简书专题连接:http://www.jianshu.com/collection/134d5825d813
提示: 1000 毫秒 = 1 秒.
清除定时器:cleanTimeout
setInterval() 方法会不停地调用函数,直到 clearInterval() 被调用或窗口被关闭。
由 setInterval() 返回的 ID 值可用作 clearInterval() 方法的参数。
提示: 1000 毫秒= 1 秒。
清除定时器:clearInterval
因为setTimeout(表达式,延时时间)在执行时,是在载入后延迟指定时间后,去执行一次表达式,记住,次数是一次 。
而setInterval(表达式,交互时间)则不一样,它从载入后,每隔指定的时间就执行一次表达式 。
所以,完全是不一样的 。
很多人习惯于将setTimeout包含于被执行函数中,然后在函数外再次使用setTimeout来达到定时执行的目的 。
这样,函数外的setTimeout在执行函数时再次触发setTimeout从而形成周而复始的定时效果 。
使用的时候各有各的优势,使用setInterval,需要手动的停止tick触发。
而使用方法中嵌套setTimeout,可以根据方法内部本身的逻辑不再调用setTimeout就等于停止了触发。
其实两个东西完全可以相互模拟,具体使用那个,看当时的需要而定了。
就像for可以模拟所有的循环包括分支,而还提供了do、while一样。
补充:
parsetInt() : 从左到右提取字符串中的整数,遇到非数字结束。(14.2312 取出来就是14)
parsetFloat(): 提取字符串中的小数
Number() : 将字符串转化为数字
String() : 将数字转换为字符串
或者说,计算机编程语言中的函数是指可以完成某些功能的一段固定代码。
当调用该函数时,会执行函数内的代码。
可以在某事件发生时直接调用函数(比如当用户点击按钮时),并且可由 JavaScript 在任何位置进行调用。
提示:JavaScript 对大小写敏感。关键词 function 必须是小写的,并且必须以与函数名称相同的大小写来调用函数。
咱们发现上面的函数,并没有具体的函数名,但是它将函数赋值给了变量 MR_LP,所以我们的 MR_LP就可以调用该函数。
这些参数可以在函数中使用。
您可以发送任意多的参数,由逗号 (,) 分隔:
变量和参数必须以一致的顺序出现。第一个变量就是第一个被传递的参数的给定的值,以此类推。
通过使用 return 语句就可以实现。
在使用 return 语句时,函数会停止执行,并返回指定的值。
上面的函数会返回值 5。
注释:整个 JavaScript 并不会停止执行,仅仅是函数。JavaScript 将继续执行代码,从调用函数的地方。
大家还记得刚才的匿名函数么?我们可以直接通过调用
而且由于 JavaScript 允许传入任意个参数而不影响调用,因此传入的参数比定义的参数多也没有问题,虽然函数内部并不需要这些参数。
当然,传入的参数比定义的要少也没有问题。
此时函数的参数将受到 undefined,计算结果为 NaN (Not a Number)。
所以我们在日常的开发中,一定要进行参数的预判断操作。
但如果我们如果把 return 语句分成了两句。
所以我们平常书写的时候,可以采用这种写法:
利用 arguments,你可以获得调用者传入的所有参数。
也就是说函数不定义任何参数,还是可以拿到参数的值。
实际上 arguments 最常用于判断传入参数的个数。你可能会看见这样的写法:
要把中间的参数 b 变为“可选”参数,就只能通过 arguments 判断,然后重新调整参数并赋值。
为了获取除了已定义参数 a , b 之外的参数,我们不得不用 arguments , 并且循环要从索引2 开始以便排除前两个参数,这种写法很别扭,只是为了获得额外的 rest 参数,那我们有没有更好的写法?
ES6 标准引入的 rest 参数,上面的函数可以改写为:
rest 参数只能写在最后,前面用
如果传入的参数连正常定义的参数都没填完,也不要紧,rest 参数会接收一个空数组(注意:不是 undefined)。
因为 rest 参数是 ES6 新标准,所以我们在使用时需要注意兼容性的问题。
如果一个变量在你函数体内部申明,则该变量的作用域为整个函数体,在函数体外不可以用该变量。
如果两个不同的函数各自申明了同一个变量,那么该变量只在各自的函数体内起作用。
换句话说,不同函数内容的同名变量相互独立,互不影响:
由于 JavaScript 的函数可以嵌套,此时内部函数可以访问外部函数定义的变量,反过来却不行。
如果内部函数和外部函数的变量名重名怎么办?
这说明 JavaScript 的函数在查找变量时从自身函数定义开始,从“内”向“外”依次查找。
如果内部函数定义的函数名与外部函数有重名的变量,则内部函数的变量将“屏蔽”外部函数的变量。
语句
[b]这正是因为 JavaScript 引擎自动提升了变量 y 的声明,但是不会提升变量 y 的赋值。
所以我们在函数内部定义变量的时候,请严格遵守函数内部首先声明所有变量的原则,最常见的就是用一个 var 声明函数内部所有用到的变量。
实际上,JavaScript 默认有一个全局对象 window (也就是浏览器对象),全局作用域的变量实际上被绑定到 window 的一个属性:
因此直接访问全局变量 course 和访问 window.course 是完全一样的。
由于函数的定义有两种方式,以变量的方式
由此我们也可以做一个更大胆的猜测,我们每次调通的 alert() 函数其实也应该是 window 的一个变量。
注意:
JavaScript 实际上之哟偶一个全局作用域。任何函数(函数也视为变量),如果没有在当前函数作用域中找到,那么就会继续向上层去查找,如果最后在全局中也没有找到,就直接报 ReferenceError 的错误。
所以为了解决块级作用域,ES6引入了新的关键字
一个坑
ES6标准出台之后,我们可以使用新的关键字 const 来定义常量,const 与 let 都具有块级作用域。
我们来实现一个对 Array 的求和,通常,求和的函数是这样定义的:
但是如果不需要立刻求和,而是在后面的代码中,根据需要再计算,那我们可以直接返回求和的函数。
当我们调用
调用函数
在这个例子中,我们在函数 lazy_sum 中又定义了函数 sum,并且内部函数 sum 可以引用外部函数 lazy_sum 的参数和局部变量,当 lazy_sum 返回函数 sum 时,相关参数和变量都保存在返回的函数中,这种我们称之为“闭包”。
需要注意,当我们调用 lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数
f1()和 f2() 的调用结果互不影响。
注意到返回的函数在其定义内部引用了局部变量 arr,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用,所以,闭包用起来容易,做起来可不容易。
另外需要注意,返回的函数并没有立即执行,而是知道调用了 f() 才执行,我们再来看另外一个例子:
在上面的例子中,每次循环都创建了一个新的函数,然后把创建的三个函数都添加到一个 array 循环中并返回了。
你可能认为代用的 f1(),f2(),f3()的结果是 1,4,9.而实际情况是
他们全部都是16,原因在于返回的函数引用了变量 i,但它却不是立即执行,等到三个函数全部执行完毕的时候,i 的值已经变成了 4 ,所以所有的结果都是 16.
所以在返回闭包的时候一定要注意一点: 返回函数不要引用任何循环变量,或者后续会发生变化的变量。
如果程序中必须使用的时候呢?
我们需要重新创建一个函数,用该函数的参数绑定循环变量当前的值,之后就不用管循环变量会怎么变,我们已经绑定到函数的参数的值是不会变的。
注意在上面的程序中,我们创建了一个“匿名函数并立即执行”的方法。
理论上讲,创建一个匿名函数并立即执行可以这儿写:
但是 JS 语法解析,会认为这个函数是错误的,会提示 SyntaxError。因此我们需要用括号吧整个函数定义括起来:
而我们日常工作中,会将这个函数分开来写
那我们之前说闭包功能非常强大,他强大在哪里呢?
在面向对象的程序设计语言内,如 Java 和 C++ ,要在对象内部封装一个私有变量,可以用 private 修饰一个成员变量。
在没有 class 机制,只有函数的语言里,借助闭包,同样可以封装一个私有变量。我们用 JavaScript 创建一个计数器:
使用的时候:
在返回的对象中,实现了一个闭包,该闭包携带了局部变量 x , 并且,从外部代码根本无法访问到变量 x。换句话说,闭包就是携带状态的函数,并且它的状态可以完全对外隐藏起来。
闭包还可以把多参数的函数编程但参数的函数。
例如,要计算
例如我们现在来生产一个 1-10之间的随机数:
所以很抱歉的通知大家,这个系列从今天起,更新日期由每日一更改为3~5日一更,实际时间只会更短,不会更长。
同时也很感谢很多小伙伴这么长时间的陪伴,谢谢大家。
我的文章简书专题连接:http://www.jianshu.com/collection/134d5825d813
1. 定时器
在我们的日常开发中,经常会需要让某一个效果等待多少秒之后才去触发,这也就引出了我们今天要学习的内容,定时器。1.1 setTimeout()
setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式。提示: 1000 毫秒 = 1 秒.
setTimeout(code,millisec,lang)
参数 | 描述 |
---|---|
code | 必需。要调用的函数后要执行的 JavaScript 代码串。 |
millisec | 必需。在执行代码前需等待的毫秒数。 |
lang | 可选。脚本语言可以是:JScript |
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>菜鸟教程(runoob.com)</title> </head> <body> <p>点击按钮,在等待 3 秒后弹出 "Hello"。</p> <button onclick="myFunction()">点我</button> <script> function myFunction() { setTimeout(function(){alert("Hello")},3000); } </script> </body> </html>
清除定时器:cleanTimeout
cleanTimeout(myFunction);
1.2 setInterval()
setInterval() 方法可按照指定的周期(以毫秒计)来调用函数或计算表达式。setInterval() 方法会不停地调用函数,直到 clearInterval() 被调用或窗口被关闭。
由 setInterval() 返回的 ID 值可用作 clearInterval() 方法的参数。
提示: 1000 毫秒= 1 秒。
setInterval(code,millisec,lang)
参数 | 描述 |
---|---|
code | 必需。要调用的函数后要执行的 JavaScript 代码串。 |
millisec | 必需。在执行代码前需等待的毫秒数。 |
lang | 可选。脚本语言可以是:JScript |
<html> <body> <input type="text" id="clock" /> <script type="text/javascript"> var int=self.setInterval("clock()",1000); function clock() { var d=new Date(); var t=d.toLocaleTimeString(); document.getElementById("clock").value=t; } </script> <button onclick="int=window.clearInterval(int)">停止</button> </body> </html>
清除定时器:clearInterval
clearInterval(clock);
1.3 setTimeout 和 setInterval 的区别?
在上方的两个模块中,大家都看见了,这两个方法全部都是定时器,但是咱们发现两个除了名字不同,貌似没什么区别,那为什么还要有两个方法呢?因为setTimeout(表达式,延时时间)在执行时,是在载入后延迟指定时间后,去执行一次表达式,记住,次数是一次 。
而setInterval(表达式,交互时间)则不一样,它从载入后,每隔指定的时间就执行一次表达式 。
所以,完全是不一样的 。
很多人习惯于将setTimeout包含于被执行函数中,然后在函数外再次使用setTimeout来达到定时执行的目的 。
这样,函数外的setTimeout在执行函数时再次触发setTimeout从而形成周而复始的定时效果 。
使用的时候各有各的优势,使用setInterval,需要手动的停止tick触发。
而使用方法中嵌套setTimeout,可以根据方法内部本身的逻辑不再调用setTimeout就等于停止了触发。
其实两个东西完全可以相互模拟,具体使用那个,看当时的需要而定了。
就像for可以模拟所有的循环包括分支,而还提供了do、while一样。
2.类型转换
JS 中类型转换分为强制转换和隐式转换。2.1 强制转换
var a = "50"; //字符串 String var b = 10; //数字 Number //字符串-->数字 var a = Number(a); //提示 Number alert(typeof a); //数字-->字符串 var b = String(b); //提示 String alert(typeof b);
补充:
parsetInt() : 从左到右提取字符串中的整数,遇到非数字结束。(14.2312 取出来就是14)
parsetFloat(): 提取字符串中的小数
Number() : 将字符串转化为数字
String() : 将数字转换为字符串
2.2 隐式转换
//隐式类型转换 var a = "5"; //字符串类型 var b = 10 ; //数字类型 alert(a-b); //隐式转换 将字符串“5”转换为数字 5
3. 函数
函数是由事件驱动的或者当它被调用时执行的可重复使用的代码块。或者说,计算机编程语言中的函数是指可以完成某些功能的一段固定代码。
3.1 如何定义函数
3.1.1 有名函数(别问我这个是什么鬼,我也不知道,23333)
函数就是包裹在花括号中的代码块,前面使用了关键词 function:function functionname() { 这里是要执行的代码 }
当调用该函数时,会执行函数内的代码。
可以在某事件发生时直接调用函数(比如当用户点击按钮时),并且可由 JavaScript 在任何位置进行调用。
提示:JavaScript 对大小写敏感。关键词 function 必须是小写的,并且必须以与函数名称相同的大小写来调用函数。
3.1.2 匿名函数
var MR_LP = function(x){ if(x > 0){ return x; } else{ return -x; } };
咱们发现上面的函数,并没有具体的函数名,但是它将函数赋值给了变量 MR_LP,所以我们的 MR_LP就可以调用该函数。
3.1.3 总结
这两种定义实际上完全等价,只是需要注意第二种方式按照完整的语法,需要在函数体末尾加一个 ” ; “.表示赋值语句结束。3.2 调用带参数的函数
在调用函数时,您可以向其传递值,这些值被称为参数。这些参数可以在函数中使用。
您可以发送任意多的参数,由逗号 (,) 分隔:
myFunction(argument1,argument2) //当您声明函数时,请把参数作为变量来声明: function myFunction(var1,var2) { 这里是要执行的代码 }
变量和参数必须以一致的顺序出现。第一个变量就是第一个被传递的参数的给定的值,以此类推。
3.3 带有返回值的函数
有时,我们会希望函数将值返回调用它的地方。通过使用 return 语句就可以实现。
在使用 return 语句时,函数会停止执行,并返回指定的值。
function myFunction() { var x=5; return x; }
上面的函数会返回值 5。
注释:整个 JavaScript 并不会停止执行,仅仅是函数。JavaScript 将继续执行代码,从调用函数的地方。
3.4 如何调用函数
我们调用函数只需要通过函数名 + 参数即可完成调用,注意当函数有参数的时候,一定要按照顺序传入参数。
大家还记得刚才的匿名函数么?我们可以直接通过调用
MR_LP,就可以直接调用函数,例如这样:
MR_LP(100); //返回100 MR_LP(-99); //返回99,函数内取反了
而且由于 JavaScript 允许传入任意个参数而不影响调用,因此传入的参数比定义的参数多也没有问题,虽然函数内部并不需要这些参数。
MR_LP(100,'13115235'); //返回100 MR_LP(-99,'asdasd','aqweqvx'); //返回99,函数内取反了
当然,传入的参数比定义的要少也没有问题。
MR_LP(); 返回 NaN
此时函数的参数将受到 undefined,计算结果为 NaN (Not a Number)。
所以我们在日常的开发中,一定要进行参数的预判断操作。
var MR_LP = function(x){ //判断参数是否是 Number 类型 if(typeof x !== 'number'){ //抛出异常 throw 'Not a number'; } if(x > 0){ return x; } else{ return -x; } };
3.5 return 的大坑
在 JavaScript 引擎中有一个在行末自动添加分号的机制,但是这个机制也有可能会让你栽一个大跟头。(参考苹果公司的那个好多年的 return 大坑)function lol(){ return{ name : 'lol'}; } lol(); //{name : 'lol'}
但如果我们如果把 return 语句分成了两句。
function lol(){ return; //return 的结尾被自动添加上了分号,相当于 return undefined; { name : 'lol'}; //永远不会被执行到 }
所以我们平常书写的时候,可以采用这种写法:
function lol(){ return{ //这时候不会自动添加分号,因为 {} 代表语句未结束 name : 'lol' }; }
4. arguments
JavaScript 还有一个免费赠送的关键字arguments,它只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。arguments 类似 Array,但实际并不是。
function foo(x){ alert(x); //10 for(var i = 0 ; i < arguments.length ; i ++){ alert(arguments[i]); //10 , 20 , 30 } } foo(10, 20, 30);
利用 arguments,你可以获得调用者传入的所有参数。
也就是说函数不定义任何参数,还是可以拿到参数的值。
function MR_LP(){ if(arguments.length === 0 ){ return 0; } var x = arguments[0]; return x >= 0 ? x : -x; } MR_LP(0); //0 MR_LP(100); //100 MR_LP(-99); //99
实际上 arguments 最常用于判断传入参数的个数。你可能会看见这样的写法:
// foo(a,,c) //接收2~3个参数,b 是可选参数,如果只传入2个参数,b 默认为 null; function foo(a,b,c){ if(arguments.length === 2){ //实际拿到的参数是 a 和 b,c 为 undefined c = b; //把 b 赋值给 c b = null; //b 变为默认值 } }
要把中间的参数 b 变为“可选”参数,就只能通过 arguments 判断,然后重新调整参数并赋值。
5. rest 参数
由于 JavaScript 函数允许接收任意个参数,于是我们就不得不用 arguments 来获取所有参数:function foo(a,b){ var i,rest = []; if(arguments.length > 2){ for(i = 2; i < arguments.length; i++){ rest.push(arguments[i]); } } console.log('a = ' + a); console.log('b = ' + b); console.log(rest); }
为了获取除了已定义参数 a , b 之外的参数,我们不得不用 arguments , 并且循环要从索引2 开始以便排除前两个参数,这种写法很别扭,只是为了获得额外的 rest 参数,那我们有没有更好的写法?
ES6 标准引入的 rest 参数,上面的函数可以改写为:
function foo(a, b ... rest){ console.log('a = ' + a); console.log('b = ' + b); sonsole.log(rest); } foo(1,2,3,4,5); //结果: // a = 1 // b = 2 // Array [ 3, 4, 5] foo(1); //结果: // a = 1 // b = undefined // Array []
rest 参数只能写在最后,前面用
...标识,从运行结果可知,传入的参数先绑定 a , b,多余的参数以数组的形式交给变量 rest, 所以不再需要 arguments 我们就获取了全部参数。
如果传入的参数连正常定义的参数都没填完,也不要紧,rest 参数会接收一个空数组(注意:不是 undefined)。
因为 rest 参数是 ES6 新标准,所以我们在使用时需要注意兼容性的问题。
6.变量及作用域
在 JavaScript 中,用 var 声明的变量实际上是有作用域的。如果一个变量在你函数体内部申明,则该变量的作用域为整个函数体,在函数体外不可以用该变量。
function foo(){ var x = 1; x = x + 1; } x = x + 2; //referrenceError ! 无法再函数体外引用变量 x
如果两个不同的函数各自申明了同一个变量,那么该变量只在各自的函数体内起作用。
换句话说,不同函数内容的同名变量相互独立,互不影响:
function foo(){ var x = 1; x = x + 1; } function bar(){ var x = 'A'; x = x + 'B'; }
由于 JavaScript 的函数可以嵌套,此时内部函数可以访问外部函数定义的变量,反过来却不行。
function foo(){ var x = 1; function bar (){ var y = x + 1; //bar 可以访问 foo 的变量 x! } var z = y + 1; //ReferenceError! foo 不可以访问 bar 的变量 y ! }
如果内部函数和外部函数的变量名重名怎么办?
function foo(){ var x = 1; function bar(){ var x = 'A'; alert(' x in bar() = ' + x); //'A' } alert('x in foo() = ' + x); // 1 bar(); }
这说明 JavaScript 的函数在查找变量时从自身函数定义开始,从“内”向“外”依次查找。
如果内部函数定义的函数名与外部函数有重名的变量,则内部函数的变量将“屏蔽”外部函数的变量。
6.1 变量提升
JavaScript 的函数定义有个特点,它会先扫描整个函数体中的语句,把所有申明的变量“提升”至函数的顶部;function foo(){ var x = 'hello, ' + y; alert(x); var y = 'larra'; } foo();
语句
var x = 'hello' + y;并不会报错,原因是变量
y在稍后直接声明了,但是我们的 alert 会提示
hello, undefined,这说明我们的变量
y的值是 undefined。
[b]这正是因为 JavaScript 引擎自动提升了变量 y 的声明,但是不会提升变量 y 的赋值。
所以我们在函数内部定义变量的时候,请严格遵守函数内部首先声明所有变量的原则,最常见的就是用一个 var 声明函数内部所有用到的变量。
function foo(){ var x = 1; //x 初始值为 1 y = x + 1; //y 初始值为 2 z,i; //z 和 i 为 undefined //其他语句 for(i = 0; i<100; i++){ ... } }
6.2 全局变量
不在任何函数内定义的变量就具有全局作用域。实际上,JavaScript 默认有一个全局对象 window (也就是浏览器对象),全局作用域的变量实际上被绑定到 window 的一个属性:
var course = 'learn JavaScript'; alert(course); alert(window.course);
因此直接访问全局变量 course 和访问 window.course 是完全一样的。
由于函数的定义有两种方式,以变量的方式
var foo = function(){}定义的函数实际上也是一个全局变量,因此,顶层函数的定义也被视为一个全局变量,并绑定到 window 对象:
'use strict'; function foo(){ alert('foo'); } foo(); //直接调用 foo(); window.foo(); //通过 window.foo()调用
由此我们也可以做一个更大胆的猜测,我们每次调通的 alert() 函数其实也应该是 window 的一个变量。
注意:
JavaScript 实际上之哟偶一个全局作用域。任何函数(函数也视为变量),如果没有在当前函数作用域中找到,那么就会继续向上层去查找,如果最后在全局中也没有找到,就直接报 ReferenceError 的错误。
6.3 局部变量
由于 JavaScript 的变量作用域实际上是函数内部,,我们在 for 循环等语句块中是无法定义具有局部作用域的变量。function foo(){ for(var i = 0;i < 100; i++){ // } i +=100; //仍然可以引用变量 i }
所以为了解决块级作用域,ES6引入了新的关键字
let,用 let 替代 var 可以声明一个块级元素作用域的变量:
function foo(){ var sum = 0; for(let i = 0; i< 100 ; i++){ sum +=i; } i +=1; //SyntaxError }
一个坑
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Document</title> </head> <body> <!--a[href=###]{我是按钮$}*4--> <a href="###">我是按钮1</a> <a href="###">我是按钮2</a> <a href="###">我是按钮3</a> <a href="###">我是按钮4</a> </body> <script type="text/javascript"> var links = document.getElementsByTagName("a"); for (var i = 0 ; i < links.length ; i++) { links[i].onclick = function(){ alert(i); //我们会发现,无论点击哪个,弹出的结果都是4。因为我们的 i 作为局部变量,当运行完成后,i 就变成了4,之后不管你如何操作,它都是4了。 } } </script> </html>
6.4 常量
变量最明显的特征就是里面储存的值是可以随意改变的,常量则与之相反,常量一旦确定,则不能发生改变。var PI = 3.14;
ES6标准出台之后,我们可以使用新的关键字 const 来定义常量,const 与 let 都具有块级作用域。
const PI = 3.14; PI = 3; //某些浏览器不会报错,但是无实际效果 PI; //3.14
7.闭包
7.1 函数作为返回值
高阶函数除了可以接受函数作为参数外,还可以把函数作为返回结果返回。我们来实现一个对 Array 的求和,通常,求和的函数是这样定义的:
function sum(arr){ return arr.reduce(function(x,y){ return x + y; }); } sum([1,2,3,4,5]); //15
但是如果不需要立刻求和,而是在后面的代码中,根据需要再计算,那我们可以直接返回求和的函数。
function lazy_sum(arr){ var sum = function(){ return arr.reduce(function (x,y){ return x + y; }); } return sum; }
当我们调用
lazy_sum()返回的并不是求和的结果,而是求和的函数:
var f = lazy_sum([1,2,3,4,5]); //function sum()
调用函数
f的时候才是真正计算求和的结果,而是求和函数:
f(); //15
在这个例子中,我们在函数 lazy_sum 中又定义了函数 sum,并且内部函数 sum 可以引用外部函数 lazy_sum 的参数和局部变量,当 lazy_sum 返回函数 sum 时,相关参数和变量都保存在返回的函数中,这种我们称之为“闭包”。
需要注意,当我们调用 lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数
var f1 = lazy_sum([1,2,3,4,5]); var f2 = lazy_sum([1,2,3,4,5]); f1===f2; //false
f1()和 f2() 的调用结果互不影响。
注意到返回的函数在其定义内部引用了局部变量 arr,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用,所以,闭包用起来容易,做起来可不容易。
另外需要注意,返回的函数并没有立即执行,而是知道调用了 f() 才执行,我们再来看另外一个例子:
function count(){ var arr = []; for(var i = 1; i<=3;i++){ arr.push(function(){ return i * i; }); } return arr; } var resullts = count(); var f1 = results[0]; var f2 = results[1]; var f3 = results[2];
在上面的例子中,每次循环都创建了一个新的函数,然后把创建的三个函数都添加到一个 array 循环中并返回了。
你可能认为代用的 f1(),f2(),f3()的结果是 1,4,9.而实际情况是
f1(); //16 f2(); //16 f3(); //16
他们全部都是16,原因在于返回的函数引用了变量 i,但它却不是立即执行,等到三个函数全部执行完毕的时候,i 的值已经变成了 4 ,所以所有的结果都是 16.
所以在返回闭包的时候一定要注意一点: 返回函数不要引用任何循环变量,或者后续会发生变化的变量。
如果程序中必须使用的时候呢?
我们需要重新创建一个函数,用该函数的参数绑定循环变量当前的值,之后就不用管循环变量会怎么变,我们已经绑定到函数的参数的值是不会变的。
function count(){ var aarr = []; for(var i=1;i<=3;i++){ arr.push((function(n){ return function(){ return n*n; } })(i)); } return arr; } var results = count(); var f1 = results[0]; var f2 = results[1]; var f3 = results[2]; f1(); //1 f2(); //4 f3(); //9
注意在上面的程序中,我们创建了一个“匿名函数并立即执行”的方法。
(function(x){ return x*x; })(3)
理论上讲,创建一个匿名函数并立即执行可以这儿写:
function (x){return x * x;} (3);
但是 JS 语法解析,会认为这个函数是错误的,会提示 SyntaxError。因此我们需要用括号吧整个函数定义括起来:
(function(x){return x*x;})(3);
而我们日常工作中,会将这个函数分开来写
(function(x){ return x*x; })(3);
那我们之前说闭包功能非常强大,他强大在哪里呢?
在面向对象的程序设计语言内,如 Java 和 C++ ,要在对象内部封装一个私有变量,可以用 private 修饰一个成员变量。
在没有 class 机制,只有函数的语言里,借助闭包,同样可以封装一个私有变量。我们用 JavaScript 创建一个计数器:
function create_counter(initial){ var x = initial || 0; return{ inc : function(){ x += 1; return x; } } }
使用的时候:
var c1 = create_counter(); c1.inc(); //1 c1.inc(); //2 c1.inc(); //3 var c2 = create_counter(10); c2.inc(); //11 c2.inc(); //12 c2.inc(); //13
在返回的对象中,实现了一个闭包,该闭包携带了局部变量 x , 并且,从外部代码根本无法访问到变量 x。换句话说,闭包就是携带状态的函数,并且它的状态可以完全对外隐藏起来。
闭包还可以把多参数的函数编程但参数的函数。
例如,要计算
x^y可以使用
Math.pow(x,y)函数,不过考虑到经常计算
x^2或者
x^3, 我们可以利用闭包创建新的函数
pow2和
pow3:
function make_pow(n){ return function (x){ return Math.pow(x,n); } } //创建两个新的函数 var pow2 = make_pow(2); var pow3 = make_pow(3); pow2(5); //25 pow3(7); //343
8.Match 函数
大家注意到,上面我们使用了一个新的函数,Match.pow(x,y),这个函数的作用是:返回底数(x)的指定次(y)幂,Match 本身就是一个庞大的数学函数库,下面我们再来学习一个新函数 :随机数
Math.random();,结果为 0 - 1 之间的一个随机数 (包括0,不包括1)
例如我们现在来生产一个 1-10之间的随机数:
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Document</title> <script type="text/javascript"> for (var i =0;i< 10;i++) { alert(parseInt(Math.random() * 10)); } </script> </head> <body> </body> </html>
9. eval 函数
eval();是 JS 中的函数,可以将字符转化为可执行的 JS 代码,下面我们来看几个例子:
var a = "5 + 10"; alert(eval(a)); //15 var str = "function(){alert('a')}"; //直接变函数 等价 var str = function(){alert('a')}; str = eval("("+str+")"); str();
相关文章推荐
- JQuery1——基础($对象,选择器,对象转换)
- Android学习笔记(二九):嵌入浏览器
- Android java 与 javascript互访(相互调用)的方法例子
- JavaScript演示排序算法
- javascript实现10进制转为N进制数
- 最后一次说说闭包
- Ajax
- 2019年开发人员应该学习的8个JavaScript框架
- HTML中的script标签研究
- 对一个分号引发的错误研究
- 异步流程控制:7 行代码学会 co 模块
- ES6 走马观花(ECMAScript2015 新特性)
- JavaScript拆分字符串时产生空字符的原因
- Canvas 在高清屏下绘制图片变模糊的解决方法
- Redux系列02:一个炒鸡简单的react+redux例子
- JavaScript 各种遍历方式详解
- call/apply/bind 的理解与实例分享