JavaScript The Good Parts

使用一个method 方法定义新方法
1
2
3
4
5
6
Function.prototype.method = function (name, func) {
if (!this.prototype[name]) {
this.prototype[name] = func;
}
return this;
}

根据数字的正负来判断是使用Math.ceiling 还是Math.floor
1
2
3
Number.method("integer", function () {
return Math[this < 0 ? 'ceil' : 'floor'](this);
});

移除字符串首尾的空白
1
2
3
String.method("trim", function () {
return this.replace(/^\s+|\s+$/g, '');
})

闭包演示
1
2
3
4
5
6
7
8
9
10
11
12
var myObject = (function () {
var value = 0;

return {
increment: function (inc) {
value += typeof inc === "number" ? inc : 1;
},
getValue: function () {
return value;
}
};
}());

缓存中间计算结果 - 斐波纳挈数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 定义
var memoizer = function (memo, formula) {
var recur = function (n) {
var result = memo[n];
if (typeof result !== 'number') {
result = formula(recur, n);
memo[n] = result;
}
return result;
};
return recur;
};

// 使用
var fibonacci = memoizer([0, 1], function (recur, n) {
return recur(n-1) + recur(n - 2);
});
for (var i = 0; i <= 10; i++) {
console.log("%d : %d", i, fibonacci(i));
}

// 扩展用法 阶乘
var factorial = memoizer([1, 1], function (recur, n) {
return n * recur (n - 1)
});
for (var i = 1; i <= 10; i++) {
console.log("%d : %d", i, factorial(i));
}

如何实现继承 - 函数化
  1. 创建一个对象
  2. 有选择地定义私有实例变量和方法。这些就是函数中通过var语句定义的普通变量
  3. 给这个新对象扩充方法。这些方法拥有特权去访问参数,以及上一步中定义的变量。
  4. 返回这个新对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// 这里是一个函数化构造器的伪代码模板(加粗的文本表示强调):
var **constructor** = function (spec, my) {
// 代码快内部不知道怎么加粗,哪位大神指点一下
var that, **其他私有变量**;
my = my || {};

// **把共享的变量和函数添加到my中**

that = {
name: "**一个新对象**"
};

// **给that添加特权方法**

return that;
}
// spec 对象包含构造器需要构造一个新实例的所哟信息。spec 的内容可能会被复制到私有变量中,或者被其他函数改变,或者方法可以在需要的时候访问spec的信息。(一个简化的方式是替换spec为一个单一的值。当构造对象过程中并不需要整个spec对象的时候,这是有用的。)
// my 对象是一个为继承链中的构造器提供秘密共享的容器。my对象可以选择性地使用。如果没有传入一个my对象,那么会创建一个my对象

// demo

var mammal = function (spec) {
var that = {};

that.get_name = function () {
return spec.name;
};

that.says = function () {
return spec.saying || "";
}

return that;
};

var cat = function (spec) {
spec.saying = spec.saying || 'meow';
var that = mammal(spec);
that.purr = function (n) {
var i, s = '';
for (i = 0; i < n; i += 1) {
if (s) {
s += "-";
}
s += "r";
}
return s;
};
that.get_name = function () {
return that.says() + " " + spec.name + " " + that.says();
};
return that;
};

var myCat = cat({name: "Henrietta"});
函数化模式还提供了一个处理父类方法的方法。我们会构造一个superior方法,它取得一个方法名并返回调用那个方法的函数。该函数会调用原来的方法,尽管属性已经变化了。
1
2
3
4
5
6
7
Object.method('superior', function (name) {
var that = this,
method = that[name];
return function () {
return method.apply(that, arguments);
};
});

让我们在coolcat上试验一下,coolcat就像cat一样,除了它有一个更酷的调用父类方法的get_name方法。它只需要一点点的准备工作。我们会声明一个super_get_name变量,并且把调用superior方法所返回的结果赋值给它。

1
2
3
4
5
6
7
8
9
10
11
12
var coolcat = function (spec) {
var that = cat(spec),
super_get_name = that.superior('get_name');
that.get_name = function (n) {
return 'like ' + super_get_name() + ' baby';
};
return that;
};

var myCoolCat = coolcat({name: 'Bix'});
var name = myCoolCat.get_name();
console.log(name); // "like meow Bix meow baby"

部件

