JavaScript || 函数

函数

  • JavaScript中,函数指只定义一次,但能够屡次被屡次执行或调用的一段JavaScript代码。与数组相似,JavaScript中函数是特殊的对象,拥有自身属性和方法算法

  • 每一个函数对象都有prototypelength属性,bindapply()call()方法。函数的特殊性在于:能够经过函数调用执行函数体中的语句。编程

  • 函数是对象,因此能够赋值给变量、做为参数传递进其余函数、挂载到对象上做为方法数组

1 函数定义

函数定义总共有三种方法:函数定义表达式、函数声明语句和new Function()浏览器

  • 可是new Function()使用不多,由于经过它建立的函数不使用词法做用域,建立的函数都在全局做用域被调用。缓存

  • 函数定义表达式和函数声明语句都利用关键字function来定义函数闭包

    // 函数声明语句
    function funcName([arg1 [, arg2] [..., argn]]) {
      statements
    }
    //函数定义表达式
    var funcName = function([arg1 [, arg2] [..., argn]]) {
      statements
    }
  • 函数名标识符funcName:引用新定义的函数对象app

  • 参数列表:函数中的参数与函数体中的局部变量相同,function(x)至关于在函数体中var x;编程语言

  • { statments }:构成函数体的语句,调用函数后执行的语句ide

1.1 变量提高

JavaScript中由var关键字声明的变量存在变量提高:将变量声明提高到做用域的顶部,但赋值仍保留在原处。因此函数声明语句和函数定义表达式有本质的区别函数式编程

  • 函数声明语句:将函数声明和函数的赋值都提高到做用域的顶部,在同一个做用域中能够出现调用在函数定义以前;

ECMAScript容许函数声明语句做为顶级语句,能够出如今全局做用域中、也能够出如今嵌套函数中,但不能出如今循环、判断、try-catch-finallywith语句中。函数定义表达式没有限制

  • 函数定义表达式:与var声明的普通变量相同,只是将变量声明提高到做用域顶部,但赋值仍然保留在原处,不能在定义前使用

    //没有显式指明返回值的函数,默认返回undefined
    //输出对象o的每一个属性的名称
    function printPrps(o) {
      for(var prop in o) {
        console.log(prop + ": " + o[prop] + "\n");
      }
    }
    
    //计算笛卡尔坐标系中两点间的距离
    function distance(x1, y1, x2, y2) {
      var dx = x2 - x1;
      var dy = y2 - y1;
      return Math.sqrt(dx * dx + dy * dy);
    }
    
    // 计算阶乘的递归函数,x!是x到1间(步长1)的累乘
    function factorial(x) {
      //递归结束标志
      if(x <= 1) {
        return 1;
      }
      return x * factorial(x - 1);
    }
    
    //将函数表达式赋值给变量
    var square = function (x) {return x * x;};
    // 函数表达式能够包含函数名,在递归时颇有用
    //
    var f = function fact(x) {
      if(x <= 1) {return 1;}
      return x * fact(x -1);
    };
    
    // 函数表达式能够做为参数传递给其余函数
    data.sort(function(a, b) {return a  - b;});
    
    //定义后当即调用函数表达式
    var tensquare = (function(x) {return x * x;})(10);

1.2 嵌套函数

JavaScript中,函数能够嵌套在其余函数中。内部函数能够访问外部函数的局部变量和参数。

//  内部函数square能够访问到外部函数的参数a、b和局部变量c
function hypotenuse(a, b) {
  var c = 10;
  function square(x) {return x * x;}
  return Math.sqrt(square(a) + square(b) + square(c));
}

2 函数的调用

在定义函数时,函数体中的代码不会执行,只有在调用函数时,才执行函数体中的语句。有四种方式能够调用函数:

  1. 做为普通函数

  2. 做为对象的方法

  3. 做为构造器函数

  4. 使用函数的call()apply()方法间接调用

2.1 调用函数

