目录

函数定义

  • 普通 函数声明 会被前置

    function abs(x) {
        ...
    }
  • 赋值 等价上一种 结尾加 ;

    var abs = function (x) {
        ...
    };
  • 立即调用函数表达式 IIFE

    var area = (
        function(){
            var width = 10;
            var height = 10;
            return width * height;
        }()
    );
  • Function构造器

    var func = new Function('a','b','console.log(a+b);'); //  调用func(1,2); //3
     // 等价
    var func = Function('a','b','console.log(a+b);');

参数

  • 基本类型是作为值穿入,对象作为引用传入

  • 传入的参数比定义的少也没有问题,形参接收到的值为 : undefined

js没有重载

重载:为同一个函数名,编写不同的定义,根据参数决定调用的函数!
js 没有重载,因为 js 是不检查参数的。后面的同名函数直接覆盖掉前面的!

arguments 不定参数

  • arguments[0] === a

  • foo.name 函数名

  • foo.length 形参个数

  • arguments.length 实参个数

    // 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变为默认值
        }
        console.log(arguments.callee === foo); //true
    }
    foo(1,2);
    console.log(foo.length); //3
    console.log(foo.name);   //foo

rest参数 ES6

  • 为了获取除了已定义参数a、b之外的参数,我们不得不用arguments,并且循环要从索引2开始以便排除前两个参数,这种写法很别扭,只是为了获得额外的rest参数,有没有更好的方法?
    ES6标准引入了rest参数,上面的函数可以改写为:

    function foo(a, b, ...rest) {
        console.log('a = ' + a);
        console.log('b = ' + b);
        console.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)。

变量

全局变量

  • JavaScript默认有一个全局对象window,全局作用域的变量实际上被绑定到window的一个属性

  • JavaScript实际上只有一个全局作用域。任何变量(函数也视为变量),如果没有在当前函数作用域中找到,就会继续往上查找,最后如果在全局作用域中也没有找到,则报ReferenceError错误。

    'use strict';
    
    var course = 'Learn JavaScript';
    alert(course); // 'Learn JavaScript'
    alert(window.course); // 'Learn JavaScript'
    
    function foo() {
        alert('foo');
    }
    foo(); // 直接调用foo()
    window.foo(); // 通过window.foo()调用
  • const 常量 ES6

    'use strict';
    const PI = 3.14;
    PI = 3; // 某些浏览器不报错,但是无效果!
    PI; // 3.14

命名空间

  • 全局减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中

    // 唯一的全局变量MYAPP:
    var MYAPP = {};
    
    // 其他变量:
    MYAPP.name = 'myapp';
    MYAPP.version = 1.0;
    
    // 其他函数:
    MYAPP.foo = function () {
        return 'foo';
    };

局部作用域

  • JavaScript的变量作用域实际上是函数内部,我们在for循环等语句块中是无法定义具有局部作用域的变量的

    'use strict';
    function foo() {
        for (var i=0; i<100; i++) {
            //
        }
        i += 100; // 仍然可以引用变量i
    }
  • let (ES6) 用于申明块级作用域变量

    'use strict';
    function foo() {
        var sum = 0;
        for (let i=0; i<100; i++) {  // i 是快级作用域
            sum += i;
        }
        i += 1; // SyntaxError
    }

变量提升

  • JavaScript的函数定义有个特点,它会先扫描整个函数体的语句,把所有申明的变量提升到函数顶部:

    'use strict';
    function foo() {
        var x = 'Hello, ' + y;
        alert(x);
        var y = 'Bob'; //提升
    }
    foo();

    虽然是strict模式,但语句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++) {
            ...
        }
    }

函数嵌套

  • 内部函数可以访问外部函数定义的变量,反过来则不行

    'use strict';
    function foo() {
        var x = 1;
        function bar() {
            var y = x + 1; // bar可以访问foo的变量x!
        }
        var z = y + 1; // ReferenceError! foo不可以访问bar的变量y!
    }
  • 函数内变量重名 函数在查找变量时从自身函数定义开始,从“内”向“外”查找。如果内部函数定义了与外部函数重名的变量,则内部函数的变量将“屏蔽”外部函数的变量

    'use strict';
    function foo() {
        var x = 1;
        function bar() {
            var x = 'A';
            alert('x in bar() = ' + x); // 'A'
        }
        alert('x in foo() = ' + x); // 1
        bar();
    }

