《JavaScript语言精粹》知识点总结(二)

第4章 函数

        函数用于指定对象的行为。一般来说,所谓编程就是将一组需求分解成一组函数与数据结构的技能。


        4.1 函数对象

        在JS中函数就是对象。对象是“名/值”对的集合并拥有一个连到原型对象的隐藏连接。对象字面量产生的对象连接到 Object.prototype。函数对象连接到 Function.prototype。每个函数在创建时附有两个附加的隐藏属性:函数的上下文和实现函数行为的代码。

        函数是对象,但与众不同之处在于它们可以被调用。


        4.2 函数字面量

        函数可以通过函数字面量来创建:

var add = function (a, b) {
  return a + b;
};
        函数字面量包括四个部分:

        一、保留字 function。

        二、函数名,若省略则为匿名函数。

        三、参数,可无。

        四、函数主体。

        函数字面量可以出现在任何允许表达式出现的地方。函数也可以被定义在其他函数中。一个内部函数自然可以访问自己的参数和变量,同时它也能方便地访问它被嵌套在其中的那个函数的参数与变量。

        通过函数字面量创建的函数对象包含一个连到外部上下文的连接。这被称为闭包。它是JS强大表现力的根基。

      

        4.3 调用

        JS中一共有四种调用模式:方法调用、函数调用、构造器调用、apply调用。这些模式在如何初始化关键参数 this 上存在差异。

        1. 方法调用

        当一个函数被保存为对象的一个属性时,我们称它为一个方法。当一个方法被调用时,this 被绑定到该对象。如果一个调用表达式包含一个属性存取表达式(即一个 . 点表达式或 [subscript] 下标表达式),那么它被当作一个方法来调用。

       

//创建myObject。它有一个value属性和一个increment(增加)方法。
//increment方法接受一个可选的参数。如果参数不是数字,那么默认使用数字1.

var myObject = {
  value: 0,
  increment: function (inc) {
    this.value += typeof inc === 'number' ? inc : 1;
  } 
};

myObject.increment();
document.writeln(myObject.value);       //1

myObject.increment(2);
document.writeln(myObject.value);       //3
方法可以使用 this 去访问对象,所以它能从对象中取值或修改对象。通过 this 可取得它们所属对象的上下文的方法称为公共方法。

        2. 函数调用

        如果在一个函数前面带上 new 来调用,那么将创建一个隐藏连接到该函数的 prototype 成员的新对象,同时 this 将会绑定到那个

        当一个函数并非一个对象的属性时,那么它被当作一个函数来调用。

        var sum = add ( 3, 4);

        当函数以此模式调用时,this 被绑定到全局对象,这是语言设计的错误,应该绑定到外部函数的 this 变量。这个设计错误的后果是方法不能利用内部函数来帮助工作。但有一个解决办法,如果该方法定义一个变量并给它赋值为 this,那么内部函数就可以通过那个变量访问到 this。

//给myObject添加一个double方法。

myObject.double = function () {
  var that = this;                  //解决方法

  var helper = function () {
    that.value = add(that.value, that.value);
  };

  helper();                         //以函数形式调用helper
}

//以方法形式调用double

myObject.double();
document.writeln(myObject.value);   //6

        3. 构造器调用模式

var Quo = function (string) {
  this.status = string;
};

Quo.prototype.get_status = function() {
  return this.status;
};

var myQuo = new Quo('confused');

document.writeln(myQuo.get_status());    //confused
        这种方法并不推荐。
       
        4. apply 调用

        apply 方法让我们构建一个参数数组并用其去调用函数。它也允许我们选择 this 的值。apply 方法接受两个参数,第一个是将绑定给 this 的值。第二个就是一个参数数组。

//构造一个包含两个数字的数组,并将它们相加。

var array = [3, 4];
var sum = add.apply(null, array);        //sum的值为7

//构造一个包含status成员的对象。

var statusObject = {
  status: 'A-OK'
};

var status = Quo.prototype.get_status.apply(statusObject);
                                 //status值为'A-OK'


        4.4 参数

        无须指定参数个数的情况

//注意该函数内部定义的变量sum不会与函数外部定义的sum产生冲突
//该函数只会看到内部的那个变量
var sum = function () {
  var sum = 0;
  for (var i = 0; i < arguments.length; i++) {
    sum += arguments[i];
  }
  return sum;
};

