JavaScript学习——函数

1. 本文是在学习廖雪峰先生的JavaScrip教程 后的归纳

一、JavaScript函数

  1. JavaScript的函数是"头等公民",而且可以像变量一样使用,具有强大的抽象能力
  2. 借助抽象,可以不关心底层具体实现,而直接在最高层次上思考问题
  3. 函数是最基本的代码抽象的方式

二、函数的定义和调用

  1. 常规定义
function abs(x){
       return x===0;
}  
* `function` 关键字,指出函数的定义
* `abs`是函数的名称
* `(x)`括号列出函数的参数,多个参数以`,`分隔
* `{...}`之间的代码是函数体,可以包含若干语句,也可以没有任何语句
* 函数无论有没有`return`都会返回结果,无return只是结果为`undefined`
* 由于JavaScript的函数是一个对象,函数名abs为一个函数对象,函数名abs可以视为指向该函数的变量
  1. 匿名函数定义
    var abs = function(x){
          return x===0;
    };
    
    • 此种情况下,function(x){..}为匿名函数,没有函数名,将该函数赋给变量abs,可以通过变量abs就可以调用该函数
    • 上述定义和常规完全等价,但需注意要在匿名函数末尾加上;,表示赋值语句结束
  2. 函数的调用
    • 调用函数时,按顺序传入参数即可
    • 由于JavaScript允许传入任意个参数而不影响调用,因此,传入的参数比定义的参数多也没问题
    • 传入参数比定义的少也没问题,不过,此时函数的参数将收到undefined,因此,为了避免收到undefine,需要进行类型检查
    function abs(x){
             if(typeof x!=='number'){
                throw 'Not a number';
             }
             return x;
          }
    
  3. arguments
    • Javascript 中还有一个关键字arguments,仅在函数内部起作用,并且永远指向当前函数的调用传入的所有参数,类似Array而非Array
    • 利用arguemnts,可以获取调用者传入的所有参数,也就是,即使函数不定义任何参数,还可以拿到参数的值
    • 小心return语句,由于JavaScript引擎有一个在行末自动添加分号的机制
    function foo(){
        return              //实际解析这样return ;
            {name:'foo'};   // {name:'foo'};
     }
    
    • 把arguments对象转换成一个真正的数组
     var args = Array.prototype.slice.call(arguments); 
    

三、变量作用域

  1. 作用域
    • var 声明的变量是有作用域的
    • 变量在函数体内部声明,则该变量的作用域为整个函数体,在函数体外不可引用该变量
    • 不同函数内部的同名变量相互独立,不受影响
    • 由于JavaScript的函数可以嵌套,内部函数可以访问外部函数定义的变量,反过来,则不行
    • JavaScript的函数在查找变量时从自身函数定义开始,从"内"向"外"查找,如果内部函数定义了与外部函数重名的变量,则内部函数的变量将"屏蔽"外部函数的变量
  2. 变量提升
    • JavaScript 会先扫描整个函数体的语句,把所有声明的变量"提升"到函数顶部
    • JavaScript 引擎自动提升了变量y的声明,但不会提升变量y的赋值
    • 要严格遵守在函数内部首先申明所有变量这一规则,最常见的做法是用一个var申明函数内部用的变量
    function foo(){
        var x = 1,
            y = x + 1,
            z, i;
        for(i=0;i<100;i++){
            ....
        }
    }
    
  3. 全局作用域
    • 不在任何函数内定义的变量就是具有全局作用域
    • JavaScript默认有一个全局对象window,全局作用域实际上被绑定window的一个属性
      'use strict'
       var course = 'JavaScript';
       alert(course);
       alert(window.course);
      
    • 直接访问全局变量course和访问window.course是完全一样的
    • 顶层函数的定义被视为一个全局变量,并绑定到windows对象
    • alert()函数也是window的一个变量
    • JavaScript实际上只有一个全局作用域,任何变量(函数也是变量),如果没有在当前函数作用域中找到,就会继续往上查找,最后在全局作用域中没有找到,则报ReferenceError错误
  4. 名字空间
    • 全局变量会绑定到window上,不同的JavaScript文件如果使用了相同的变量,或者定义了相同名字的顶层函数,会造成命名冲突
    • 减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中
    • 把自己的代码全部放入唯一的名字中,会大大减少全局变量冲突的可能,许多著名的JavaScript库就是这样干的:如JQuery
  5. 局部作用域
    • for循环等语句块中是无法定义具有局部作用域的变量
    • 为了解决块级作用域,ES6引入了新的关键字let,用let代替var可以申明一个块级作用域的变量
  6. 常量
    • 在ES6之前,无法申明一个常量,通常采用大写的变量来表示"这是一个常量,不要修改它的值"
    • ES6标准引入了新的关键字const来定义常量,constlet都具有块级作用域

