JavaScript语言精粹 笔记02

函数
函数对象
函数字面量
调用
参数
返回
异常
给类型增加方法
递归
作用域
闭包
回调
模块
级联
套用
记忆

 

函数

1 函数对象

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

因为函数是对象,所以它可以像任何其他的值一样被使用。函数可以存放在变量、对象和数组中。函数可以被当做变量传递给其他函数,函数亦可以再返回函数。函数可以拥有方法。

函数的与众不同之处在于它们可以被调用。

2 函数字面量

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

//创建一个名为add的变量,并用两个数字相加的函数赋值给它
var add = function(a,b){
    return a+b;
};

函数字面量可以出现在任何允许表达式出现的地方。函数也可以被定义在其他函数中。

3 调用

调用一个韩式时将暂停当前函数的运行,传递控制权和参数给新函数。除了声明时定义的形式参数,每个函数接收两个附加的参数:this和arguments。参数this在面型对象编程中非常重要,它的值取决于调用的模式。JS中一共有4种调用模式:方法调用模式、函数调用模式、构造器调用模式和apply调用模式。这些模式在如何初始化关键参数this上存在差异。

调用时,当实际参数个数与形式参数个数不匹配时不会导致运行时错误。如果实际参数值过多了,超出的参数值将被忽略。如果实际参数值过少,缺失的值将会替换为undefined。对参数值不会进行类型检查,任何类型的值都可以被传递给参数。

方法调用模式

当一个函数被保存为对象的一个属性时,我们称它为一个方法。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到对象的绑定发生在调用的时候。这个超级迟绑定使得函数可以对this高度复用。通过this可以取得它们所属上下文的方法称为公共方法。

函数调用模式

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

var sum = add(3,4);//sum的值为7

当函数以此模式调用时,this被绑定到全局对象。这是语言设计上的一个错误。倘若语言设计正确,当内部函数调用时,this应该仍然绑定到外部函数的this变量。这个设计的错误后果是方法不能利用内部函数来帮助它工作,因为内部函数的this被绑定了错误的值,所以不能共享该方法的访问权。幸运的是,有一个很容易的解决方案:如果该方法定义一个变量并给它赋值为this,那么内部函数就可以通过那个变量访问到this。

//给myObject增加一个double方法
myObject.double=function(){
    vat that=this;//解决方法
    var helper=function(){
        that.value=add(that.value,that.value);
    }
    helper();//以函数的形式调用helper。
}

//以方法的形式调用double。
myObject.double();
document.writeln(myObject.getValue());//6

构造器调用模式

JS是一门基于原型继承的语言。这意味着对象可以直接从其他对象继承属性。该语言是无类别的。这偏离了当今编程语言的主流。当今大多数语言都是基于类的语言。尽管原型继承有着强大的表现力,但它并不能被广泛理解。

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

//创建一个名为Quo的构造器函数,它构造一个带有status属性的对象。
var Quo = function(string){
    this.status=string;
}
//给Quo的所有实例提供一个名为get_status的公共方法。
Quo.prototype.get_status=function(){
    return this.status;
}

//构造一个Quo实例
var myQuo=new Quo("confused");
document.writeln(myQuo.get_status());//confused

目的就是结合new前缀调用的函数被称为构造器函数。按照约定,它们保存在以大写格式命名的变量里。如果调用构造器函数时没有在前面加上new,可能会方式非常糟糕的事情,即没有编译警告,也没有运行时警告,所以大写约定非常重要。不推荐使用这种形式的构造器函数。

Apply调用模式

JS是一门函数式的面向对象编程语言,所以函数可以拥有方法。

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

//构造一个包含两个数字的数组,并将它们相加。
var array = [3, 4];
var sum = add.apply(null, array); // sum 值为 7
// 构造一个包含status成员的对象。
var statusObject = {
status: ‘A-OK‘
};
// statusObject并没有继承自Quo.prototype,但我们可以在statusObject上调用get_status方法,尽管statusObject并没有get_status方法。
var status = Quo.prototype.get_status.apply(statusObject);
// status is ‘A-OK‘

4 参数

当函数调用时,会得到一个免费奉送的参数,那就是arguments数组。通过它函数可以访问所有它被调用时传递给它的参数列表,包括那些没有被分配给函数声明时定义的形式参数的多余参数。这使得编写一个无需指定参数个数的函数成为可能:

var sum = function ( ) {
var i, sum = 0;
for (i = 0; i < arguments.length; i += 1) {
sum += arguments[i];
}
    return sum;
};
document.writeln(sum(4, 8, 15, 16, 23, 42)); // 108