我们可以从一套部件中把对象组装出来。例如,我们可以构造一个给任何对象添加简单事件处理特性的函数。他会给对象添加一个on方法、一个fire方法和一个私有的事件注册表对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
var eventuality = function (that) {
var registry = {};
that.fire = function (event) {
// 在一个对象上触发一个事件。该事件可以是一个包含事件名称的字符串
// 或者是一个拥有包含事件名称的 type 属性的对象。
// 通过'on'方法注册的事件处理程序中匹配事件名称的函数将被调用。
var array,
func,
handler,
i,
type = typeof event === 'string' ? event : event.type;
if (registry.hasOwnProperty(type)) {
array = registry[type];
for (i = 0; i < array.length; i += 1) {
handler = array[i];
// 每个处理程序包含一个方法和一组可选的参数。
// 如果该方法是一个字符串形式的名字,那么寻找到该函数。
func = handler.method;
if (typeof func === 'string') {
func = this[func];
}
// 调用一个处理程序。如果该条目包含参数,那么传递它们过去。否则,传递该事件对象。
func.apply(this, handler.parameters || [event]);
}
}
return this;
};

that.on = function (type, method, parameters) {
// 注册一个事件。构造一条处理程序条目。将它插入到处理程序数组中,
// 如果这种类型的事件还不存在,就构造一个。
var handler = {
method: method,
parameters: parameters
};
if (registry.hasOwnProperty(type)) {
registry[type].push(handler);
} else {
registry[type] = [handler];
}
return this;
};

return that;
};

我们可以在任何单独的对象上调用eventuality, 授予它事件处理方法。我们也可以赶在that被返回之前在一个构造器函数中调用它 eventuality(that);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
eventuality(myCoolCat);
myCoolCat.health = 100;
myCoolCat.on('dead', function (event) {
var date = event.date;
var killer = event.killer;
console.log("%s killed me at %s", killer, date);
});
myCoolCat.on('hurt', function () {
this.health -= 50;
if (this.health <= 0) {
var event = {
type: 'dead',
killer: 'stone from sky',
date: new Date()
};
this.fire(event);
} else {
console.log('nothing, who is the strong guy.');
}
});

myCoolCat.fire("hurt"); //nothing, who is the strong guy.
myCoolCat.fire("hurt"); //stone from sky killed me at Sat Mar 14 2015 15:47:29 GMT+0800 (CST)

数组与对象

javascript 编程中,一个常见的错误是在必须使用数组时使用了对象,或者在必须使用对象时使用了数组。其实规则很简单:当属性名是小而连续的整数时,你应该使用数组。否则,使用对象。
javascript 本身没有一个好的机制来区分数组和对象。我们可以通过定义自己的is_array函数来弥补这个缺陷:

1
2
3
4
5
6
7
8
9
10
var is_array = function (value) {
return value
&& typeof value === 'object'
&& value.constructor === Array;
};
// 不过该方法在识别从不同的窗口(window)或者frame里构造的数组时会失败。下面是一个更好的方式
// 上面这句话这里本人尚未理解,请高手指点
var is_array = function (value) {
return Object.prototype.toString.apply(value) === '[object Array]';
};
为数组扩充reduce函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Array.method('reduce', function (f, value) {
var i;
for (i = 0; i < this.length; i += 1) {
value = f(this[i], value);
}
return value;
});
// DEMO
var data = [4, 8, 15, 16, 23, 42];
var add = function (a, b) {
return a + b;
};
var mult = function (a, b) {
return a * b;
};
var sum = data.reduce(add, 0);
console.log(sum);// 108
var product = data.reduce(mult, 1);
console.log(product); // 7418880

// 因为数组其实就是对象,所以我们可以直接给一个单独的数组添加方法
data.total = function () {
return this.reduce(add, 0);
};
var total = data.total();
console.log(total); // 108
为数组指定初始值
1
2
3
4
5
6
7
8
9
Array.dim = function (dimension, initial) {
var a = [], i;
for (i = 0; i < dimension; i += 1) {
a[i] = initial;
}
return a;
};
var myArray = Array.dim(10, 0); //[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
// 注意这里不能使用我们之前说的method方法,应为那个方法是给某个类型的每一个具体的对象增加方法的,而这里我们是给Array这个 类型 增加方法,类似于某些静态语言中的静态方法或者类方法
数组排序

sort 方法会array中的内容进行排序。但是它不能正确第给一组数字排序:

1
2
var myNumbers = [4, 8, 15, 16, 23, 42];
myNumbers.sort(); // [15, 16, 23, 4, 42, 8]

javascript 的默认比较函数把要被排序的元素都视为字符串。它尚未足够智能到在比较这些元素之前先检测他们的类型,所以当它比较这些数字的时候,会把它们转化为字符串,于是得到一个错的离谱的结果。
辛运的是,你可以使用自己的比较函数来替换默认的比较函数。

你的比较函数应该接受两个参数,并且如果这两个参数相等则返回0,如果第1个参数应该排列在前面,则返回一个负数,如果第2个参数应该排在前面,则返回一个正数。

1
2
3
myNumbers.sort(function (a, b) {
return a - b;
}); // [4, 8, 15, 16, 23, 42]

上面这个函数可以使数字正确排序,但它不能使字符串排序。如果我们想要给任何包含简单值的数组排序,必须要做更多的工作:

1
2
3
4
5
6
7
8
9
10
var myStrings = ['aa', 'bb', 'a', 4, 8, 15, 16, 23, 42];
myStrings.sort(function (a, b) {
if (a === b) {
return 0;
}
if (typeof a === typeof b) {
return a < b ? -1 : 1;
}
return typeof a < typeof b ? -1 : 1;
}); //[4, 8, 15, 16, 23, 42, "a", "aa", "bb"]
对象数组排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// by 函数接受一个成员名字字符串做为参数,
// 并返回一个可以用来对包含该成员的对象数组的进行排序的比较函数
var by = function (name) {
return function (o, p) {
var a, b;
if (typeof o === 'object' && typeof p === 'object' && o && p) {
a = o[name];
b = p[name];
if (a === b) {
return 0;
}
if (typeof a === typeof b) {
return a < b ? -1 : 1;
}
return typeof a < typeof b ? -1 : 1;
} else {
throw {
name: 'Error',
message: 'Expected an object when sorting by ' + name
};
}

};
}
var myPeople = [
{first: 'Joe', last: 'Beasser'},
{first: 'Moe', last: 'Howard'},
{first: 'Joe', last: 'DeRita'},
{first: 'Shemp', last: 'Howard'},
{first: 'Larry', last: 'Fine'},
{first: 'Curly', last: 'Howard'}
];
myPeople.sort(by('first'));
/*
[
{first: "Curly", last: "Howard"},
{first: "Joe", last: "Beasser"},
{first: "Joe", last: "DeRita"},
{first: "Larry", last: "Fine"},
{first: "Moe", last: "Howard"},
{tfirst: "Shemp", last: "Howard"}
]
*/

sort 方法是不稳定的

排序的稳定性是指排序后数组中的相等值的相对位置没有发生改变,而不稳定性排序则会改变相等值的相对位置。详细内容参见排序算法。JavaScript的sort方法的稳定性根据不同浏览器的实现而不一致。可参见Array.prototype.sort中的介绍。

下面的调用:

1
2
3
4
5
6
7
8
9
10
myPeople.sort(by('last')).sort(by('first'));
/*
[
{"first": "Curly", "last": "Howard"},
{"first": "Joe", "last": "Beasser"},
{"first": "Joe", "last": "DeRita"},
{"first": "Larry", "last": "Fine"},
{"first": "Moe", "last": "Howard"},
{"first": "Shemp", "last": "Howard"}]
*/

不能保证产生正确的序列。如果你想基于多个键值进行排序,你需要再次做更多的工作。我们可以修改by函数,让其可以接受第2个参数,当主要的键值产生一个匹配的时候,另一个compare方法将被调用以决出高下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// by 函数接受一个成员名字字符串和一个可选的次要比较函数做为参数,
// 并返回一个可以用来对包含该成员的对象数组进行排序的比较函数。
// 当 o[name] 和 p[name] 相等时,次要比较函数被用来决出高下。
var by = function (name, minor) {
return function (o, p) {
var a, b;
if (o && p && typeof o === 'object' && typeof p === 'object') {
a = o[name];
b = p[name];
if (a === b) {
return typeof minor === 'function' ? minor(o, p) : 0;
}
if (typeof a === typeof b) {
return a < b ? -1 : 1;
}
return typeof a < typeof b ? -1 : 1;
} else {
throw {
name: 'Error',
message: 'Expected an object when sorting by ' + name
};
}
};
}
myPeople.sort(by('last', by('first')));
[
{"first": "Joe", "last": "Beasser"},
{"first": "Joe", "last": "DeRita"},
{"first": "Larry", "last": "Fine"},
{"first": "Curly", "last": "Howard"},
{"first": "Moe", "last": "Howard"},
{"first": "Shemp", "last": "Howard"}
]