document.writeln(sum(4, 8, 15, 16, 23, 42));
        但这并不是一个特别有用的模式。

        arguements并不是一个真正的数组,它只是拥有 length 属性,但缺少所有数组的方法。


        4.5 返回

        如果函数以在前面加上 new 前缀的方式来调用,且返回值不是一个对象,则返回 this (该新对象)。


       4.6 异常

var add = function (a, b) {
  if (typeof a !== 'number' || typeof b !== 'number') {
    throw {
      name: 'TypeError',
      message: 'add needs numbers'
    };
  }
  return a + b;
};

//构造一个try_it函数,用不正确的方式调用之前的add函数
var try_it = function () {
  try {
    add('seven');
  }
  catch(e) {
    document.writeln(e.name + ': ' + e.message);
  }
};

try_it();


       4.7 给类型增加方法

       通过给 Function.prototype 增加方法来使得该方法对所有函数可用(要先确定没有该方法时才添加它)。

Function.prototype.method = function(name, func) {
  if (!this.prototype[name]) {
    this.prototype[name] = func;
  }
  return this;
};
       只提取数字中的整数
Number.method('integer', function () {
  return Math[this < 0 ? 'ceiling' : 'floor'](this);
});

document.writeln(-3.3.integer());          //-3
       移除字符末端空白的方法

String.method('trim', function () {
  return this.replace(/^\s+|\s+$/g, '');
});

document.writeln('"' + "   neat   ".trim() + '"');   //"neat"


       4.8 递归

//定义walk_the_DOM函数,它从某个给定的节点开始,按HTML源码中的顺序
//访问该树的每个节点。
//它会调用一个函数,并依次传递每个节点给它。walk_the_DOM调用自身去处理
//每一个节点。

var walk_the_DOM = function walk(node, func) {
  func(node);
  node = node.firstChild;
  while(node) {
    walk(node, func);
    node = node.nextSibling;
  }
};
      

//定义getElementsByAttribute函数,它取得一个属性名称字符串
//和一个可选 的匹配值
//它调用walk_the_DOM,传递一个用来查找节点属性名的函数。
//匹配的节点会累积到一个结果数组中。

var getElementsByAttribute = function (attr, value) {
  var results = [];

  walk_the_DOM(document.body, function (node) {
    var actual = node.nodeType === 1 && node.getAttribute(att);
    if (typeof actual === 'string' && 
          (actual === value || typeof value !== 'string') ) {
      results.push(node);
    }
  });
  return results;
};
       阶乘

var factorial = function factorial(i, a) {
  a = a || 1;
  if (i < 2) {
    return a;
  }
  return factorial(i - 1, a * i);
}

document.writeln(factorial(4));        //24

       4.9 闭包

var myObject = function () {
  var value = 0;

  return {
    increment: function (inc) {
      value += typeof inc === 'number' ? inc : 1;
    },
    getValue: function () {
      return value;
    }
  }
}();
       我们是把调用函数的结果赋值给 myObject,注意最后一行的()。该函数返回一个包含两个方法的对象,并且这些方法继续享有访问value变量的特权。

       下面的 quo 函数被设计成无须在前面加上 new 来使用,所以名字也没有首字母大写。

//创建一个quo的构造函数。
//它构造出带有get_status方法和status私有属性的一个对象。
var quo = function (status) {
  return {
    get_status: function () {
      return status;
    }
  };
};

//构造一个quo实例
var myQuo = quo('amazed');
document.writeln(myQuo.get_status());
       下面的例子更有用,注意要等 document 加载完成后再调用此函数。

// 定义一个函数,它设置一个DOM节点为黄色,然后把它渐变为白色
var fade = function (node) {
  var level = 1;
  var step = function () {
    var hex = level.toString(16);
    node.style.backgroundColor = '#FFFF' + hex + hex;
    if (level < 15) {
      level += 1;
      setTimeout(step, 100);
    }
  };
  setTimeout(step, 100);
};

fade(document.body);

       理解内部函数能访问外面函数的实际变量而无须复制:

//糟糕的例子
var add_the_handlers = function (nodes) {
  var i;
  for (i = 0; i < nodes.length; i++) {
    nodes[i].onclick = function (i) {
      alert(i);
    }(i);
  }
};
//结束糟糕的例子

//更好的例子
var add_the_handlers = function (nodes) {
  var i;
  for (i = 0; i < nodes.length; i++) {
    nodes[i].onclick = function (i) {
      return function () {
        alert(i);
      };
    }(i);
  }
};
       add_the_handler函数目的是给每个时间处理器一个唯一值(i)。糟糕的例子未能达到目的是因为事件处理器函数绑定了变量 i ,而不是函数在构造时变量 i 的值。

        更好的例子中,我们定义了一个函数并立即传递i进去执行,而不是把一个函数赋值给 onclick。那个函数将返回一个事件处理器函数。这个事件处理器函数绑定的是传递进去的 i 的值,而不是定义在add_the_handlers 函数里的 i 的值。那个被返回的函数被赋值给 onclick。


       4.11 回调

<span style="color:#000000;">//同步的请求,不好,会导致客户端进入假死状态。
request = prepare_the_request();
response = send_request_synchronously(request);
display(response);


//异步的情况,好,提供一个当服务器的响应到达时将被调用的回调函数。
//异步的函数立即返回,这样客户端不会阻塞。
request = prepare_the_request();
send_request_asynchronously(request, function (response) {
  display(response);
});</span>

       4.12 模块

String.method('deentityify', function () {

  //字符实体表,它映射字符实体的名字到对应的字符
  var entity = {
    quot: '"',
    lt: '<',
    gt: '>',
  };

  //返回deentityify方法。
  return function () {

    //这才是deentityify方法,它调用字符串的replace方法。
    //查找'&'开头和';'结束的子字符串,如果这些字符可以在字符实体表中找到。
    //那么就将该字符实体替换为映射表中的值,它用到了一个正则表达式
    return this.replace(/&([^&;]+);/g, 
      function (a, b) {
        var r = entity[b];
        return typeof r === 'string' ? r : a;
      }
    );
  };
}());

       再构造一个产生序列号的方法。

var serial_maker = function () {
  var prefix = '';
  var seq = 0;

  return {
    set_prefix: function (p) {
      prefix = String(p);
    },
    set_seq: function (s) {
      seq = s;
    },
    gensym: function () {
      var result = prefix + seq;
      return result;
    }
  };
};

var seqer = serial_maker();
seqer.set_prefix('Q');
seqer.set_seq(1000);
var unique = seqer.gensym();

document.writeln(unique);
        seqer包括的方法都没用到 this 或 that。因此没有办法损害 seqer。只能调用方法才能改变prefix 和 seq 的值。


       4.14 套用

Function.method('curry', function () {
  var slice = Array.prototype.slice;
  var args = slice.apply(arguments);
  var that = this;
  return function () {
    return that.apply(null, args.concat(slice.apply(arguments)));
  };
});


var add4 = add.curry(4);
document.writeln(add4(6));

       4.15 记忆

//调用次数太多
var fibonacci = function (n) {
  return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
};

//调用次数较少
var fibonacci = function () {
  var memo = [0, 1];
  var fib = function (n) {
    var result = memo[n];
    if (typeof result !== 'number') {
      result = fib(n - 1) + fib(n - 2);
      memo[n] = result;
    }
    return result;
  };
  return fib;
}();

for (var i = 0; i < 10; i++) {
  document.writeln('//' + i + ': ' + fibonacci(i) + '<br>');
}

       一般情况

var memoizer = function (memo, fundamental) {
  var shell = function (n) {
    var result = memo[n];
    if (typeof result !== 'number') {
      result = fundamental(shell, n);
      memo[n] = result;
    }
    return result;
  };
  return shell;
};


var fibonacci = memoizer([0, 1], function (shell, n) {
  return shell(n - 1) + shell(n - 2);
});

var factorial = memoizer([1, 1], function (shell, n) {
  return n * shell(n - 1);
})

for (var i = 0; i < 10; i++) {
  document.writeln('//' + i + ': ' + fibonacci(i) + '<br>');
}

for (var i = 0; i < 10; i++) {
  document.writeln('//' + i + ': ' + factorial(i) + '<br>');
}


      

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值