使用调用表达式来调用普通函数,每一个调用表达式由多个函数表达式组成。每一个函数表达式包括函数对象、括号和传入的实参组成。

  • 每次调用会拥有本次调用的上下文this;在ES5非严格模式下,普通函数的this值是全局对象;在严格模式下是undefined

  • 以函数形式调用的函数一般不使用this关键字

  • 若是函数没有显式return语句返回一个值,默认返回undefined

  • 传入的实参是由逗号分隔的0个或多个函数表达式

    // 调用printProps()函数,传入对象做为实参便可
    printPrps({x: 1});  
    // 调用distance()函数
    var total = distance(0, 0, 2, 1) + distance(2, 1, 3, 5);
    // 调用factorial()函数
    var probability = factorial(5) / factorial(13);

2.2 方法调用

方法是保存在JavaScript对象属性中的函数。

  • 对方法调用的参数和返回值处理与函数调用相同

  • 方法调用由两个部分组成:对象.属性名(),其中属性名是值为函数的属性

  • 方法调用中:调用上下文指调用方法的对象,使用this关键字引用

    printProps({x: 1});
    var total = distance(0, 0, 2, 1) + distance(2, 1, 3, 5);
    var probability = factorial(5) / factorial(13);
    
    
    var calculator = {  //对象字面量
      operand1: 1,
      operand2: 2,
      add: function() {
        //用this关键字指代当前对象calculator
        this.result = this.operand1 + this.operand2;
      }
    };
    calculator.add();      //调用其add方法,使calculator对象得到result属性
    calculator.result;   //   ==> 3

方法和this关键字是面向对象的核心,任何函数做为方法调用时会传入一个隐式实参(指代调用方法的对象this),基于this的方法能够执行多种操做。

  • this是一个关键字,不是变量名、属性名,JavaScript不容许为this赋值,可是能够将其赋值给其余变量

  • this没有做用域限制,可是嵌套的函数不会从调用它的函数中继承this

    • 嵌套函数若是做为方法调用,this的值指向调用它的对象;

    • 嵌套函数若是做为函数调用,this不是全局变量(ES5非严格模式),就是undefined(ES5严格模式)

    • 嵌套函数的this并不指向调用它的外层函数的上下文

  • 在外层函数中使用变量将外层函数的this对象及arguments属性保存下来,在嵌套函数中即可以访问

    var o = {
      m: function() {
        var self = this;             // 保存this(指向o对象)在变量self中
        console.log(this === o);    // ==> true,this指向o对象
        f();                        //将f()做为函数调用
    
        function f() {
          console.log(this);  //  ==> window严格模式下,嵌套函数做为函数来调用,其this是undefined;非严格模式下是全局对象
          console.log(this === o);   //false,此处的this指向全局对象或undefined
          console.log(self === o);   //true,self指向外部函数的this值
        }
      }
    };
    o.m();       //调用对象o的方法m()

2.3 构造函数调用

若是函数或方法调用前有关键字new,函数或者方法便做为构造函数来调用。构造函数会建立一个新对象,新对象继承构造函数的prototype属性。

  • 做为构造器函数的调用,会将新建立的对象做为其调用上下文(this指向新建立的对象),在构造器函数中使用this引用新建立的对象。

2.4 间接调用call()apply()

函数是对象,每一个函数都有call()apply()两个方法,做用是改变函数运行时的上下文context--改变函数体内部this的指向
,由于JavaScript中有函数定义时上下文函数运行时上下文函数中上下文能够改变的概念。

  • call()apply()做用都是动态改变函数体内this指向,只是接受参数形式不太同样。

  • call()须要将参数按顺序传递进函数,而且知道参数的数量(参数数量肯定时使用)

  • apply()将参数放在数组中传进函数(参数数量不肯定时使用)

call()apply()存在的意义

在JavaScriptOOP中,使用原型实现继承,call()apply()是用于不一样对象间的方法复用。当一个object没有某个方法,可是另外一个objAnother对象有,能够借助call()apply()使object能够操做objAnother对象的方法。

