【前端学习】《JavaScript语言精粹》读书笔记
第2章:语法
1. 通常需通过检测object.hasOwnProperty(variable)来确定这个属性名就是该对象的成员,还是从其原型链中找到的。
for (myvar in obj) {
if (obj.hasOwnProperty(myvar)) {
...
}
}
2. throw语句中的表达式通常是一个对象字面量,它包含一个name属性和一个message属性。
3. break语句可以指定一个可选的标签,那将会使程序退出带该标签的语句。
4. typeof运算符产生的值有'number', 'string', 'boolean', 'undefined', 'function'和'object'。如果运算符是一个数组或null,那么结果是'object'。
5. 如果第一个运算数的值为假,那么&&产生它的第一个运算数的值,否则产生第二个运算数的值。
6. 如果第一个运算数的值为真,那么||产生它的第一个运算数的值,否则产生第二个运算数的值。
第3章:对象
1. 在对象字面量中,如果属性名是一个合法的JavaScript标识符且不是保留字,并不强制要求用引号括住属性名。所以用引号括住"first-name"是必须的,但是否括住first_name则是可选的。
2. ||运算符可用于为对象检索填充默认值,如var status = flight.status || "unknown"。
3. &&运算符可用于避免尝试检索undefined值时将会导致的TypeError异常,例如flight.equipment && flight.equipment.model。
4. 对象通过引用来传递,它们永远不会被拷贝。
5. 原型连接只有在检索值的时候才被用到。如果我们尝试去获取对象的某个属性值,且该对象没有此属性名,那么JavaScript会试着从原型对象中获取属性值。如果那个原型对象也没有该属性,那么再从它的原型中寻找,依此类推,直到该过程最后到达终点Object.prototype。如果想要的属性完全不存在于原型链中,那么结果就是undefined值。这个过程称为委托。
6. 原型关系是一种动态的关系。如果我们添加一个新的属性到原型中,该属性会立即对所有基于该原型创建的对象可见。
7. typeof操作符会检查原型链中的属性,而hasOwnProperty方法则不会检查原型链。这个方法还可以用于在for in语句中遍历属性。
8. for in语句中,属性名出现的顺序是不固定的。若想要保证属性以特定的顺序出现,最好的办法是创建一个数组,在其中以正确的顺序包含属性名。
9. delete运算符可以用来删除对象的属性,但它不会触及原型链中的任何对象。
10. 减少全局变量污染的一个方法是在应用中只创建一个全局变量,让该变量变成应用的容器。
第4章:函数
1. 当实际参数(arguments)的值与形式参数(parameters)的个数不匹配时不会导致运行时错误。如果实际参数过多了,超出的参数值将被忽略。如果实际参数值过少,缺失的值将会被替换为undefined。对参数值不会进行类型检查:任何类型的值都可以被传递给参数。
2. 函数的调用模式:
方法调用模式:
// 函数被保存为对象的一个属性
var myObject = {
value = 0;
increment = function (inc) {
this.value += typeof inc === 'number' ? inc : 1;
}
};
myObject.increment();
document.writeln(myObject.value); // 1
myObject.increment();
document.writeln(myObject.value); // 3
// 方法可以使用this去访问对象。this到对象的绑定发生在调用时(very late binding)
// 通过this可取得它们所属对象的上下文的方法称为公共方法。
函数调用模式:
// 一个函数并非一个对象的属性时,当作一个函数来调用 // 使用该模式调用时,this被绑定到全局对象
// 这是语言设计上的一个错误,错误的后果是方法不能利用内部函数来帮助它工作 var sum = add(3, 4) // sum的值为7 // 解决办法:如果该方法定义一个变量并给它赋值为this,那么内部函数就可以通过那个变量访问到this myObject.double = function () { var that = this; var helper = function () { that.value = add(that.value, that.value); } helper(); }; myObject.double(); document.writeln(myObject.getValue()); // 6
构造器调用模式:
// 如果在一个函数前面带上new来调用,那么将创建一个隐藏连接到该函数的prototype成员的新对象
// 同时this将会被绑定到那个新对象上 var Quo = function (string) { this.status = string; }; Quo.prototype.get_status = function () { return this.status; }; var myQuo = new Quo("confused"); document.writeln(myQuo.get_status()); //令人困惑
// 按照约定,构造器函数被保存在以大写格式命名的变量里。大写约定很重要。
// 不推荐使用这种形式的构造器函数。
Apply调用模式:
var array = [3, 4];
var sum = add.apply(null, array); // sum值为7
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'
3. ”免费“奉送的参数:arguments数组。注意:由于语言的设计错误,arguments只是一个类似数组(array-like)的对象,拥有一个length属性,但它缺少所有的数组方法。
4. 如果函数以在前面加上new前缀的方式来调用,且返回值不是一个对象,则返回this(该新对象)。
5. JavaScript抛出异常的方法:
var add = function (a, b) {
if (typeof a !== 'number' || typeof b !== 'number') {
throw {
name: 'TypeError',
message: 'add needs numbers'
};
}
return a + b;
}
6. 一个try语句只会有一个将捕获所有异常的catch代码块。如果处理手段取决于异常的类型,那么异常处理器必须检查异常对象的name属性以确定异常的类型。
7. JavaScript支持给Object、prototype、函数、数组、字符串、数字、正则表达式和布尔值增加方法。一个给字符串类型添加trim方法的实例如下:
Function.prototype.method = function (name, func) {
if(!this.prototype[name]) {
this.prototype[name] = func;
return this;
}
};
String.method('trim', function () {
return this.replace(/^\s+|\s+$/g, '');
});
console.log('"' + " neat ".trim() + '"')
8. JavaScript当前没有提供尾递归优化,深度递归的函数可能会因为返回堆栈溢出而运行失败。
9. JavaScript不支持块级作用域。最好的做法是在函数体的顶部声明函数中可能用到的所有变量。
10. 通过调用一个函数的形式去初始化myObject对象的一个实例如下。最后一行的()使得myObject的值是调用函数后返回的结果,而不是这个函数。这个函数返回一个包含两个方法的对象,并且这些方法继续享有访问value变量的特权,但函数的作用域使得value对其他的程序来说是不可见的。
var myObject = function () {
var value = 0;
return {
increment: function (inc) {
value += typeof inc === 'number' ? inc : 1;
},
getValue: function () {
return value;
}
}
}();
11. 理解闭包比较好用的三个实际例子:
// 定义一个函数,它设置一个DOM节点为黄色,然后把它渐变为白色
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);
// 糟糕的例子:点击一个节点时,总是会显示节点的数目
var add_the_handlers = function (nodes) {
var i;
for (i = 0; i < nodes.length; i += 1) {
nodes[i].onclick = function (e) {
alert(i);
}
}
};
// 更好的例子:点击一个节点时,显示节点的序号
var add_the_handlers = function (nodes) {
var i;
for (i = 0; i < nodes.length; i += 1) {
nodes[i].onclick = function (i) {
return function (e) {
alert(e);
};
}(i);
}
};
12. 使用函数和闭包构造模块的两个实际例子:
String.method('deentityify', function () {
var entity = {
quot: '"',
lt: '<',
gt: '>'
};
return function () {
return this.replace(/&([^&;]+);/g,
function (a, b) {
var r = entity[b];
return typeof r === 'string' ? r : a;
}
);
};
}());
模块模式的一般形式是:一个定义了私有变量和函数的函数;利用闭包创建可以访问私有变量和函数的特权函数;最后返回这个特权函数,或者把它们保存到一个可访问到的地方。
var serial_maker = function () {
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"
13. 让没有返回值的方法返回this而不是undefined,就可以启用级联。、
14. 函数可以用对象去记住先前操作的结果,从而避免无谓的运算。这种优化被称为记忆。JavaScript的对象和数组要实现这种优化是非常方便的。一个利用memorizer来定义fibonacci以及factorial函数的例子如下:
var memorizer = 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 = memorizer([0, 1], function (shell, n) {
return shell(n - 1) + shell(n - 2);
});
var factorial = memorizer([1, 1], function (shell, n) {
return n * shell(n - 1);
});
第5章:继承
15. 下面是一个函数化构造器的伪代码模板:
var constructor = function (spec, my) {
var that, 其他的私有实例变量;
my = my || {};
把共享的变量和函数添加到my中
that = 一个新对象
添加给that的特权方法
return that;
}
16. 我们可以从一套部件中组合出对象来。例如,我们可以构造一个能添加简单事件处理特性到任何对象上的函数,它会给对象添加一个on方法、一个fire方法和一个私有的事件注册表对象:
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.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: parameter,
};
if (registry.hasOwnProperty(type)) {
registry[type].push(handler);
} else {
registry[type] = [handler];
}
return this;
};
return that;
};
// 这样一来,我们可以在任何单独的对象上调用eventuality,授予它事件处理方法。我们也可以赶在that被返回前在一个构造器函数中调用它
eventuality(that);
第6章:数组
17. JavaScript应该对矩阵提供更好的支持。我们可以这样修正它:
Array.matrix = function (m, n, initial) {
var a, i, j, mat = [];
for (i = 0; i < m; i += 1) {
a = [];
for (j = 0; j < n; j += 1) {
a[j] = 0;
}
mat[i] = a;
}
return mat;
}
// 构造一个用0填充的4x4矩阵
var myMatrix = Array.matrix(4, 4, 0);
document.writeln(myMatrix[3][3]); // 0
// 用来构造一个恒等矩阵的方法
Array.identity = function (n) {
var i, mat = Array.matrix(n, n, 0);
for (i = 0; i < n; i += 1) {
mat[i][i] = 1;
}
return mat;
}
myMatrix = Array.identity(4);
document.writeln(myMatrix[3][3]); // 1
第7章:正则表达式
18. 正则表达式分组共有4种:
捕获型:一个捕获型分组是一个被包围在圆括号中的正则表达式选择。任何匹配这个分组的字符将被捕获。每个捕获型分组都被指定了一个数字。在正则表达式中第一个捕获(的是分组1,第二个捕获(的是分组2
非捕获型:非捕获型分组有一个(?:前缀。非捕获型分组仅做简单的匹配,并不会捕获所匹配文本。这会有微弱的性能优势。非捕获型分组不会干扰捕获型分组的编号
向前正向匹配(Positive lookahead):向前正向匹配分组有一个(?=前缀。它类似于非捕获分组,但在这个组匹配后,文本将倒回它开始的地方,实际上并不匹配任何东西
向前负向匹配(Negative lookahead):向前负向匹配分组有一个(?!前缀。它类似于向前正向匹配分组,但只有当它怕匹配失败时它才进行匹配
第8章:方法
19. array.shift()方法移除array中的第一个元素并返回该元素。如果这个数组array是空的,它会返回undefined。shift通常比pop慢得多
20. JavaScript的默认比较函数假定所有要被排序的元素都是字符串。幸运的是,你可以使用自己的比较函数来替换默认的比较函数。sort方法是不稳定的,如果想基于多个键值进行排序,需要再次做更多的工作:
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;
};
}
};
};
s.sort(by('last', by('first'))); // s是[
// {first: 'Joe', last: 'Besser'},
// {first: 'Joe', last: 'DeRita'},
// {first: 'Larry', last: 'Fine'},
// {first: 'Curly', last: 'Howard'},
// {first: 'Moe', last: 'Howard'},
// {first: 'Shemp', last: 'Howard'}
// ]
21. slice用于字符串切片,而splice方法则是用于从array中移除元素,并用新的item替换它们
22. exec方法是使用正则表达式的最强大(和最慢)的方法,test方法是使用正则表达式的最简单(和最快)的方法,但不要对这个方法使用g标识
23. string.replace方法的replaceValue可以是一个字符串或一个函数。如果replaceValue是一个字符串,字符$拥有特别的含义
第10章:优美的特性
24. 精简的JavaScript里都是好东西。包括以下主要内容:
1. 函数是头等对象
函数是有词法作用域的闭包(lambda)
2. 基于原型继承的动态对象
对象无类别的。我们可以通过普通的赋值给任何对象增加一个新成员元素。一个对象可以从另一个对象继承成员元素
3. 对象字面量和数组字面量
这对创建新的对象和数组来说是一种非常方便的表示法。JavaScript字面量是数据交换格式JSON的灵感之源
附录
25. JavaScript没有真正的数组,typeof运算符不能辨别数组和对象。要判断一个值是否为数组,你还需要检查它的constructor属性
if (my_value && typeof my_value === 'object' &&
typeof my_value.length === 'number' &&
!(my_value.propertyIsEnumerable('length')) {
// my_value确实是一个数组
}
26. JavaScript拥有令人惊讶的一大组假值,但它们是不可互换的:
值 | 类型 |
0 | Number |
NaN | Number |
'' | String |
false | Boolean |
null | Object |
undefined |
Undefined |
27. 打算与new结合使用的函数应该命名为首字母大写的形式,并且首字母大写的形式应该只用来命名那些构造器函数
28. 全局声明以/*global字样开头,可以用来列出所有特意用作全局变量的名字。JSLint可以用此信息去辨别拼写错误和被遗忘的var声明
29. 通过使用JSON.parse方法替代eval就能避免JSON解析过程中的危险和漏洞
30. 把服务端发送过来的一个HTML文本片段赋值给某个HTML元素的innerHTML属性是一个非常糟糕的习惯