《JavaScipt 语言精粹》养分

《JavaScipt 语言精粹》 作者: Douglas Crockford

拿到这本书的时候就读错了名字。悲剧啊。内容写的挺详细的,就是书中用得铁路图实在看着有点费劲。

养分

1. beget创建一个使用原对象作为其原型的新的对象。

1
2
3
4
5
6
7
if (typeof Object.beget !== 'function') {
Object.beget = function(obj) {
var F = function() {};
F.prototype = obj;
return new F();
}
}

2. 使用 hasOwnProperty 方法来判断对象是否拥有独有的属性。这个方法不会检查原型链的属性。

3. 减少全局变量污染:

  • 可以把所有的全局变量放在一个全局对象里面。 把多个全局变量都整理在一个名称空间下。
  • 也可以使用闭包来实现。

4. 模块,可以使用函数和闭包来构造模块。模块是一个提供接口却隐藏状态与实现的函数或对象。几乎可以完全摒弃全局变量的使用。通过特权函数(可以理解为接口)访问外部函数的局部变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var moduleDemo = function() {
var _name = '';
var _age = 0;
return {
setName: function(name) {
_name = name;
},
setAge: function(age) {
_age = age;
},
getProfile: function() {
return 'name:' + _name + ' age:' + _age;
}
};
};
var module = moduleDeme();
module.setName('aikin');
module.setAge(24);
var result = module.getProfile();

// _name 和 _age 都是匿名函数 moduleDemo 的局部变量(私有变量)。函数 moduleDemo // 内,通过闭包的方式提供了访问_name 和 _age 的特权函数(也可以理解我接口)。

5. 级联, 就是有点像函数式编程的样子。通过不断‘点’来调用,如 jquery对某个元素操作:

1
2
3
4
5
6
7
8
9
$('#demoId')
.removeClass('yellow')
.addClass('blue')
.on('mousedown', function(event) {
})
.on('mousemove', function(event) {
})

//这里调用的方法都是同一个对象的,然后每个方法返回的变量是 this 。

6. 套用,使用闭包实现 curry 化。调用一个函数,将传入的参数,作为闭包使用的变量。

1
2
3
4
5
6
7
function curry(x) {
return function(y) {
return x + y;
}
}
var add = curry(2);
console.log(add(4)); // 6

7. 记忆,使用对象缓存(记忆)先前操作的结果,从而避免无用功操作。

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
var fibonacci = function(n) {
return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
};

for (var i = 0; i <= 10; i += 1) {
console.log('//' + i + ': ' + fibonacci(i));
}

// 实现 记忆模式
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;
}());

// 重构 让递归流程不变,初始化存储结果和基本公式
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);
});

8. 函数化。解决继承模式无法设置保护隐私问题。实现更好的风筝和信息隐藏。用函数化的样式创建一个对象,并且该对象的所有方法都不使用 this 或 that,那么该对象就是持久性的。一个持久性对象就是一个简单功能函数集合。

函数化构造器的伪代码模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var constructor = function(spec, my) {
var that; // 其他的私有实例变量
my = my || {};

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

that = 一个新对象

添加给 that 的特权方法

return that;
}

// spec 对象包含构造器需要构造一个新实例的所有信息。
// my 对象是一个为继承链中的构造器提供秘密共享的容器。my 对象可变参数。

9. 部件。就是通过调用一个函数返回一个对象。而被调用的函数就像是一个中间者,函数的外部不需要知道内部发生什么。把传入的参数,经过函数内部的加工后,而具有了某些或某类的功能和属性。这些功能和属性就像部件一样。

