2016.3.23__ JavaScript基础_3__第十四天

Posted MR_LP

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2016.3.23__ JavaScript基础_3__第十四天相关的知识,希望对你有一定的参考价值。

最近感冒了,身体太疲惫,同时我也发现每天更新一篇确实不能保证我的文章的质量,与其每天写一些水文,不如静下心来把一些知识梳理好再呈现给大家。

所以很抱歉的通知大家,这个系列从今天起,更新日期由每日一更改为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,[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 , 我们可以利用闭包创建新的函数 pow2pow3:

    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__第十四天的主要内容,如果未能解决你的问题,请参考以下文章

001_JS基础_JavaScript简介

前端基础之JavaScript_1

从零开始学_JavaScript_系列——jquery(基础,选择器,触发条件,动画,回调函数)

从零开始学_JavaScript_系列——dojo(基础,动画移动,重力模拟,动画合并,添加标签)

Web前端开发工程师知识体系_3_JavaScript基础

javascript基础集锦_Json