正则表达式 - 解析URL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var parse_url = /^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/;
var url = "http://www.ora.com:80/goodparts?q#fragment";
var result = parse_url.exec(url);
var names = ['url', 'scheme', 'slash', 'host', 'port', 'path', 'query', 'hash'];
var blanks = ' ';
var i;
for (i = 0; i < names.length; i += 1) {
console.log("%s:%s%s", names[i], blanks.substring(names[i].length), result[i]);
}
/*
url: http://www.ora.com:80/goodparts?q#fragment
scheme:http
slash: //
host: www.ora.com
port: 80
path: goodparts
query: q
hash: fragment
*/
正则表达式 - 验证数字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var parse_number = /^-?\d+(?:\.\d*)?(?:e[+\-]?\d+)?$/i;
var isNumber = function (value) {
var result = parse_number.test(value);
console.log("%s %s a number", value, result ? "is" : "is not");
}
isNumber('1')
// 1 is a number
isNumber('number')
// number is not a number
isNumber('98.6')
// 98.6 is a number
isNumber('132.21.86.100')
// 132.21.86.100 is not a number
isNumber('123.45E-67')
// 123.45E-67 is a number
isNumber('123.45D-67')
//123.45D-67 is not a number

// 数字可能由一个整数部分加上一个可选的负号、一个可选的小数部分和一个可选的指数部分构成
正则表达式中32个ASCII特殊字符集合:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
!"#$%
&'()*
+,-./
:;<=>
?@[\]
^_`{\
}~
*/
var ascii32 = /(?:!|"|#|\$|%|&|'|\(|\)|\*|\+|,|-|\/|:|;|<|=|>|\?|@|\[|\\|]|\^|_|`|\{|\||\}|~)/;
// 这是其中一种简写方式,不过依旧难以阅读
var ascii32_short1 = /[!-\/:-@\[-`{-~]/;
// 正则表达式的一个方便之处就是类的求反。如果 [ 后的第一个字符是 ^ ,那么这个类会排除这些特殊字符。
var not_ascii = /[^!-\/:-@\[-`{-~]/;

糟粕

javascript 有两组相等运算符:=== 和 !==, 以及它们邪恶的孪生兄弟 == 和 !=。=== 和 !== 这一组运算符会按照你期望的方式工作。如果来两个运算数类型一致并且拥有相同的值,那么 === 返回 true,!== 返回 false。而它们邪恶的孪生兄弟只有在两个运算数类型一致时才会做出正确的判断,如果两个运算数是不同的类型,它们试图去强制转换值的类型。转换的规则复杂且难以记忆。这里有一些有趣的例子:

1
2
3
4
5
6
7
8
9
10
11
12
'' == '0'           // false
0 == '' // true
0 == '0' // true

false == 'false' // false
false == '0' // true

false == undefined // false
false == null // false
null == undefined // true

' \t\r\n' == 0 // true

== 运算符对传递性的缺乏值得我们警惕。我的建议是永远不要使用那对邪恶的孪生兄弟。相反,请始终使用 === 和 !==。如果以上所有比较使用 === 运算符,结果都是false

传递性是一种编程约定。可以这么理解:对于任意的引用值x、y和z,如果 x == yy == ztrue,那么x == ztrue。而javascript中的 == 运算符在某些特例上违背了传递性。


最后 以一个自定义的JSON解析器 结尾吧
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
// 这是一个用JavaScript编写JSON解析器的实现方案:
var json_parse = function () {
// 这是一个能把JSON文本解析成JavaScript数据结构的函数。
// 它是一个简单的递归降序解析器。
// 我们在另一个函数中定义此函数,以避免创建全局变量。

var at, // 当前字符索引
ch, // 当前字符
escapee = {
'"' : '"',
'\\' : '\\',
'/' : '/',
b : 'b',
f : '\f',
n : '\n',
r : '\r',
t : '\t'
},
text,
error = function (m) {
// 当某处出错时,调用error。
throw {
name : 'SyntaxError',
messsage: m,
at : at,
text : text
};
},
next = function (c) {
// 如果提供了参数 c, 那么检查它是否匹配当前字符。
if (c && c !== ch) {
error("Expected '" + c + "' insttead of '" + ch + "'");
}

// 获取下一个字符。当没有下一个字符时,返回一个空字符串。
ch = text.charAt(at);
at += 1;
return ch;
},
number = function () {
// 解析一个数字值。
var number,
string = '';
if (ch === '-') {
string = '-';
next('-');
}
while (ch >= '0' && ch <= '9') {
string += ch;
next();
}
if (ch === '.') {
string += ".";
while (next() && ch >= '0' && ch <= '9') {
string += ch;
}

}
if (ch == 'e' || ch == 'E') {
string += ch;
next();
if (ch === '-' || ch === '+') {
string += ch;
next();
}
while (ch >= '0' && ch <= '9') {
string += ch;
next();
}
}
number = +string;
if (isNaN(number)) {
error("Bad number");
} else {
return number;
}
},
string = function () {
// 解析一个字符串值。
var hex,
i,
string = '',
uffff;
// 当解析字符串值时,我们必须找到 " 和 \ 字符。
if (ch === '"') {
while (next()) {
if (ch === '"') {
next();
return string;
} else if (ch === '\\') {
next();
if (ch === 'u') {
uffff = 0;
for (i = 0; i < 4; i += 1) {
hex = parseInt(next(), 16);
if (!isFinite(hex)) {
break;
}
uffff = uffff * 16 + hex;
}
string += String.fromCharCode(uffff);
} else if (typeof escapee[ch] === 'string') {
string += escapee[ch];
} else {
break;
}
} else {
string += ch;
}
}
}
error("Bad string");
},
white = function () {
// 跳过空白
while (ch && ch <= ' ') {
next();
}
},
word = function () {
// true, false 或者 null
switch (ch) {
case 't':
next('t');
next('r');
next('u');
next('e');
return true;
case 'f':
next('f');
next('a');
next('l');
next('s');
next('e');
return false;
case 'n':
next('n');
next('u');
next('l');
next('l');
return null;
}
error("Unexpected '" + ch + "'");
},
value, // 值函数的占位符。
array = function () {
// 解析一个数组值。
var array = [];
if (ch === '[') {
white();
if (ch === ']') {
next(']');
return array; // 空数组
}
while (ch) {
array.push(value());
white();
if (ch === "]") {
next(']');
return array;
}
next(',');
white();
}
}
error("Bad array");
},
object = function () {
// 解析一个对象值
var key,
object = {};
if(ch === '{') {
next('{');
white();
if (ch === "}") {
next('}');
return object; // 空对象
}
while (ch) {
key = string();
white();
next(":");
object[key] = value();
white();
if(ch === '}') {
next('}');
return object;
}
next(",");
white();
}
}
error("Bad object");
};
value = function () {
// 解析一个JSON值。它可以是对象、数组、字符串、数字或一个词。
white();
switch (ch) {
case '{':
return object();
case '[':
return array();
case '"':
return string();
case "-":
return number();
default:
return ch >= '0' && ch <= '9' ? number() : word() ;
}
};
// 返回json_parse 函数。它能访问上述所有的函数和变量。
return function(source, reviver) {
var result;
text = source;
at = 0;
ch = ' ';
result = value();
white();
if (ch) {
error("Syntax error");
}

// 如果存在reviver 函数,我们就递归地对这个新结构调用walk函数,
// 开始时先创建一个临时的启动对象,并以一个空字符串作为键名保存结果,
// 然后传递每个键值对给reviver函数去处理可能存在的转换。
// 如果没有reviver函数,我们就简单地返回这个结果。
return typeof reviver === 'function' ?
function walk(holder, key) {
var k, v, value = holder[key];
if (value && typeof value === 'object') {
for (k in value) {
if (Object.hasOwnProperty.call(value, k)) {
v = walk(value, k);
if (v !== undefined) {
value[k] = v;
} else {
delete value[k];
}
}
}
}
return reviver.call(holder, key, value);
}({'': result}, '')
: result;
}
}();

This is the end.

正文部分到此结束。
第一次写读书笔记,一是加深自己的印象,二来方便以后查阅,毕竟纸张没有搜索功能。
如果你也喜欢这篇文章欢迎转载,但本文代码版权属于《JavaScript 语言精粹》,小部分代码扩充是为了演示代码效果。