function Cat() {}
function Dog() {}

Cat.prototype = {
  food: "fish",
  say: function () {
    console.log("I love " + this.food);
  }
};

Dog.prototype = {food: "bone"};

var bCat = new Cat();
var bDog = new Dog();
bCat.say();                 //  ==> "I love fish"
bCat.say.call(bDog);          //==>"I love bone",bDog对象使用bCat对象的say方法,输出自身的`this.food`属性

3 函数的实参和形参

实参和形参是相对的概念,在函数定义时指定的参数叫作形参;在函数调用时传入的参数叫作实参。对于须要省略的实参,可使用null或undefined`做为占位符。

3.1 参数默认值

若是调用函数时,传入的实参个数arguments.length小于定义时形参的个数arguments.callee.length,剩余的形参都被设置为undefined。对能够省略的值应该赋一个合理的默认值。

//  将对象obj中可枚举的自身属性追加到数组a中,并返回数组a
//  若是省略a,则建立一个新数组,并返回这个新数组
function getPropertyNames(obj, /*optional*/ a) {
  if(!a) { a = []; }       //若是未传入a,则使用新数组。
  // a = a || [];代替写法更有语义
  for(var prop in obj) {
    if(!obj.hasOwnProperty(prop)) {continue;}
    a.push(prop);
  }
  return a;
}
// 调用,出入一个参数或两个参数
var a = getPropertyNames(obj);   //将obj的属性存储到一个新数组中
getPropertyNames(obj, arr);   //将obj的属性追加到arr数组中

函数中的参数等同于函数体内的局部变量,具备函数的做用域。

3.2 参数对象

函数体内,标识符arguments指向实参对象的引用,实参对象是一个类数组对象,能够经过下标访问每一个传入的参数。

  • arguments仅是一个标识符,严格模式下不能赋值;

  • 应用场景:函数包含固定个数的必须参数,随后包含不定数量的可选参数

    // 能够接收任意个数的实参,
    // 接收任意数量的实参,返回传入实参的最大值,内置的Math.max()方法功能相似
    function max(/*...optional*/) {  //实参个数不能为0
      var maxNum = Number.NEGATIVE_INFINITY;   //将保存最大值的变量初始化
      for(var i in arguments) {
        maxNum = (arguments[i] > maxNum) ? arguments[i] : maxNum;
      }
      return maxNum;
    }

3.3 calleecaller属性

  • callee是ECMAScript规范中arguments对象的属性:表明当前正在执行的函数。

  • caller是非标准的,只是浏览器基本都实现了这个属性:带表调用当前函数的函数。

  • 在严格模式中,对这两个属性读写都会产生错误

    // arguments的callee属性用在匿名函数的递归实现
    var factorial = function(x) {
      if(x <= 1) {return 1;}
      return x * arguments.callee(x - 1);
    }

3.4 将对象属性做为参数

在定义一个函数时,若是传入的参数多于3个,在调用时按顺序传入会变得很麻烦。一种解决方式是传入key/value形式的参数,无需关注参数的顺序。

  • 在定义函数时,形参指定为一个对象;

  • 调用函数时,将整个对象传入函数,无需关心每一个属性的顺序。(性能会差,参数须要在对象中去查找值)

    // 将原始数组的length复制到目标数组
    // 开始复制原始数组的from_start元素
    // 而且将其复制至目标数组的to_start中
    // 参数复杂,调用时顺序难以控制
    function arrayCopy(array, from_start, target_arr, to_start, length) {
      // (原始数组, index, 目标数组, index, length)
      {
        // 实现逻辑
      }
    }
    
    // 无需关心参数顺序的版本,效率略低
    // from_start和to_start默认为0
    function easyCopy(args) {
      arrayCopy(args.array,
                args.from_start || 0,
                args.target_arr,
                args.to_start || 0,
                args.length);
    }
    // easyCopy()的调用
    var a = [1, 2, 3, 4];
    var b = [];
    easyCopy({array: a, target_arr: b, length: 4});

3.5 实参类型

JavaScript在定义函数时并未声明形参类型,形参总体传入函数体前不会作类型检查,若是对传入的实参有某种限制,最好在函数体内增长类型检查的代码。

// 返回数组或类数组a中元素的累加和
// 数组a中的元素必须是数字,null和undefined被忽略
// 类型检查严格,可是灵活性不好
function sum(a) {
  if(isArrayLike(a)) {  // a是数组或类数组
    var result = 0;
    for(var i in a) {
      var element = a[i];
      if(element == null) {continue;}   //跳过null和undefined
      if(isFinite(element)) {
        result += element;
      } else {
        throw new Error("sum(): elements must be finite number");
      }
    }
    return result;
  } else {
    throw new Error("sum(): arguments must be array-like");
  }
}

4 函数做为值

函数定义及调用是JavaScript中的词法特性;同时JavaScript中函数是一个对象:

  • 能够赋值给变量

  • 存储在对象的属性中或者数组的元素中

  • 做为参数传入另外一个函数:例如Array.sort()方法,用来对数组元素进行排序。可是排序的规则有不少中,将具体规则封装在函数中,传入sort()。函数实现对任意两个值都返回一个值,指定它们在排序好数组中的前后顺序

    // 简单函数
    function add(x, y) {return x + y;}
    function subtract(x, y) {return x - y;}
    function mutiply(x, y) {return x * y;}
    function divide(x, y) {return x / y;}
    
    // 这个函数以上面一个函数做为参数,并传入两个操做数,使用传入的函数来调用
    // 过程抽象:两个数能够执行加、减、乘、除四个操做,将四个运算抽象为操做符,根据操做符不一样,执行不一样的函数
    function operate(operator, operand1, operand2) {
      return operator(operand1, operand2);
    }
    // 执行(2 + 3) + (4 * 5)
    var i = operate(add, 2, 3) + operate(mutiply, 4, 5);   // ==>25
    
    // 另一种实现
    var  operators = {
      add: function(x, y) {return x + y;},
      subtrack: function(x, y) {return x + y;},
      mutiply: function(x, y) {return x + y;},
      divide: function(x, y) {return x + y;},
      pow: Math.pow
    };
    function operate2(operator, operand1, operand2) {
      if(typeof operators[operator] === 'function') {
        return operators[operator](operand1, operand2);
      } else {
        throw "unknown operator";
      }
    }
    // 计算("hello" + " " + "world")的值
    operate2("add", "hello", operate2("add", " ", "world"));   //  ==> "hello world"
    operate2("pow", 10, 2);    // ==> 100

自定义属性

函数是对象,能够拥有属性。对于函数中的静态变量,能够直接存入函数的属性中。

// 初始化函数对象的计数器属性,函数声明会被提早,因此能够先给他的属性赋值
uniqueInteger.counter = 0;
// 每次调用这个函数,都会返回一个不一样的整数,使用counter属性保存下次要返回的值
function uniqueInteger() {
  return uniqueInteger.counter++;   // 先返回计数器的值,再自增1
}

5 函数做为命名空间

JavaScript中只存在函数做用域和全局做用域,没有块级做用域。可使用自执行函数用做临时命名空间,这样不会污染全局变量。

(function() {/* 模块代码 */})();  //注意调用括号的位置,两种写法都可
(function() {/* 模块代码 */} ());

6 闭包

编程界崇尚优雅简洁惟美,不少时候若是你以为一个概念很复杂,那么多是你理错了

闭包在JavaScript中,指内部函数老是能够访问其所在的外部函数中声明的变量和参数,即便外部函数被返回(调用结束)。

  • Closure使JavaScript使当前做用域可以访问到外部做用域中的变量;

  • 函数是JavaScript中惟一拥有自身做用域的结构,因此Closure的建立依赖于函数

6.1 如何理解

var scope = "global scope";
function checkScope() {
  var scope = "local scope";
  function f() {return scope;}
  return f;   //将函数对象返回
}
checkScope()();    //  ==>  "local scope"

图片描述

  1. 在JavaScript中,每一个函数在定义时会建立一个与之相关的做用域链,而且在程序执行期间一直存在

    • 外部函数checkScope有自身的做用域链,内部函数f有自身单独的的做用域链)

  2. 每次调用函数会建立一个新对象来保存参数和局部变量,并将其添加到做用域链。

    • 当函数返回时,将绑定的新对象从做用域链上删除。若是没有其余变量引用该对象、或该对象没有保存在某个对象的属性中,它会被当作垃圾回收。

    • 若是没有外部变量引用checkScope调用函数时建立的临时对象,函数return后便被垃圾回收

  3. 若是checkScope定义有嵌套函数f,并将f做为返回值或保存在某个对象的属性中。至关于有一个外部引用指向嵌套函数。

    • f有自身的做用域链和保存参数与局部变量的对象

    • fcheckScope函数体内,能够访问外部函数中全部的变量和参数

综上所述:JavaScript中的函数,经过做用域链和词法做用域二者的特性,将该函数定义时的所处的做用域中的相关函数进行捕获和保存,从而能够在彻底不一样的上下文中进行引用

6.2 注意点

  1. 每一个函数调用都有一个this值和arguments对象,须要在外部函数中用变量保存this值和arguments对象,Closure才能够访问到外部函数的这两个值。that = thisouterArguments = arguments

  2. Closure是经过调用外部函数返回内部嵌套函数建立的,每次调用外部函数都会建立一个Closure可是每一个Closure共享外部函数声明的变量,不会为每一个Closure单首创建一份外部做用域的副本

    // 函数返回一个返回v的函数
    function constFunc(v) {
      return function() {return v;};
    }
    //建立一个数组用来保存常数
    var funcs = [];
    for(var i=0; i<10; i++) {
      funcs[i] = constFunc(i);  // 建立了10个Closure,每一个Closure的值不一样,由于每次传入外层函数constFunc的值不一样
    }
    console.log(funcs[6]());   //  ==> 6
    
    function  constFuncs() {
      var funcs = [];
      for(var i=0; i<10; i++) {
        funcs[i] = function() {return i;};   // 建立10个Closure,但10个Closure在同一个外层函数constFuncs内,共享它的局部变量。
      }                                     // 10个Closure建立完毕后,i的值变为0,因此每一个Closure返回的值都是0
      return funcs;
    }
    var foo = constFuncs();
    console.log(foo[4]());    //  ==> 10
  3. CLosure中部分资源不能自动释放,容易形成内存泄漏

内存泄漏指因为疏忽或错误形成程序未能释放已经再也不使用的内存(即再也不利用的值或对象依然占据内存空间)


7 函数的属性、方法和构造函数

JavaScript中函数是对象,每一个函数都有lenghtprototype属性;每一个函数都有call()apply()bind()方法,而且能够利用函数的构造函数Function()来建立函对象。

7.1 length属性

函数对象的length属性是只读的,用于获取定义函数时指定的形参个数。能够用来检验定义的参数与传入的参数是否相同。

// arguments.callee不能在严格模式下工做
function check(args) {
  var actual = args.length;
  var expected = args.callee.length;      // arguments.callee指代函数自己
  if(expected !== actual) {
    throw Error("Expected:" + expected + " args; got " + actual + "args;");
  }
}
// 测试函数,只有传入三个函数才不会报错
function f(x, y, z) {
  check(arguments);
  return x + y + z;
}

7.2 prototype属性

每一个函数都有一个prototype属性,指向一个原型对象的引用,每一个函数的原型对象都不一样

  • 将函数用做建立对象的构造器函数使用时,新建立的对象会从函数的原型对象上继承属性

7.3 call()apply()

call()apply()用于动态改变this的指向,使对象的方法能够借用给别的对象使用。

7.4 bind()

bind()方法的做用是将函数绑定至某个对象,bind()方法的返回值是一个新的函数对象

  • f()函数调用bind()方法绑定至对象o,用变量g来接收bind()返回的函数,(以函数调用形式)调用g时,会将原始函数f当作对象o的方法来使用。

    var f = function(y) {return this.x + y;};
    var o = {x: 2};
    var g = f.bind(o);   // 将f()绑定到o对象上
    console.log(g(6));    //  ==>  以函数调用的方式调用g(x),至关于调用o.f(x)
    
    // 实现bind()绑定
    function bind(f, o) {
      if(f.bind) {   //若是bind()方法存在,使用bind()方法
        return f.bind(o);
      } else {
        return function() {  //利用apply()使o对象来调用f()方法,而且传入类数组对象参数arguments
          return f.apply(o, arguments);  //arguments是调用绑定函数时传入的参数
        };
      }
    }
  • bind()第一个实参是要绑定方法的对象(本质是将函数的this指向改成传入的对象),同时后面的实参也会绑定至this,函数式编程中的currying 柯里化。

    var sum = function(x, y) {return x + y;};
    // 建立一个相似sum的新函数,可是this绑定到null
    // 而且第一个参数绑定为1,新的函数只指望传入一个参数
    var g = sum.bind(null, 1);  //  将sum的第一个参数x绑定为1
    console.log(g(3));   // ==> 4,由于x绑定为1,将3做为参数传入y
    
    function f(y, z) {return this.x + y + z;}
    var g = f.bind({x: 2}, 3); // 将f函数绑定到对象{x: 2},将3绑定到函数的第一个参数y,新建立的函数传入一个参数
    console.log(g(1));   // ==>6
  • 模拟实现bind()方法:bind()方法返回的是一个Closure

    if(!Function.prototype.bind) {  //不支持bind方法
      Function.prototype.bind = function (o) {
        var self = this;     // 保存bind()中的this与arguments,便于在嵌套函数中使用
        var boundArgs = arguments;
        // bind()方法返回一个函数对象
        return function() {
          // 建立一个实参列表,将传入bind()的第二个及之后的实参都传入这个函数
          var args = [];
          // 传入bind()函数的参数处理,从第二位开始
          for(var i=1; i<boundArgs.length; i++) {args.push(boundArgs[i]);}
          // 将调用新函数时传入的参数继续添加到args中
          for (var j=0; j < arguments.length; j++) {args.push(arguments[j]);}
    
          // 将self做为o的方法来调用
          return self.apply(o, args);
        };
      };
    }

注意点

bind()方法的某些特性是上述模拟方法不能替代的。

  1. bind()方法返回一个真正的函数对象,函数对象的length属性是绑定函数的形参个数减去绑定的实参个数(length的值不能小于0

    function f(y, z) {return this.x + y + z;}  // 绑定函数f的形参个数时2
    var g = f.bind({x: 2}, 3); // 绑定的实参个数是1(从第二位开始是传入绑定函数的实参),即将3传递给f的第一个参数y
    g(1);   // ==> 6,继续将1传递给函数f的形参z
  2. ES5的bind()方法能够顺带作构造函数,此时将会忽略传入bind()方法的this,原始函数以构造函数的形式调用,其实参已经绑定。

  3. bind()方法返回的函数并不包含prototype属性(普通函数的固有prototype属性是不能删除的);而且将绑定的函数用做构造器函数时所建立的对象,从原始为绑定的构造器函数中继承prototype

    • 若是将g()做为构造函数,其建立的对象与直接利用f当作构造函数建立的对象原型是同一个prototype

7.5 toString()方法

根据ECMAScript规范,函数的toString()方法返回一个字符串,字符串与函数声明语句的语法有关。

  • 大多数函数的toString()方法都返回函数的完整源码

  • 内置函数的toString()方法返回一个相似"[native code]"的字符串做为函数体

7.6 Function()构造函数

  • Function()构造函数运行JavaScript在运行时动态建立并编译函数

  • 每次调用Function()构造函数都会解析函数体,并建立新的函数对象。若是在循环中执行Function(),会影响效率;

  • Function()建立的函数不使用词法做用域,函数体的代码编译总在全局做用域执行

Function()在实际编程中使用不多。

8 函数式编程

JavaScript并不是函数式编程语言,但JavaScript中函数是对象,能够像对象同样操控,因此能够应用函数式编程技术

8.1 使用函数处理数组

假设有一个数组,元素都是数字,要计算全部元素的平均值与标准差。

  • 非函数式编程风格

    var data = [1, 1, 3, 5, 5];
    var total = 0;  //平均数是全部元素的和除以元素的个数
    data.forEach(function(value) {
      total += value;
    });
    var mean = total / data.length;
    //标准差:先计算每一个元素与平均值的差的平方的和
    var sum = 0;
    data.forEach(function(value) {
      var tmp = value - mean;
      sum += tmp * tmp;
    });
    //标准差stddev
    var stddev = Math.sqrt(sum / data.length-1);
  • 函数式编程风格,利用map()reduce()来实现,抽象出两个过程:

    • 求平均值和标准差会用到求一个数组中全部元素的和:使用reduce()

    • 求数组中每一个元素的平方:使用map()

      // 定义求和、求积两个过程
      var add = function(x, y) {return x + y;};
      var square = function(x) {return x * x;};
      
      var data = [1, 1, 3, 5, 5];
      // reduc()实现数组求和
      var avg = data.reduce(add) / data.length;
      // map()实现差的平方,返回操做后的数组,再调用reduce()
      var sum = data.map(function(value) {return value - avg;});
      var stddev = Math.sqrt(sum.map(square).reduce(add) / (data.length - 1));

8.2 高阶函数

高阶函数higher-order function指操做函数的函数,接收一个或多个函数做为参数,并返回一个新函数。

// 高阶函数not()返回一个新函数,新函数将它的实参传入f()
function not(f) {
  return function() {    // 返回一个新函数
    var result = f.apply(this, arguments);    // 调用f()
    return !result;   // 对结果求反
  };
}
var even = function (x) {   //判断一个数是不是偶数
  return x % 2 === 0;
};
var odd = not(even);     // 一个新函数,所作的事情与even()相反
[1, 1, 3, 5, 5].every(odd);    // ==> true每一个元素都是奇数

// mapper()返回的函数的参数是数组,对每一个元素执行函数f()
// 返回全部计算结果组成的数组
function mapper(f) {
  return function(a) {
    return map(a, f);
  };
}
var increment = function(x) {return x + 1;};
var incrementer = mapper(increment);
incrementer([1, 2, 3]);

8.3 不彻底函数

将一次完整的函数调用拆分为屡次函数调用,每次传入的实参都是完整实参的一部分,每一个拆分开的函数叫作不彻底函数partial function,每次函数调用叫作不彻底函数调用partial application特色是每次调用都返回一个函数,知道获得最终运行结果为止

if(!Function.prototype.bind) {  //不支持bind方法
  Function.prototype.bind = function (o) {
    var self = this;     // 保存bind()中的this与arguments,便于在嵌套函数中使用
    var boundArgs = arguments;
    // bind()方法返回一个函数对象
    return function() {
      // 建立一个实参列表,将传入bind()的第二个及之后的实参都传入这个函数
      var args = [];
      // 传入bind()函数的参数处理,从第二位开始
      for(var i=1; i<boundArgs.length; i++) {args.push(boundArgs[i]);}
      // 将调用新函数时传入的参数继续添加到args中
      for (var j=0; j < arguments.length; j++) {args.push(arguments[j]);}

      // 将self做为o的方法来调用
      return self.apply(o, args);
    };
  };
}
  • 函数f()bind()方法返回一个新函数,给新函数传入特定的上下文和一组指定的参数,而后调用函数f()。传入bind()的实参都是放在传入原始参数的实参列表开始的位置。
    但有时但愿将传入bind()的实参放在完整实参列表的右侧:

// 实现一个工具函数,将类数组对或对象转化为真正的数组
// 将arguments对象转化为真正的数组
function array(a, n) {return Array.prototype.slice.call(a, n || 0);}

// 这个函数的实参传递至左侧
function partialLeft(f) {
  var args = arguments;   // 保存外部的实参数组
  return function() {    // 返回一个函数
    var a = array(args, 1);  // 开始处理外部的第一个args
    a = a.concat(array(arguments));  //而后增长全部的内部实参
    return f.apply(this, a);   // 基于这个实参列表调用f()
  };
}

// 这个函数的实参传递至右侧
function partialRight(f) {
  var args = arguments;   // 保存外部的实参数组
  return function() {    // 返回一个函数
    var a = array(arguments);  // 从内部参数开始
    a = a.concat(array(args, 1));  //而后从外部第一个args开始添加
    return f.apply(this, a);   // 基于这个实参列表调用f()
  };
}

// 这个函数的实参被用做模板,实参列表中的undefined值都被填充
function partial(f) {
  var args = arguments;
  return function() {
    var a = array(args, 1);
    var i = 0, j = 0;
    // 遍历args,从内部实参填充undefined值
    for(; i<a.length; i++) {
      if(a[i] === undefined) {a[i] = arguments[j++];}
    }
    a = a.concat(array(arguments, j));
    return f.apply(this, a);
  };
}

// 函数带有三个实参
var f = function(x, y, z) {
  return x * (y - z);
};
// 注意三个不彻底调用间的区别
partialLeft(f, 2)(3, 4);   //  ==> -2:绑定第一个实参 2*(3-4)
partialRight(f, 2)(3, 4);   //  ==> 6:绑定最后一个实参 3*(4-2)
partial(f, undefined, 2)(3, 4);   //  ==> -6:绑定中间的实参 3*(2-4)
  • 利用不彻底函数的编程技巧,能够利用已有的函数来定义新的函数

8.4 记忆

在函数式编程中,把将上次计算记过缓存的技术叫作记忆memerization

本质上是牺牲算法的空间复杂度以换取更优的时间复杂度。由于在客户端中JavaScript代码的执行速度每每成为瓶颈。

// 返回f()的带有记忆功能的版本(缓存上次计算结果)
// 只有在f()的实参字符串表示都不相同时才工做
function memorize(f) {
  var cache = {};   //将值保存在闭包内
  return function() {
    // 将实参转为字符串形式,并将其用做缓存的键
    var key = arguments.length + Array.prototype.join.call(arguments, ",");
    if(key in cache) {
      return cache[key];
    } else {
      return cache[key] = f.apply(this, arguments);
    }
  };
}
// memorize()建立新对象cache并将其保存在局部变量中,对于返回的函数来讲它是私有的(在闭包中)。
// 返回的函数将它的实参数组转化为字符串,并将字符串用做缓存对象的属性名。若是在缓存中有这个值,则直接返回
// 若是没有,调用既定函数对实参进行计算,将计算结果缓存并返回

// 返回两个整数的最大公约数
function gcd(a, b) {
  var t;
  if(a < b) { t= b; b = a; a = t; }
  while(b !== 0) {
    t = b;
    b = a %  b;
    a = t;
  }
  return a;
}
var gcdmemo = memorize(gcd);
gcdmemo(85, 187);   //  ==> 17
//注意写一个递归函数时,每每须要记忆功能
// 调用实现了记忆功能的递归函数
var factorial = memorize(function(n) {
  return (n <= 1)? 1 : n * factorial(n - 1);
});
factorial(5);   // ==> 120,同时缓存了1~4的值。
相关文章
相关标签/搜索