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

来源:互联网 发布:淘宝助理怎么导出宝贝 编辑:程序博客网 时间:2024/06/01 08:59

第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);       //1myObject.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}//以方法形式调用doublemyObject.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
原创粉丝点击