高阶函数

  • 将函数本身作为参数传入函数

    function  sum(num){
        return  num +10;
    }
    function box(a,b){
        return  a(b);
    }
    console.log(box(sum,10)); // 返回 20

map

  • 将函数作用在一个数组[1, 2, 3, 4, 5, 6, 7, 8, 9]上,我们调用Array的map()方法,传入我们自己的函数,就得到了一个新的Array作为结果:

    function pow(x) {
        return x * x;
    }
    var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
    arr.map(pow); // [1, 4, 9, 16, 25, 36, 49, 64, 81]
  • map()作为高阶函数,事实上它把运算规则抽象了,因此,我们不但可以计算简单的f(x)=x2,还可以计算任意复杂的函数,比如,把Array的所有数字转为字符串:

    var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
    arr.map(String); // ['1', '2', '3', '4', '5', '6', '7', '8', '9']

reduce

  • 再看reduce的用法。Array的reduce()把一个函数作用在这个Array的[x1, x2, x3...]上,这个函数必须接收两个参数,reduce()把结果继续和序列的下一个元素做累积计算,其效果就是:

    [x1, x2, x3, x4].reduce(f) = f(f(f(x1, x2), x3), x4)
  • 比方说对一个Array求和,就可以用reduce实现:

    var arr = [1, 3, 5, 7, 9];
    arr.reduce(function (x, y) {
        return x + y;
    }); // 25

filter

用于把Array的某些元素过滤掉,然后返回剩下的元素。
filter()把传入的函数依次作用于每个元素,然后根据返回值是true还是false决定保留还是丢弃该元素。

  • 在一个Array中,删掉偶数,只保留奇数,可以这么写:

    var arr = [1, 2, 4, 5, 6, 9, 10, 15];
    var r = arr.filter(function (x) {
        return x % 2 !== 0;
    });
    r; // [1, 5, 9, 15]
  • 把一个Array中的空字符串删掉,可以这么写:

    var arr = ['A', '', 'B', null, undefined, 'C', '  '];
    var r = arr.filter(function (s) {
        return s && s.trim(); // 注意:IE9以下的版本没有trim()方法
    });
    r; // ['A', 'B', 'C']

sort

  • 可以接收一个比较函数来实现自定义的排序。

    var arr = [10, 20, 1, 2];
    arr.sort(function (x, y) {
        if (x < y) {
            return -1;
        }
        if (x > y) {
            return 1;
        }
        return 0;
    }); // [1, 2, 10, 20]
  • 如果要倒序排序,我们可以把大的数放前面:

    var arr = [10, 20, 1, 2];
    arr.sort(function (x, y) {
        if (x < y) {
            return 1;
        }
        if (x > y) {
            return -1;
        }
        return 0;
    }); // [20, 10, 2, 1]
  • 默认情况下,对字符串排序,是按照ASCII的大小比较的,现在,我们提出排序应该忽略大小写,按照字母序排序。要实现这个算法,不必对现有代码大加改动,只要我们能定义出忽略大小写的比较算法就可以:

    var arr = ['Google', 'apple', 'Microsoft'];
    arr.sort(function (s1, s2) {
        x1 = s1.toUpperCase();
        x2 = s2.toUpperCase();
        if (x1 < x2) {
            return -1;
        }
        if (x1 > x2) {
            return 1;
        }
        return 0;
    }); // ['apple', 'Google', 'Microsoft']

    忽略大小写来比较两个字符串,实际上就是先把字符串都变成大写(或者都变成小写),再比较。

从上述例子可以看出,高阶函数的抽象能力是非常强大的,而且,核心代码可以保持得非常简洁。

  • sort()方法会直接对Array进行修改,它返回的结果仍是当前Array

    var a1 = ['B', 'A', 'C'];
    var a2 = a1.sort();
    a1; // ['A', 'B', 'C']
    a2; // ['A', 'B', 'C']
    a1 === a2; // true, a1和a2是同一对象

函数返回

  • 自动分号机制

    function foo() {
        return; // 自动添加了分号,相当于return undefined;
            { name: 'foo' }; // 这行语句已经没法执行到了
    }
    // vs
    function foo() {
        return { // 这里不会自动加分号,因为{表示语句尚未结束
            name: 'foo'
        };
    }

返回函数

  • 如果不需要立刻求和,而是在后面的代码中,根据需要再计算怎么办?可以不返回求和的结果,而是返回求和的函数

    function lazy_sum(arr) {
        var sum = function () {
            return arr.reduce(function (x, y) {
                return x + y;
            });
        }
        return sum;
    }
    var f = lazy_sum([1, 2, 3, 4, 5]); // function sum() 当我们调用lazy_sum()时,返回的并不是求和结果,而是求和函数
    f(); // 15 调用函数f时,才真正计算求和的结果:

