「JavaScript 语言精粹」读书笔记--函数

函数对象

JavaScript中函数就是对象. 函数对象连接到Function.prototype.

当把一个函数当作构造函数(使用new关键字)使用时, 新创建的对象的原型就是该函数的prototype对象. 我们可以通过给prototype设置属性而达到让该类对象拥有同样的公共属性的目的.

新创建的对象有一个__proto__属性, 指向该函数的prototype对象.

函数字面量

函数对象通过函数字面量来创建. 函数字面量可以出现在任何允许表达式出现的地方, 甚至可以被定义在函数内部.

内部函数处理可以访问自己的参数和变量, 他也能自由访问把它嵌套在其中的父函数的参数与变量. 通过函数字面量创建的函数对象包含一个连接到外部上下文的连接, 叫做闭包.

1
2
3
4
5
6
7
var add = function (a, b) {
return a + b;
}
//or
function add (a, b) {
return a + b;
}

调用

每个函数除了声明的变量外, 还会接收两个附加的参数thisarguments.

调用运算符是跟在任何产生一个函数值的表达式之后的一对圆括号. 这就解释了立即执行函数的写法. 圆括号内包含参数. 参数个数不会导致运行时错误, 多余参数会被忽略, 参数缺失会被替换为undefined.

方法调用模式

当一个函数被保存为一个对象的属性时, 称之为方法.

这种模式下this参数被绑定到该对象.

函数调用模式

当一个函数不是一个对象的属性时, 他就是被当作函数来调用的.

这种模式下this被绑定到全局对象. 作者认为这是一个语言设计上的错误, 应该将其绑定到外部函数的this变量. 我们可以这样做:

1
2
3
4
5
6
var foo = function () {
var that = this;
var helper = function () {
//do something with var that
}
}

构造函数调用模式

使用new来调用一个函数时, 就会创建一个连接到该函数的prototype成员的新对象, 同时, this被绑定到这个新对象上.

1
2
3
4
5
var Car = function () {
this.wheels = 4;
}
var ford = new Car();
ford.__proto__ === Car.prototype //true

Apply调用模式

apply方法让我们构建一个参数数组传递给调用函数. 同时, 也可以指定this的值.

该方法有两个参数:

  • 第一个: 绑定给this的值
  • 第二个: 参数数组

参数

前面提到过arguments参数, 函数可以通过访问此参数以访问所有参数, 包括多余参数. 然而, 因为语言的设计错误, 该参数只是一个”类似数组”的对象, 它拥有一个length属性, 但没有任何数组的方法.

返回

使用return关键字返回, 如果没有指定, 则返回undefined. 构造函数调用时, 如果返回值不是一个对象, 则返回this.

异常

抛出:

1
2
3
4
5
6
7
8
9
var add = function (a, b) {
if (typeof a !== 'number' || typeof b !== 'number') {
throw {
name: 'TypeError',
message: 'add needs numbers'
};
}
return a + b;
}

捕获:

1
2
3
4
5
6
7
var try_it = function() {
try {
add("seven");
} catch (e) {
document.writeln(e.name + "; " + e.message);
}
}

一个try语句只会有一个捕获异常的代码块, 如果有多种异常的情况, 只有通过name属性判断.

闭包

通过函数字面量创建的函数对象包含一个连接到外部上下文的连接, 叫做闭包.

因为JavaScript是一个函数式语言, 所以支持返回一个函数. 这样将会导致内部函数比它的外部函数拥有更长的生命周期. 这一特性也让创建私有变量成为可能.

1
2
3
4
5
6
7
8
9
10
11
var myObj = (function () {
var value = 1;
return {
setValue: function (inc) {
value = typeof inc === 'number' ? inc : value;
},
getValue: function () {
return value;
}
};
}());

再来看一个糟糕的例子及其改进:

1
2
3
4
5
6
7
8
var add_the_handlers = function (nodes) {
var i;
for (i = 0; i < nodes.length; i++) {
nodes[i].onclick = function (e) {
alert(i);
};
}
};

这个函数的目的是点击一个节点时, 弹出对话框显示节点的序号. 而这个函数的效果却是每次显示节点的数目.

原因在于, 创建onclick函数时, 函数引用的变量i属于add_the_handlers方法, 而i一直在改变, 直到变为nodes.length. 所以, 当所有的onclick方法创建完成后, 引用的i实际上是一个变量, 值为nodes.length.

1
2
3
4
5
6
7
8
9
10
11
var add_the_handlers = function (nodes) {
var helper = function (i) {
return function (e) {
alert(i);
};
};
var i;
for (i = 0; i < nodes.length; i++) {
nodes[i].onclick = helper(i);
}
}

这个改进的方法就能达到目的, 原因在于, 返回给onclick的函数是helper的内部函数, 其引用的ihelper函数的i, 覆盖了外部的add_the_handlersi. 所以, 当循环进行时, helper中的i因为是形参, 而不会收到add_the_handlers中的i的影响.

级联

其实这是一个技巧.

多数的setter方法往往不需要返回任何内容, 这时在JavaScript中, 函数将会返回undefined. 如果我们需要对一个对象设置很多值, 不得不写成:

1
2
3
4
obj.setName(name);
obj.setAge(age);
obj.setSex(sex);
...

如果我们让这样的函数返回this, 就可以启动级联, 情况就大不一样.

1
obj.setName(name).setAge(age).setSex(sex)...