arguments并不是一个真正的数组。它是一个类似数组的对象。arguments拥有一个length属性,但它缺少所有数组的方法。

5 返回

一个函数总是会返回一个值。如果没有指定返回值,则返回undefined。

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

6 异常

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

throw语句中断函数的执行,它应该抛出一个exception对象,该对象包含name属性和message属性,你也可以添加其他属性。

该exception对象被传递到一个try语句的catch从句:

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

如果try代码块内抛出一个异常,控制器就会跳转到它的catch从句。

一个try语句只会有一个捕获所有异常的catch代码块。如果你的处理手段取决于异常的类型,那么异常处理必须检查异常对象的name属性以确定异常的类型。

7 给类型增加方法

JS允许给语言的基本类型添加方法。通过给Object.prototype添加方法来使得该法对所有对象可用。这样的方式对函数、数组、字符串、数字、正则表达式、布尔值同样适用。

举例来说,我们可以通过给Function.prototype增加方法使得该方法对说有函数可用:

Function.prototype.method = function (name, func) {
    this.prototype[name] = func;
    return this;
};

通过给Function.prototype增加一个method方法,我们就不必键入prototype这个属性名。

JS没有单独的整数类型,我们给Number.prototype添加一个integer方法。

Number.method(‘integer‘,function(){
    return Math[this<0?‘ceil‘:‘floor‘](this);
});
document.writeln((-10/3).integer());//-3

再举一个例子,给String添加一个trim方法:

String.method(‘trim‘, function ( ) {
    return this.replace(/^\s+|\s+$/g, ‘‘);
});document.writeln(‘"‘ + " neat ".trim( ) + ‘"‘)

基本类型的原型是公共的结构,所以在类库混用时务必要小心。一个保险的做法就是在确定没有该方法时才添加它。

Function.prototype.method = function (name, func) {
    if (!this.prototype[name]) {
        this.prototype[name] = func;
    }
};

8 递归

一些语言提供了尾递归优化。这意味着如果一个函数返回自身递归调用的结果,那么调用过程会被替换为一个循环,它可以显著提高速度。但是,JS并没有提供尾递归优化。深度递归的函数可能会因为返回堆栈溢出而运行失败。

9 作用域

尽管代码块的语法似乎表现出JS支持块级作用域,但实际上JS并不支持。

JS却实有函数作用域。在函数中定义的参数和变量在函数外部是不可见的,而且在一个函数中的任何位置定义的变量在该函数中的任何地方都是可见的。

很多现代语言都推荐尽可能迟地声明变量。而使用在JS上的话却会成为糟糕的建议,因为它缺少块级作用域。所以,最好的做法是在函数体的顶部声明函数中可能用到的所有变量。

10 闭包

闭包,指的是词法表示包括不被计算的变量的函数,也就是说,函数可以使用函数之外定义的变量。
简单的闭包实例
在 ECMAScript 中使用全局变量是一个简单的闭包实例。请思考下面这段代码:

var sMessage = "hello world";
function sayHelloWorld() {
  alert(sMessage);
}
sayHelloWorld();

在上面这段代码中,脚本被载入内存后,并没有为函数 sayHelloWorld() 计算变量 sMessage 的值。该函数捕获 sMessage 的值只是为了以后的使用,也就是说,解释程序知道在调用该函数时要检查 sMessage 的值。sMessage 将在函数调用 sayHelloWorld() 时(最后一行)被赋值,显示消息 "hello world"。
复杂的闭包实例
在一个函数中定义另一个会使闭包变得更加复杂。例如:

var iBaseNum = 10;

function addNum(iNum1, iNum2) {
  function doAdd() {
    return iNum1 + iNum2 + iBaseNum;
  }
  return doAdd();
}

这里,函数 addNum() 包括函数 doAdd() (闭包)。内部函数是一个闭包,因为它将获取外部函数的参数 iNum1 和 iNum2 以及全局变量iBaseNum 的值。 addNum() 的最后一步调用了 doAdd(),把两个参数和全局变量相加,并返回它们的和。
这里要掌握的重要概念是,doAdd() 函数根本不接受参数,它使用的值是从执行环境中获取的。
可以看到,闭包是 ECMAScript 中非常强大多用的一部分,可用于执行复杂的计算。
提示:就像使用任何高级函数一样,使用闭包要小心,因为它们可能会变得非常复杂。

以上关于闭包的内容摘自W3School

内部函数可以访问定义它们的函数外部的参数和变量(除了this和arguments)。