四、方法

  1. this 关键字
    • 在一个对象中绑定函数,称为这个对象的方法
    • 示例:
    var xiaoming={
        name:'小明',
        birth:1990,
        age:function(){
            var y = new Date().getFullYear();
            return y - this.birth;
        }
    };
    xiaoming.age;//function xiaoming.age()
    xiaoming.age();//返回小明的年纪
    
    • 在一个方法内部,this是一个特殊变量,它始终指向当前对象
    • 要保证this的正确调用,必须用obj.xxx()的形式调用
    • strict模式下让直接调用的函数中的this指向undefined
    • strict模式下,通常在函数没有指向正确的位置,那它将指向全局对象的window
    • 在方法内部定义其他函数时,可以在方法内部加上var that=this;方便函数调用对象的属性
  2. apply
    • 在独立的函数调用中,根据是否是strict模式,this指向undefinedwindow
    • 函数本身的apply方法,接受两个参数:
      • 需要绑定的this变量
      • Array,表示函数本身的参数
    • apply()的类似方法是call(),唯一区别是:
      • apply() 把参数打包成Array在传入
      • cal() 把参数按顺序传入
    • 对普通函数调用,通常把this绑定为null
  3. 装饰器
    • 利用apply(),我们可以动态改变函数的行为
    • JavaScript的所有对象都是动态的,即使内置的函数,也可以重新指向新的函数,如统计某内置函数的调研次数,可以用自定义函数替换掉默认的内置函数
    var count = 0;
    var oldParseInt = parseInt; // 保存原函数
    
    window.parseInt = function () {
        count += 1;
        return oldParseInt.apply(null, arguments); // 调用原函数
    };
    
    // 测试:
    parseInt('10');
    parseInt('20');
    parseInt('30');
    count; // 3
    

五、高阶函数

  1. 定义
    • Higher-order function 高级函数
    • JavaScript的函数其实都是指向某个变量
    • 一个函数可以接收另一个函数作为参数,这种函数称之为高阶函数
    • 编写高级函数就是让函数能够接收别的函数
    • 高级函数有强大的抽象能力,可以使核心代码保持得非常简洁
    function add(x,y,f){
        return f(x)+f(y);
    }
    
  2. map/reduce
    • 示例
      function pow(x){
        return x*x;
      }
      var arr = [1,2,3,4,5,6,7,8,9];
      arr.map(pow);
      
    • map()作为高阶函数,事实上是把运算规则抽象,不但可以计算简单的函数,也可以计算任意复杂的函数
    • 如可以吧Array的所有数字转化为字符串: var arr=[1,2,]; arr.map(String);
    • Array的reduce()把一个函数作用域Array[x1,x2,x3,..]上,这个函数必须接收两个参数,reduce()把结果继续和序列的下一个元素做累积计算
    [x1,x2,x3,x4].reduce(f)=f(f(f(x1,x2),x3),x4);//reduce类似递归
    
    var arr=[1,3,5,7,9];
    arr.reduce(function(x,y){
        return x+y;
    });
    
  3. filter
    • 也是一个常用的操作,它用于把Array的某些元素过滤掉,然后返回剩下的元素
    • map()类似,Arrayfilter()也接收一个函数,和map()不同的时,filter()把传入的函数依次作用于每个元素,然后根据返回值是true还是false决定保留还是丢弃该元素
  4. sort
    • 排序算法: 比较的过程必须通过函数抽象出来,通常规定,对于两个元素xy,如果认为x,则返回-1,如果认为x===y,则返回0,如果认为x>y,返回1
    • 排序算法不用关心具体的比较过程,而根据比较结果直接排序
    • JavaScript的Arraysort()方法是用于排序,排序规则如下:
      • 字符串根据ASCII码进行排序(小写字母的ASCII码在大写字母之后)
      • 默认将所有的元素先转换成String在排序
      • 可以接收一个比较函数来实现自定义排序
    • 示例
    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]
    

六、闭包

  1. 函数作为返回值
    • 高级函数可以接收函数做参数,还可以把函数作为结果值返回
    • 示例
    function lazy_sum(arr){
        var sum = function(){
            return arr.reduce(function(x,y){
               return x+y;
            }
        }
    }
     
    var f= lazy_sum([1,2,3,4,5]);
    f();
    
    • 在上述例子中,函数layz_sum中定义了函数sum,并且,内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为"闭包(Closure)"的程序结构拥有巨大威力
    • 当调用`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()的调用结果互不影响
    
  2. 闭包作用
    • 返回的函数在其定义内部引用了局部变量arr,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用,函数并没有立刻执行,而是调用了f()才行
    • 返回函数不要引用任何循环变量,或者后续会发生变化的变
    • 若一定引用循环变量: 方法是在再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定函数参数的值不变
    function count() {
        var arr = [];
        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);
    • JavaScript 借助闭包,可以封装一个私有变量,闭包就是携带状态的函数,并且它的状态可以完全对外隐藏起来
    'use strict';
    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
    
    • 闭包就是携带状态的函数,并且它的状态可以完全对外隐藏起来
    • 闭包还可以把多参数的函数变成单参数的函数
    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
    

七、generator

  1. 定义
    • generator(生成器)是ES6标准引入的新的数据类型。一个generator看上去像一个函数,但可以返回多次
    • generator 有function*定义,除了return语句,还可以使用yield返回多次
  2. 使用
    • 示例
    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;
    }
    
    • 直接调用generator和调用函数不一样,仅仅是创建了一个generator对象,还没有去执行它
    • 调用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()方法会执行时,每次遇到yield x;就返回一个对象 {value:x,done:true/false},然后暂停,返回value表示yield的返回值,done表示这个generator对象是否已经执行结束,如果donetrue,则value就是return的返回值。
      • 直接用for...of循环迭达generator对象,这种方式不需要自己判断done
       for (var x of fib(5)) {
          console.log(x); // 依次输出0, 1, 1, 2, 3
      }
      
    • generator可以把异步回调代码变成"同步"代码

你可能感兴趣的:(JavaScript学习——函数)