《JavaScript 语言精粹》 学习笔记 —— 第四章 函数

时间:2020-11-30 21:24:41

《JavaScript 语言精粹》 学习笔记 —— 第四章 函数

第四章 函数

4.1、在JavaScript中,函数就是对象。
4.2、函数的对象连接到Function.prototype(该原型对象本身被连接到Object.prototype)。
4.3、函数在创建的时候附有两个附加的隐藏属性:函数上下文和实现函数行为的代码。
4.4、函数与其它对象的不同之处在于它可以被调用。
4.5、函数对象可以通过函数字面量来创建:
//  创建一个名为 add 的变量,并用来把两个数字相机的函数赋值给它。
var add = function (a, b) {
return a + b;
};
  函数字面量包括四个部分。
  第一部分是保留字function。
  第二部分是函数名,它可以被省略——即“匿名函数”。函数名可以用来递归地调用自己。
  第三部分是包围在圆括号中的一组参数。其中每个参数用逗号分隔。
  第四部分是包围在花括号中的一组语句。这些语句是函数的主体。它们在函数被调用是执行。
4.6、函数字面量可以出现在任何表达式允许出现的地方,函数也可以被定义在其它函数中。一个内部函数自然可以访问自己的参数和变量,同时它也能方便地访问它被嵌套在其中的那个函数的参数和变量。通过函数字面量创建的函数对象包含一个连接到外部上下文的连接。这被称为 “闭包”。它是JavaScript强大表现力的根基。
4.7、调用一个函数将暂停当前函数的执行,传递控制权和参数给新函数。
4.8、除声明时定义的形参,每个函数还接收两个附加的参数:this和arguments。
4.9、参数this的值取决于调用的模式。 在JavaScript中一共有四种调用模式:方法调用模式、函数调用模式、构造器调用模式和apply调用模式
4.10、当实际参数的个数和形式参数的个数不匹配时不会导致运行时错误。如果实际参数值过多,超出的参数值将被忽略。如果实际的参数值过少,缺失的值将会被替换为undefined。
4.11、对参数值不会进行类型检查,任何类型的值都可以被传递给参数。
4.12、方法调用模式:当一个函数被保存为对象的一个属性时,称它为一个“方法”。当一个方法被调用时,this被绑定到该对象。
// 创建 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
演示Demo
4.13、函数调用模式:当一个函数并非一个对象的属性时,那么它被当作一个函数来调用:
var sum = add(3, 4); // sum 的值为 7
  当函数以此模式被调用时,this被绑定到全局对象。这是语言设计上的一个错误。倘若正确,this应该绑定到外部函数的this变量。这个设计错的后果是方法不能利用内部函数来帮助它工作,因为内部函数被绑定了错误的值,所以不能共享该方法对对象的访问权。解决方案是该方法定义一个变量并给它赋值为this。
