2016.3.23__ JavaScript基础_3__第十四天
Posted MR_LP
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2016.3.23__ JavaScript基础_3__第十四天相关的知识,希望对你有一定的参考价值。
最近感冒了,身体太疲惫,同时我也发现每天更新一篇确实不能保证我的文章的质量,与其每天写一些水文,不如静下心来把一些知识梳理好再呈现给大家。
所以很抱歉的通知大家,这个系列从今天起,更新日期由每日一更改为3~5日一更,实际时间只会更短,不会更长。
同时也很感谢很多小伙伴这么长时间的陪伴,谢谢大家。
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,[b],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。
这正是因为 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();
以上是关于2016.3.23__ JavaScript基础_3__第十四天的主要内容,如果未能解决你的问题,请参考以下文章
从零开始学_JavaScript_系列——jquery(基础,选择器,触发条件,动画,回调函数)