读书笔记之JavaScript语言精粹
作为一个前端开发人员, js的一些坑已经踩了不少, 但这本书总结的非常好, 把精华部分和糟粕部分都非常系统详细的列出来, 个人认为附录部分的js毒瘤与糟粕非常值得一读, 如果你是刚开始接触js, 那就更值得你好好看看了, 绝对能少踩很多坑. 这里做个读书笔记, 总结一下这本书的附录部分, 也就是js的坑.
-
全局变量
对全局变量的依赖可能是JavaScript最糟糕的特性. 很多编程语言也有全局变量, 例如Java使用public static 关键字定义全局变量, 但在JavaScript中, 由于没有连接器(linker), 所有的编译单元都在如到一个公共全局对象中(也就是window对象). 也许在小型程序中这会带来很多便利, 但当程序变得复杂时, 全局变量便会使程序变得难以维护, 因为无论在全局哪一个地方更改全局变量的值, 全局变量都会改变. 更糟糕的是如果我们没有使用var关键字进行变量的定义, JavaScript会默认该变量为全局变量, 不管在程序的哪个地方.
function test(){ globle = 10; } test(); console.log(globle) //10
由于globle没有用var关键字修饰, JavaScript会把globle当成全局变量, 导致在函数外也可以使用. 这是一个非常糟糕的设计, 因此声名变量时, 一定要在前面加关键字 var.
-
作用域
JavaScript与C语言类似, 都是用{}来表示代码块. 但糟糕的是, 与C语言不同, JavaScript没有提供相应的块级作用域, 也就是说, 代码块中生命的变量在包含此码块的函数的任何位置都是可见的, 听起来十分拗口, 举个例子就好懂多了.
for (var i=0; i<5; i++){ var g = i; } console.log(g) //4
for循环中的变量 g 是属于块级代码, 但在全局中却可以调用. 这就是 js 的作用域问题, 与之相对的是C语言的块级作用域. 先看以下代码
#include<stdio.h> int main(){ int i = 2; if (i){ int j = 10; } printf(j); }
运行这段代码, 编译器会报错. error: ‘j’ was not declared in this scope. 这是由于j是在块级作用域if语句里面的. 所以在全局中式无法使用的.
因此在JavaScript中要注意这一点, 由于js的这个特性, 我们最好在每个函数的开头部分就声明所有的变量.
-
自动插入分号
JavaScript并不强制使用 分号 作为一个语句的结尾, 但当你忘记在语句中使用 分号做为结尾时, 它会自动插入分号来试图修正程序. 注意了, 这个特性可能会出现一些神奇的问题. 例如以下的 return 语句.
return { type: 1, msg: "ok" }
上面的代码看似返回一个包含元素的对象, 但由于JavaScript自动插入分号的特性, 实际执行的代码已经变成了
return; { type: 1, msg: "ok" }
这样就导致返回值变成了 undefined, 一定要注意, 上例正确的写法应该把 "{" 放在上一行,也就是
return { type: 1, msg: "ok" }
-
typeof
typeof 运算符可以用来识别变量或常量的类型.部分情况下它是可以正常判断的, 但是
type null; // object
返回的是object 而不是 null ! 这一点千万注意.我们可以用其他的方法来判断一个变量是否为null,最简单的方法就是
value === null;
不知道你有没有注意到,由于typeof无法判断object 和null, 因此判断一个变量是否为object时,一定要再三小心.
typeof value === 'object'; //错误的判断方法 value && type value === 'object'; // 正确,先判断是否为null
-
parseInt
曾经我在面试的时候遇到过一道经典的JavaScript题,至今让我印象深刻.
var arr = ['1', '2', '3', '4', '5']; var num; num = arr.map(parseInt);
很简单的代码,就是把arr数组中的元素转化成整数,理论上来说应该是[1,2,3,4,5], 是的,理论上.然而实际结果确实是[1, NaN, NaN, NaN, NaN].其实原因也不难,就是因为 parseInt 方法其实是可以带两个参数的,第一个参数是要转化成数字的字符串,另一个是指定进制,默认是使用十进制来进行转换,这里举两个例子说明一下.
parseInt('10', 8); // 8 parseInt('10'); // 10
我想你已经知道问题所在了,还不知道?再看看map方法的语法.
var new_array = arr.map(function callback(currentValue, index, array) { // Return element for new_array }
也就是说,实际运行过程中数组的索引会被当成parseInt的第二个参数.也就是像下面这样.所以就会出现这样看似不合理的结果了.要避免这种情况也不难,把parseInt 换成Number 就可以了.
parseInt('1',0); parseInt('2',1); parseInt('3',2); ...
-
浮点数
这个其实不算坑,只是需要注意一下,先看下面的例子.
0.1+0.2; // 0.30000000000000004
第一次看到这个结果的时候我也吓了一跳,但其实原因很简单,因为CPU的计算其实都是二进制的,而二进制的浮点数不能正确的处理十进制的小数点,说以会导致计算结果不精准,这不仅是JavaScript的问题,很多编程语言也有同样的问题,例如Python,Java.幸运地是,整数是可以完美处理的.对于有小数点的运算,我们可以指定计算精度来避免这样的问题.
-
NaN
不得不说这是JavaScript中一个奇葩的变量类型,它表示一个特殊的数量值,它不代表一个数字,当数学表达式无法计算时会用NaN表示,亦或者把非数字形式的字符串转换成数字时.但
typeof NaN === 'number'; // true
也就是说typeof 是无法辨认数字与NaN的(typeof要你有何用!),更恶心的是
NaN === NaN; // false
真的无话可说,发明NaN这个关键字绝对是设计者脑抽了.要判断一个变量只能用一个方法;
isNaN(NaN); //true
延伸一下,由于上面的问题,我们是无法直接用 typeof 方法来判断一个变量是不是数字的.千万要注意,别掉坑里了,我们可以加一个判断.
typeof value === 'number' && isFinite(value);
-
数组
JavaScript 中没有真正的数组,JavaScript 中没有真正的数组,JavaScript 中没有真正的数组.重要的事情说三遍.在JavaScript中,一切皆为对象,数组其实是一个有length的对象.这并不都是坏事,由于这个特性,你不必给数组设置维度,也不用担心越界错误(但其实这更容易导致出现隐藏的bug).但这也导致其性能下降的厉害.同理的,你也无法用typeof直接判断一个变量是否为数组(typeof要你有何用!!),你需要检查一个名作constructor的属性.
typeof value === 'object' && value.constructor === Array;
-
对象
是的,JavaScript 的对象也是要我们注意的.与Python, Java 不同,JavaScript 中的对象不是通过类来定义的,而是通过原型链.因此,当你创建一个空对象时,该对象绝对不是空的,而是包含了从上一级中继承下来的各种方法与属性,这在有时候会导致问题变得很麻烦.举个例子,你现在在做一个文本分析脚本,首先需要统计一篇文章中每个单词出现的个数,这个时候就要十分小心了.先看下面的代码.
var text = "To deal with this, you can generate a stack trace in the constructor of the exception object during the throw exception statement. "; var words = text.toLowerCase().split(/[\s,.]+/) var count = {}; var word; for (var i=0;i<words.length;i++){ word = words[i]; if (count[word]){ count[word] += 1; } else{ count[word] = 1; } }
上面的代码很简单,就是统计一下每个单词出现的次数,但如果查看 count.constructor,你会发现 输出了这个鬼东西:"function Object() { [native code] }1",原因就是上面所说的. count对象继承自Object.prototype.而Object.prototype 中包含着一个constructor的对象.如何避免这个问题呢,我们可以做一个判断,检测成员类型,是数字才进行处理.
-
其他
接下来的东西就不细讲了,都是不建议使用的.
== 这个不用说了吧,大家都知道
with 别用
eval 有坑,用前详细查看文档.
new 尽量别用
void 别用