4.14、构造器调用模式:如果在一个函数前面带上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()); // 令人困惑
演示Demo
注: 作者不推荐这种写法,原因在下章阐述。
4.15、Apply 调用模式:因为 JavaScript 是一门函数式的面向对象编程语言,所以函数可以拥有方法。
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 值为 'A-OK'。
演示Demo
4.16、函数可以通过 arguments 数组访问所有它被调用时传递给它的参数列表。因为语言的一个设计错误,arguments 并不是一个真正的数组,它只是一个类似数组的对象。arguments 拥有一个 length 属性,但它缺少所有数组的方法。
4.17、一个函数总有一个返回值,如果没有指定则返回 undefined。
4.18、如果函数在前面加上 new 前缀来调用,且返回值不是一个对象,则返回 this (该新对象)。
4.19、throw 语句中断函数的执行。它抛出一个 exception 对象,该对象包含可识别异常类型的 name 属性和一个描述性的 message 属性。也可以添加其它属性。
4.20、递归函数是直接或间接地调用自身的函数。经典递归函数例子——汉诺塔(更多可见wiki:http://zh.wikipedia.org/wiki/%E6%B1%89%E8%AF%BA%E5%A1%94)。
var hanoi = function (disc, src, aux, dst) {
if (disc > 0) {
hanoi(disc - 1, src, dst, aux);
document.writeln('Move disc ' + disc + ' from ' + src + ' to ' + dst);
hanoi(disc ? 1, aux, src, dst);
}
};


hanoi(3, 'Src', 'Aux', 'Dst');
 圆盘数量为3时返回这样的解法
Move disc 1 from Src to Dst
Move disc 2 from Src to Aux
Move disc 1 from Dst to Aux
Move disc 3 from Src to Dst
Move disc 1 from Aux to Src
Move disc 2 from Aux to Dst
Move disc 1 from Src to Dst
演示Demo
4.21、尾递归是一种在函数的最后执行递归调用语句的特殊形式的递归。
4.22、尾递归可被替换为一个循环,但JavaScript没有对尾递归作出优化。
4.23、作用域控制着变量与参数的可见性及生命周期。作用:1、减少变量名称冲突;2、提供自动内存管理。
4.24、JavaScript不支持代码块的块级作用域。
4.25、JavaScript有函数作用域。定义在函数中的参数和变量在函数外部是不可见的,在函数中的任何位置定义的变量在该函数中的任何地方都是可见的。
4.26、JavaScript因缺少块级作用域,因此建议在函数体的顶部声明函数中可能用到的所有变量。
4.27、作用域的好处是内部函数可以访问定义它们的外部函数的参数和变量(除了 this 和 arguments)。
4.28、闭包例子一:一个有趣的情形是内部函数拥有比它外部函数更长的生命周期
var myObject = function () {
var value = 0; // 私有变量,对 increment 和 getValue 可见
return {
increment : function (inc) {
value += typeof inc === 'number' ? inc : 1;
},
getValue : function () {
return value;
}
};
}();

document.writeln(myObject.value);// undefined
document.writeln(myObject.getValue());// 0
myObject.increment(2);
document.writeln(myObject.getValue());// 2
myObject.increment(4);
document.writeln(myObject.getValue());// 6
演示Demo
仔细一看,并没有把一个函数赋值给myObject。而是把嗲用该函数后返回的结果赋值给它。该函数返回了包括两个方法的对象,并且这些方法继续享有访问value变量的特权。
4.29、闭包例子二:创建一个名为 quo 的构造函数
// 它构造出带有 get_status 方法和 status 私有属性的一个对象
var quo = function(status){
  return {
    get_status:function(){
      return status;
    }
  };
};
var myQuo = quo("amazed");
document.writeln(myQuo.get_status()); //amazed
演示Demo
这个 quo 函数被设计成无须在前面加上 new 来使用,所以名字首字母也没有大写。当我们调用 quo 时,它返回包含 get_status 方法的一个新对象。该对象的一个引用保存在 myQuo 中。即使 quo 已经返回了,但 get_status 方法仍然享有访问 quo 对象的 status 属性的特权。get_status 方法并不是访问该参数的一个拷贝;它访问的就是该参数本身。这是可能的,因为该函数可以访问它被创建时所处的上下文环境。这被称为 闭包
4.30、闭包例子三:一个更有用的例子
// Define a function that sets a DOM node's color
// to yellow and then fades it to white.
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);
演示Demo
setTimout 存在会让fade函数中的level变量再次被赋值,fade函数在之前已经返回了,但只要fade内部函数需要,它的变量就会持续保留。
4.31、内部函数访问外部函数的变量是不需要复制的。
4.32、“模块”是一个提供接口却隐藏状态与实现的函数或对象。可以使用函数和闭包来构造模块。
4.33、模块模式利用了函数作用域和闭包来创建绑定对象与私有成员的关联。
4.34、模块模式的一般形式是:最后返回这个特权函数,或者把它们保存到一个可以访问到的地方。
4.35、使用模块模式可以摒弃全局变量的使用。它促进了信息隐藏和其它优秀的设计实践。对于应用程序的封装,或者构造其它单例对象,模块模式非常有效。
4.36、使用模块模式的具有单例的特性产生一个安全的对象 serial_maker。
var serial_maker = function (  ) {

// 返回一个用来产生唯一字符串的对象
// 唯一字符串由两部分组成:前缀 + 序列号
// 该对象包含一个设置前缀的方法,一个设置序列号的方法和一个产生唯一字符串的 gensym 方法

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;
seq += 1;
return result;
}
};
};
var seqer = serial_maker( );
seqer.set_prefix('Q');
seqer.set_seq(1000);
var unique = seqer.gensym(); // unique is "Q1000"
document.writeln(unique);
document.writeln(seqer.gensym());
document.writeln(seqer.gensym());
document.writeln(seqer.gensym());
演示Demo
4.37、让一些没有返回值的方法返回 this 而不是 undefined,就启用了级联。


总结:
  这一章最抽象且难以理解的就是 “闭包”。我的理解是,“闭包”是一个函数(假设该函数名为Fa),函数Fa内部有一个子函数(假设该函数名为Fb),子函数Fb能读取其外部父函数Fa的私有变量,它使得内部函数Fb与外部函数Fa之间有一种通信机制,但这一切都在Fa内部有效,离开Fa通信中断,它因与外部不能联系而封闭,所以可以称为“closure”,翻译成中文就成了“闭包”,包可以被理解成其因“闭”而独立。
  这篇文章比较通俗易懂地解释了什么是闭包—— 《学习Javascript闭包(Closure)》

Refer:
英文参考文档: 《JavaScript: The Good Parts》
提供学习帮助的网站:
1、 《Javascript的10个设计缺陷》
2、 《学习Javascript闭包(Closure)》

3、汉诺塔演示


转载请注明出处:http://blog.csdn.net/xxd851116/article/details/7669803