箭头函数 ES6

  • 用于替代 匿名函数

    x => x * x
    //相当于
    function (x) {
        return x * x;
    }
  • 多行语句的

    x => {
        if (x > 0) {
            return x * x;
        }
        else {
            return - x * x;
        }
    }
    // 两个参数:
    (x, y) => x * x + y * y
    
    // 无参数:
    () => 3.14
    
    // 可变参数:
    (x, y, ...rest) => {
        var i, sum = x + y;
        for (i=0; i<rest.length; i++) {
            sum += rest[i];
        }
        return sum;
    }
  • 返回对象

    // SyntaxError:
    x => { foo: x }
    // ok:
    x => ({ foo: x })

箭头函数的 this 指向取决于上下文 也就是它所在的对象

  • 匿名函数的this

    var obj = {
        birth: 1990,
        getAge: function () {
            var b = this.birth; // 1990
            var fn = function () {
                return new Date().getFullYear() - this.birth; // this指向window或undefined
            };
            return fn();
        }
    };
  • =>的this

    var obj = {
        birth: 1990,
        getAge: function () {
            var b = this.birth; // 1990
            var fn = () => new Date().getFullYear() - this.birth; // this指向obj对象
            return fn();
        }
    };
    obj.getAge(); // 25
  • 用call()或者apply()调用箭头函数时,无法对this进行绑定,即传入的第一个参数被忽略

    var obj = {
        birth: 1990,
        getAge: function (year) {
            var b = this.birth; // 1990
            var fn = (y) => y - this.birth; // this.birth仍是1990
            return fn.call({birth:2000}, year);
        }
    };
    obj.getAge(2015); // 25

Generator 多次返回函数

  • function* 定义(注意多出的 * 号),并且,除了return语句,还可以用yield返回多次

    function* foo(x) {
        yield x + 1;
        yield x + 2;
        return x + 3;
    }
  • 正常编写一个产生斐波那契数列的函数

    function fib(max) {
        var
            t,
            a = 0,
            b = 1,
            arr = [0, 1];
        while (arr.length < max) {
            t = a + b;
            a = b;
            b = t;
            arr.push(t);
        }
        return arr;
    }
    
    // 测试:
    fib(5); // [0, 1, 1, 2, 3]
    fib(10); // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
  • 使用 generator 改写

    function* fib(max) {
        var
            t,
            a = 0,
            b = 1,
            n = 1;
        while (n < max) {
            yield a;
            t = a + b;
            a = b;
            b = t;
            n ++;
        }
        return a;
    }
    fib(5); // fib {[[GeneratorStatus]]: "suspended", [[GeneratorReceiver]]: Window}

    fib(5)仅仅是创建了一个generator对象,还没有去执行它

  • 调用generator对象的next() 

    var f = fib(5);
    f.next(); // {value: 0, done: false}
    f.next(); // {value: 1, done: false}
    f.next(); // {value: 1, done: false}
    f.next(); // {value: 2, done: false}
    f.next(); // {value: 3, done: true}

    next()方法会执行generator的代码,然后,每次遇到yield x;就返回一个对象{value: x, done: true/false},然后“暂停”。
    返回的 value 就是 yield 的返回值,done 表示这个 generator 是否已经执行结束了。如果 done 为 true ,则 value 就是 return 的返回值。
    当执行到 done 为 true 时,这个 generator 对象就已经全部执行完毕,不要再继续调用next()了。

  • 使用 for of 访问

    for (var x of fib(5)) {
        console.log(x); // 依次输出0, 1, 1, 2, 3
    }
  • 改写ajax

    ajax('http://url-1', data1, function (err, result) {
        if (err) {
            return handle(err);
        }
        ajax('http://url-2', data2, function (err, result) {
            if (err) {
                return handle(err);
            }
            ajax('http://url-3', data3, function (err, result) {
                if (err) {
                    return handle(err);
                }
                return success(result);
            });
        });
    });
    
    // 改为
    try {
        r1 = yield ajax('http://url-1', data1);
        r2 = yield ajax('http://url-2', data2);
        r3 = yield ajax('http://url-3', data3);
        success(r3);
    }
    catch (err) {
        handle(err);
    }

    看上去是同步的代码,实际执行是异步的。