构造一个能添加简单事件处理特性到任何对象上的函数。它会给对象添加一个 on 方法、一个 fire 方法和一个私有的事件注册表对象(可以把这个函数理解为一个小车间,一个对象经过这个小车间加工后,就组装了部件 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
var eventuality = function(that) {

var registry = {};
that.fire = function(event) {
var array,
func,
handler,
i,
type = typeof event === 'string' ?
event : event.type;

if (registry.hasOwnProperty(type)) {
array = registry[type];
for (i = 0; i < array.lenght; 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); 用这种方式,一个构造函数可以从一套部件中组装处对象来。

10. 正则表达式,嵌套的正则表达式也能导致极恶劣的性能问题,因此简单是最好的策略。使用非捕获型分组来代替少量不优美的捕获型分组是很好的方法,因为捕获会有性能上的损失。

解析 url 例子

1
2
3
4
5
6
var parse_url = /^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/;
var url = "http://www.ora.com:80/googparts?q#fragment";

var result = parse_url.exec(url);

// ["http://www.ora.com:80/googparts?q#fragment", "http", "//", "www.ora.com", "80", "googparts", "q", "fragment"]
  • ^ 表示这个字符串的开始。
  • (?:([A-Za-z]+):)? 用于匹配一个协议名,当仅当它之后跟随一个:的时候才匹配。(?: ... ) 表示一个非捕获型分组。后缀?表示这个分组是可选的,他表示重复 0或1次。(...)表示一个捕获型分组,一个捕获型分组将复制它所匹配的文本,并将其放入 result 数组中,每个捕获型分组都将被指定一个编号。第一个捕获型分组的编号是 1, 所以该分组所匹配的文本拷贝将出现在 result[1] 中。[...]表示一个字符类,这个字符类A-Za-z包含26个大写字母和26个小写字母。连字符-表示范围从 A 到 Z。后缀+表示这个字符类将被匹配 1 次或多次。这组后面跟着字符:表示按字面进行匹配。
  • (\/{0,3}) 是捕获型分组 2。\/表示一个应该被匹配的 /(斜杠)。(反斜杠)表示转义。{0,3}表示 / 将被匹配0次,或者1到3次之间。
  • ([0-9.\-A-Za-z]+) 是捕获型分组 3。它将匹配一个主机名,由1个或多个数字、字母或 . 或 - 组成。因为 - 将被转义为 \- 以防止与表示范围的连字符相混淆。
  • (?::(\d+))? 可选的匹配端口号的分组。它由一个前置 : 加上1一个或着多个数字而组成的序列。(?::)表示不会捕获:,只会捕获:后的数字。\d表示一个数字字符,(\d+)表示捕获型分组4, 1个或多个数字串将被捕获。
  • (?:\/([^?#]*))? 可选的分组。[^?#]以一个^开始,表示这个字符类包含除了?#之外的所有字符。*表示这个字符类将被匹配0次或多次。
  • (?:\?([^#]*))? 是一个?开始的可选分组。([^#]*)是捕获型分组,包含0个或多个非#字符。
  • (?:#(.*))?#开始,.将匹配除行结束符以外的所有字符。
  • $ 表示这个字非常的结束。

判断是否为数字

1
2
var parse_number = /^-?\d+(?:\.\d*)?(?:e[+\-]?\d+)?$/i;
var is_number = parse_number.test('1222.45');
  • (?:e[+\-]?\d+)? 可选的非捕获型分组。它将匹配一个 e(或 E),[+\-]?表示一个可选的正负号。\d+表示一个或多个数字。

11. 方法

array.concat(item…) 可以对数组进行浅拷贝,并将一个或多个参数item附加到新数组最后。

1
2
3
4
5
var a = ['a', 'b', 'c'];
var b = ['x', 'y', 'z'];
var c = a.concat(b, true);

// c 是 ['a', 'b', 'c', 'x', 'y', 'z', true];

array.slice(start, end) 对 array 中的一段做浅拷贝。

1
2
3
4
5
var a = ['a', 'b', 'c'];
var b = a.slice(0, 1); // ['a']
var c = a.slice(1); // ['b', 'c']
var d = a.slice(1, 2); // ['b'];
var e = a.slice(4); // [];

array.splice(start, deleteCount, item…) 从 array 中移除一个或多个元素,并用新的 item 替换它们。item 是可选参数。

1
2
3
4
5
var a = ['a', 'b', 'c'];
var b = a.splice(1, 1, 'ache', 'bug');

// a 是 ['a', 'ache', 'bug', 'c'];
// b 是 ['b'];

string.charAt(pos)
string.chartCodeAt(pos)

1
2
3
4
var name = 'Curly';
var initial = name.chatAt(0); // 'C'

var otherInitial = name.chatACodet(0); // 67

string.slice(start, end)
string.substring(start, end)
这个两个方法相似,都是复制字符串的指定部分。但是 substring 不能 start 参数不能为负数。

1
2
3
4
var text = 'and in it he says "Any damn fool could';
var a = text.slice(18); // '"Any damn fool could'
var b = text.slice(0, 3); // 'and'
var c = text.slice(-5); // 'could'

string.split(separator, limit)
将字符串分割成数组。separator 可以是个字符串或正则表达式,limit是可选参数。

1
2
3
4
5
6
var digits = '0123456789';
var a = digits.split('', 5); // ['0', '1', '2', '3', '456789']

var text = 'last, first, middle';
var d = text.split(/\s*,\s*/); // ["last", "first", "middle"]
var e = text.split(/\s*(,)\s*/) // ["last", ",", "first", ",", "middle"]