《Javascript秘密花园》读书笔记
allenGKC opened this issue · comments
Allen commented
背景
近来闲来无事,翻翻之前收藏的一些网页,看到了《Javascript秘密花园》,这是很久之前面试前看到的,当时收藏了一下想着以后有时间看,但是一直没有时间去好好读一读,最近花了点时间仔细读了读,写下自己的一点感悟和心得。
本文包括了几块内容,涵盖了Javascript的一些常见的一些知识点,温故而知新。书中包括几大块:对象,函数,数组,类型,核心,其他。本书并没有对于一些常用的点进行罗列,而是列举了一些我们平时并不怎么会注意到的点,但又特别容易犯错,自己写了这么久代码,却对其知之甚少,实在惭愧,下面我自己对每一块内容记录一些自己觉得比较重要的点。
对象
- JavaScript 一切皆对象,除了两个例外 null 和 undefined;
- 有很多变通方法可以让数字的字面值看起来像对象;
2..toString(); // 第二个点号可以正常解析
2 .toString(); // 注意点号前面的空格
(2).toString(); // 2先被计算
- 删除属性的唯一方法是使用 delete 操作符;设置属性为 undefined 或者 null 并不能真正的删除属性, 而仅仅是移除了属性和值的关联;
- 当使用 for in 循环遍历对象的属性时,原型链上的所有属性都将被访问。要提防原型链过长带来的性能问题,并知道如何通过缩短原型链来提高性能。 更进一步,绝对不要扩展内置类型的原型,除非是为了和新的 JavaScript 引擎兼容。
- hasOwnProperty 是 JavaScript 中唯一一个处理属性但是不查找原型链的函数,在使用 for in loop 遍历对象时,推荐总是使用 hasOwnProperty方法, 这将会避免原型对象扩展带来的干扰。
函数
- this在不同情况指向不同:全局范围内指向全局对象;函数调用时指向全局对象;方法调用指向调用该方法的对象;构造函数在函数内部指向新创建的对象;使用call或者apply时指向第一个参数对象。
- 闭包是 JavaScript 一个非常重要的特性,这意味着当前作用域总是能够访问外部作用域中的变量。 因为函数是 JavaScript 中唯一拥有自身作用域的结构,因此闭包的创建依赖于函数。
- 经典的循环中使用闭包
for(var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
上面的代码不会输出数字 0 到 9,而是会输出数字 10 十次。
为了正确的获得循环序号,最好使用 匿名包装器(即自执行匿名函数):
for(var i = 0; i < 10; i++) {
(function(e) {
setTimeout(function() {
console.log(e);
}, 1000);
})(i);
}
- arguments 变量不是一个数组(Array)。 尽管在语法上它有数组相关的属性 length,但它不从 Array.prototype 继承,实际上它是一个对象(Object)。
- 强烈建议大家不要使用 arguments.callee 和它的属性,会显著的影响现代 JavaScript 引擎的性能。
- 工厂模式创建新对象
function Foo() {
var obj = {};
obj.value = 'blub';
var private = 2;
obj.someMethod = function(value) {
this.value = value;
}
obj.getPrivate = function() {
return private;
}
return obj;
}
上面这种方式存在几个问题:1)会占用更多的内存,因为新创建的对象不能共享原型上的方法。2)为了实现继承,工厂方法需要从另外一个对象拷贝所有属性,或者把一个对象作为新创建对象的原型。3)放弃原型链仅仅是因为防止遗漏 new 带来的问题,这似乎和语言本身的**相违背。
7. 声明变量时绝对不要遗漏 var 关键字,不使用 var 声明变量将会导致隐式的全局变量产生。
8. 推荐使用匿名包装器(译者注:也就是自执行的匿名函数)来创建命名空间。这样不仅可以防止命名冲突, 而且有利于程序的模块化。
(function() {
// 函数创建一个命名空间
window.foo = function() {
// 对外公开的函数,创建了闭包
};
})(); // 立即执行此匿名函数
数组
- 为了更好的性能,推荐使用普通的 for 循环并缓存数组的 length 属性。 使用 for in 遍历数组被认为是不好的代码习惯并倾向于产生错误和导致性能问题。
- 应该尽量避免使用数组构造函数创建新数组。推荐使用数组的字面语法。它们更加短小和简洁,因此增加了代码的可读性。
类型
- 强烈推荐使用严格等于操作符。如果类型需要转换,应该在比较之前显式的转换, 而不是使用语言本身复杂的强制转换规则。
- typeof只有一个实际的应用,而这个应用却不是用来检查对象的类型。
Value Class Type
-------------------------------------
"foo" String string
new String("foo") String object
1.2 Number number
new Number(1.2) Number object
true Boolean boolean
new Boolean(true) Boolean object
new Date() Date object
new Error() Error object
[1,2,3] Array object
new Array(1, 2, 3) Array object
new Function("") Function function
/abc/g RegExp object (function in Nitro/V8)
new RegExp("meow") RegExp object (function in Nitro/V8)
{} Object object
new Object() Object object
- 为了检测一个对象的类型,强烈推荐使用 Object.prototype.toString 方法;
Object.prototype.toString.call([]) // "[object Array]"
Object.prototype.toString.call({}) // "[object Object]"
Object.prototype.toString.call(2) // "[object Number]"
- instanceof 操作符应该仅仅用来比较来自同一个 JavaScript 上下文的自定义对象。 正如 typeof 操作符一样,任何其它的用法都应该是避免的。
- 为了避免复杂的强制类型转换,强烈推荐使用严格的等于操作符
核心
- 绝对不要使用 eval,任何使用它的代码都会在它的工作方式,性能方面受到质疑。eval 也存在安全问题,因为它会执行任意传给它的代码,在代码字符串未知或者是来自一个不信任的源时。
- 建议绝对不要省略分号,同时也提倡将花括号和相应的表达式放在一行, 对于只有一行代码的 if 或者 else 表达式,也不应该省略花括号。
其他
- 基于 JavaScript 引擎的计时策略,以及本质上的单线程运行方式,所以其它代码的运行可能会阻塞此线程。 因此没法确保函数会在 setTimeout 指定的时刻被调用。
- 绝对不要使用字符串作为 setTimeout 或者 setInterval 的第一个参数, 这么写的代码明显质量很差。当需要向回调函数传递参数时,可以创建一个匿名函数,在函数内执行真实的回调函数。另外,应该避免使用 setInterval,因为它的定时执行不会被 JavaScript 阻塞。