var myObject = function ( ) {
    var value = 0;
    return {
        increment: function (inc) {
            value += typeof inc === ‘number‘ ? inc : 1;
        },
        getValue: function ( ) {
            return value;
        }
    };
}( );

和对象字面量形式去初始化myObject不同,我们通过调用一个函数的形式去初始化myObject,该函数将返回一个对象字面量。此函数定义了一个value变量。该变量对increment和getValue方法总是可用的,但函数的作用域使得它对其他的程序来说是不可见的。

var quo = function (status) {
    return {
        get_status: function ( ) {
            return status;
        }
    };
};
//构造一个quo实例
var myQuo = quo("amazed");
document.writeln(myQuo.get_status( ));

当调用quo时,它返回博阿含get_status方法的一个新对象。该方法的一个引用保存在myQuo中。即使quo已经返回了,但get_status方法仍然享有访问quo对象的status属性的特权。get_status方法并不是访问该参数的一个拷贝,它访问的就是该参数本身。该参数可以访问它被创建时所处的上下文环境。这被称为闭包

为了避免下面的问题,理解内部函数能访问外部函数的实际变量而无须复制是很重要的:

//糟糕的例子
var nodes=document.getElementsByTagName("h3");

var i;
for (i = 0; i < nodes.length; i++) {
  nodes[i].onclick = function (i) {
              console.log(i);//想要输出nodes[i]的序号,但实际上输出的是nodes个数
        };
  };
}        

解决方案:

var nodes=document.getElementsByTagName("h3");

var i;
for (i = 0; i < nodes.length; i += 1) {
  nodes[i].onclick = function (e) {
      return function () {
          console.log(e);
        };
  }(i);//将i传入函数中
}

11 回调

网络上的同步请求将导致客户端进入假死状态。如果网络传输或服务器很慢,响应性的降低将是不可能接受的。所以要发起异步请求,提供一个当服务器响应到达时将被调用的回调函数。异步的函数立即返回,这样客户端就不会被阻塞。

12 模块

可以用函数和闭包来构造模块。模块是一个提供接口却隐藏状态与实现的函数或对象。通过使用函数去产生模块,我们几乎可以完全摒弃全局变量的使用。举例来说,给Stirng增加一个方法deentityify方法,它的任务是寻找字符串中的HTML字符实体并替换为它们对应的字符。

String.prototype.deentityify= function ( ) {
    // 字符实体表,它映射字符实体的名字到对应的字符
    var entity = {
        quot: ‘"‘,
        lt: ‘<‘,
        gt: ‘>‘
    };
    //返回deentityify方法
    return function ( ) {
        //这才是deentityify方法,它调用字符串的replace方法。
        return this.replace(/&([^&;]+);/g,
        function (a, b) {//a对应匹配/&([^&;]+);/的字符串,b对应匹配([^&;]+)的字符串
            var r = entity[b];
            return typeof r === ‘string‘ ? r : a;
            }
        );
    };
}( );

console.log(‘&lt;&quot;&gt;‘.deentityify( ));//输出:<">

模块模式利用了函数作用域和闭包来创建绑定对象与私有成员的关联。这个例子中,只有deentityify方法有权访问字符实体表这个数据对象。

模块模式的一般形式是:一个定义了私有变量和函数的函数;利用闭包创建可以访问私有变量和函数的特权函数;最后返回这个特权函数,或者把它们保存到一个可以访问到的地方。使用模块模式可以摒弃全局变量的使用,它促进了信息隐藏和其他优秀的设计实践。

13 级联

有一些方法没有返回值。如果我们让这些方法返回this而不是undefined,就可以启动级联。在一个级联中,我们可以在单独一条语句中依次调用同一个对象的多个方法。这在jQuery中很常见。

14 套用

套用允许我们将函数与传递给它的参数结合去产生一个新的函数。不知道有什么用。

15 记忆

函数可以用对象去记住前面操作的结果,从而避免无谓的运算。这种优化被称为记忆。

优化后的Fibonacci数列函数:

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;
}( );

减少了不必要的运行。

参考:《JavaScript语言精粹》Douglas Crockford著    赵泽欣 鄢学鹍 译

转载请注明出处:

作者:JesseLZJ
出处:http://jesselzj.cnblogs.com

时间: 09-04

JavaScript语言精粹 笔记02的相关文章

javascript语言精粹----笔记【转载】

javascript语言精粹----笔记 1.6种值会为假(==false),分别是false,null,undefined,' ',0,NaN 2.typeof有6种值,分别是'number','string','boolean','undefined','function','object';其中typeof(null),结果是'object' 3.number类型总是64位浮点数,两个整数相除也可能出现非整数结果 4.如果第一个运算数的值为假,那么运算符&&产生它的第一个运算数的值.

JavaScript语言精粹笔记

JavaScript语言精粹笔记 掌握语言的每个特性可以让你出风头,但是并不推荐,因为一部分的特性带来的麻烦可能远超本身的价值.正如书中所言,坏的材料并不能雕刻出好的作品,要成为一名更好的程序员,要取其精华去其糟粕.当你知道要做什么的时候,它还能表现的更好,编程时一件相对困难的事情,绝不应该在懵懂的状态下开始编程之旅. JS中的注意为//或者/*content*/,注释一定要精确地描述代码,不然没有用的注释比没有注释更糟糕. JS中的代码块不会创建新的作用域,因此变量应该被定义在函数的头部,而不

javascript 语言精粹 笔记推荐。。

http://www.cnblogs.com/Cohlint/archive/2012/11/26/2788790.html 这篇javascript 语言精粹 学习笔记不错..不过看上去就是读书笔记,没有深入研究某个点..比如闭包..我还是不懂噢.. 比如17条:“add_the_handles 函数目的是给每个时间处理器一个唯一值(i).它未能达到目的是因为事件处理器函数绑定了变量i,而不是函数在构造时的变量i的值.” 这是原书的翻译过来的理解,但是感觉还是很艰涩,不太理解. 闭包这个变量作

JavaScript语言精粹 笔记01

内容比较简单,只是从头梳理一下JS的知识 语法空白标识符数字字符串语句 对象对象字面量检索更新引用原型反射枚举删除减少全局变量污染  语法 1 空白 空白可能表现为格式化字符或注释的形式.空白通常没有意义,但是偶尔必须用它来分割字符序列,否则它们就会被合并成一个单一的符号.例如: var that = this; var 和that之间的空格是不能去掉的,其他的空格都可以被移除. JS提供两种注释: /* */ // 建议使用//,因为/* */中要注释的内容可能包括字符 */ 而报错,例如:

JavaScript语言精粹 笔记03

继承伪类对象说明符原型函数化部件 继承 JS不是基于类的,而是基于原型的,这意味着对象直接从其他对象继承. 1 伪类 JS提供了一套丰富的代码重用模式,它可以模拟那些基于类的模式,因为JS实际上没有类,所以把模拟的类称为伪类.我们可以定义一个构造器并扩充它的原型: var Mammal = function (name) { this.name = name; }; Mammal.prototype.get_name = function ( ) { return this.name; }; M

JavaScript语言精粹 笔记06 方法

JS包含了少量可用在标准类型上的标准方法. ArrayFunctionNumberObjectRegExpString Array array.concat(item...) concat方法返回一个新数组,它包含array的浅复制并将1个或多个参数item附加在其后.如果参数item是一个数组,那么他的每个元素会被本别添加. var a = ['a', 'b', 'c']; var b = ['x', 'y', 'z']; var c = a.concat(b, true); // c 是 [

JavaScript语言精粹 笔记05 正则表达式

正则表达式 正则表达式以方法的形式被用于对字符串中的信息进行查找.替换画图提取操作.可处理正则表达式的方法有:regexp.exec, regexp.test,string.match, string.replace, string.search, 和string.split. 结构 有两个方法来创建一个RegExp对象.优先方法是使用正则表达式字面量.正则表达式被包围在一对斜杠中.有3个标志能在RegExp中设置,分别为g.i.m. // 构造一个匹配JavaScript字符串的正则表达式对象

JavaScript语言精粹 笔记04 数组

数组1 数组字面量2 长度3 删除4 列举5 混淆的地方6 方法7 维度 数组1 数组字面量 var empty = []; var numbers = [ 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine' ]; empty[1] // undefined numbers[1] // 'one' empty.length // 0 numbers.length // 10 数组字面量可以出

JavaScript语言精粹读书笔记- JavaScript对象

JavaScript 对象 除了数字.字符串.布尔值.null.undefined(都不可变)这5种简单类型,其他都是对象. JavaScript中的对象是可变的键控集合(keyed collections). 对象是属性的容器,其中每个属性都拥有名字和值. JavaScript中的对象是无类别的(class-free)的.它对新属性的名字和值没有约束. JavaScript包括一个原型链特性,允许对象继承另一对象的属性. 对象的检索: stooge[“first-name”]或者stooge.