目录
本文为《编写高质量代码》读书笔记
01.警惕Unicode乱码
要点
-
在JS中可以使用中文来命名变量或函数名
-
ECMA标准规定JS语言基于Unicode标准进行开发,JS内核完全采用UCS字符集进行编写,因此在JS代码中每个字符都采用两个字节来表示。
- Unicode把一对字符视为一个单一的字符,而JS认为一对字符是两个不同的字符,这将会带来很多问题,考虑到代码的安全性,我们应该尽量使用基本字符进行编码
名词解释
-
Unicode标准:一种字符集标准,用于对世界上不同语言、文字系统和符号进行编号和定义
-
UCS字符集:Unicode字符集简称UCS
资料
http://www.ruanyifeng.com/blog/2014/12/unicode.html
02.正确辨析JS句法中的词、句和段
词语是JS句法结构中的最小语义单位,包括指令、变量、常量、运算符等。
在JS中,词语之间必须使用分隔符进行分隔,否则JS会错误解析。
03.减少全局变量污染
-
全局变量就是在所有作用域中都可见的变量。
-
一个全局变量可以被程序的任何部分在任意时间改变,就使得程序的行为被极大地复杂化,在程序中使用全局变量降低了程序的可靠性。
04.注意JS数据类型的特殊性
防止浮点数溢出
-
二进制的浮点数不能正确地处理十进制的小数,因此0.1+0.2不等于0.3。根本原因是JS使用Number类来表示数字(没有区分int和float)。遵循IEEE 754标准,通过64位来表示一个数字(1 + 11 +52)。
-
1 符号位,0表示正数,1表示负数s
-
11 指数位(e)
-
52 尾数,小数部分(即有数字)
在0.1+0.2时,会将0.1和0.2转化为二进制,而转化过程中尾数会发生无限循环,然后进行对阶运算,js引擎对二进制进行截断,所以造成精度丢失。
- 而浮点数的整数运算是精确的,所以可以用相加来处理。
慎用JS类型自动转化
- JS一般遵循:如果某个类型的值被用于需要其他类型的值的环境中,JS就自动将这个值转换成所需要的类型。
值(value) | 字符串操作环境 | 数字运算环境 | 逻辑运算环境 | 对象操作环境 |
---|---|---|---|---|
undefined | “undefined” | NaN | false | Error |
null | “null” | 0 | false | Error |
非空字符串 | 不转换 | 字符串对应的数字值 | ||
NaN | true | String | ||
空字符串 | 不转化 | 0 | false | String |
0 | “0” | 不转化 | false | Number |
NaN | “NaN” | 不转化 | false | Number |
Infinity | “Infinity” | 不转化 | ture | Number |
Number.POSITIVE_INFINITY(正无穷) | “Infinity” | 不转化 | true | Number |
Number.NEGATIVE_INFINITY | “-Infinity” | 不转化 | true | Number |
Number.MAX_VALUE | “1.7976931348623157e+308” | 不转化 | true | Number |
Number.MIN_VALUE | “5e-324” | 不转化 | true | Number |
其他所有数字 | “数字的字符串” | 不转化 | true | Number |
true | “true” | 1 | 不转化 | Boolean |
false | “false” | 0 | 不转化 | Boolean |
对象 | toString() | valueOf()或 toString() 或 NaN | true | 不转化 |
正确检测数据类型
typeof 任何变量,返回的始终都是以下6种类型之一:
- “number”
- “string”
- “boolean”
- “object”
- “function”
- “undefined”
另外,比较特殊的是,typeof null 返回 “object”。
检测类型的一般方法:
function type(T){
return (T === null) ? "null" : (typeof o);
}
-
但是typeof不能够检测复杂的数据类型,如正则表达式对象、日期对象等
-
对于对象或数组,可以使用constructor属性,该值的引用是原来构造该对象的函数。
constructor判断类型表
值(value) | typeof value(表达式返回值) | value.constructor(构造函数的属性值) |
---|---|---|
var value = 1 | “number” | Number |
var value = ‘b’ | “string” | String |
var value = true | “boolean” | Boolean |
var value = {} | “object” | Object |
var value = new Object() | “object” | Object |
var value = [] | “object” | Array |
var value = new Array() | “object” | Array |
var value = function() {} | “function” | Function |
function className() {} | “object” | className |
使用constructor属性可以判断大部分数据的类型,
但是,对于undefined和null特殊值,就不能使用constructor,JS解析器会抛出异常。
那么对于null和undefined,如果要利用constructor属性,需这样判断:
const type = null && null.constructor; // null
const type = undefined && undefined.constructor; // undefined
对于数值直接量也不能直接使用constructor属性,
需要给数值加上一个小括号,原因是小括号运算符能够把数值转换为对象。
apply的妙用
- 数组中的最大值
const arr = [1,23,123,1,4];
const a = Math.max.apply(null, arr);
console.log(a); // 123
- 合并两个数组
const arr = [1,23,123,1,4];
const arr1 = ['t','n','s'];
Array.prototype.push.apply(arr, arr1);
console.log(arr);
/**
* [
1, 23, 123, 1,
4, 't', 'n', 's'
]
*/
toString()检测对象类型是最安全的
- toString()方法的原理
调用toString()方法把对象转为字符串,然后通过检测字符串中是否包含数组所特有的标志字符
从而确定对象的类型。
安全检测JS基本数据类型和内置对象
/**
* 获取基本数据类型和内置对象
* @param {*} o 检测的值
*/
function typeOf(o) {
const _toString = Object.prototype.toString;
const _type = {
"undefined": undefined,
"number": "number",
"boolean": "boolean",
"string": "string",
"[object Function]": "function",
"[object Array]": "array",
"[object RegExp]": "regexp",
"[object Date]": "date",
"[object Error]": "error",
};
return _type[typeof o] || _type[_toString.call(o)] || (o ? "object" : "null");
}
- 对于非内置对象,该方法不适用,需要使用constructor和instanceof
避免误用parseInt
- parseInt是一个将字符串转换为整数的函数。
parseInt("123abc"); // 123
parseInt("1.72"); // 1
parseInt(".123"); // NaN
- parseInt(‘要转换的字符串’,‘该字符串的进制’)
parseInt('10',2); // 把二进制数字10转化为十进制整数2
parseInt('10',8); // 把八进制数字10转化为十进制整数8
05.防止JS自动插入分号
JS语言有一个机制:在解析时,能够在一句话后面自动插入一个分号。
如下:在return语句中自动插入分号将会导致返回为undefined的情况。
const f = function(){
return
{
status: true
};
}
-
最佳实践
在编写代码时,应养成使用分号结束的句子的良好习惯。
06.正确处理JS特殊值
正确使用NaN和Infinity
NaN是IEEE 754中定义的一个特殊的数量值,它不表示一个数字,尽管下面的表达式返回的是true。
typeof NaN === 'number'; // true
NaN与其它运算数的结果是NaN,并且NaN不等同于它自己。
- isNaN(),辨别数字与NaN区别
- 该全局 isFinite() 函数用来判断被传入的参数值是否为一个有限数值(finite number)。在必要情况下,参数会首先转为一个数值。
isNumber函数
const isNumber = function isNumber(value){
return typeof value === 'number' && isFinite(value);
}
正确使用null和undefined
js有5种基本类型:String、Number、Boolean、null、Undefined。
null == undefined;
null !== undefined;
与null不同,undefined不是JavaScript的保留字,在ECMAScript v3标准中才定义undefined为全局变量,
初始值为undefined。
因此,在使用undefined值时就会存在一个兼容性问题(早期浏览器可能不支持undefined)
问题是:直接赋值和使用typeof运算符外,其他任何运算符对undefined的操作都会引发异常。
怎么判断浏览器是否支持undefined
var undefined;
console.log(undefined); // undefined
如果浏览器不支持undefined关键字,可以自定义undefined变量,并将其赋值为undefined。
例如:
var undefined = void null;
由于void在执行其后面的表达式时会忽略表达式的结果值,而总是返回值undefined
还可以:
var undefined = void 1;
或者:
var undefined = function(){}();
- 可以使用typeof运算符来检测某个变量的值是否为undefined
var a;
if(typeof a == "undefined"){
}
使用假值
下面这些值的布尔值都是false,但是他们是不可互换的。
0
NaN
''
false
null
undefined
07.小心保留字的误用
JS定义了很多保留字,根据JS语法规则,保留字是不能用来命名变量或参数的。当保留字作为对象字面量的键值时,必须用引号括起来。因为保留字不能出现在对象点语法中,所以使用了保留字就需要使用括号表示法。
08.谨慎使用运算符
用===,而不用 ==
上面表达式如果全部使用 ===运算符,则都会返回true。
关于x === y返回值
若x和y类型相同,那么可以根据x的类型展开不同的判断:
- 若x的类型是undefined或null,则返回true
- 若x的类型是Number,只要x或y中有一个值为NaN,就返回false;若x和y的数字值相等,就返回true;若x或y中有一个是+0,另外一个是-0,返回true。
- 若x的类型是String,当x和y的字符序列完全相同时返回true,否则返回false
- 若x和y引用相同的对象时返回true,否则返回false
- 若x的类型是boolean,当x和y同为true或false时返回true,否则返回false
关于x==y返回值
谨慎使用++和–
需要知道先执行递加运算,还是先进行赋值运算。
小心逗号运算符
逗号在JS中表示连续运算,并返回最后运算的结果。
const a = (1,2,3,4);
console.log(a); // 4
在JS的实际开发中,逗号运算符常与for循环语句联合使用。
for(var a =10, b= 0; a > b; a++,b+=2){
//...
}
09.不要信任hasOwnProperty
hasOwnProperty方法是判断对象是否有属性,但是不会去原型上面找。
- 考虑到hasOwnProperty是一个方法,而不是一个运算符,因此,它可能会被重新赋值。
例如,下面的代码中,obj对象的hasOwnProperty成员被清空了,再使用hasOwnproperty判断obj的本地属性就会出现问题。
let obj = {};
obj.hasOwnProperty = null;
for(const name in obj) {
if(obj.hasOwnProperty(name)){
//...
}
}
10.谨记对象非空特性
JS没有真正的空对象,因为每个对象都可以从原型链中取得成员。
比如:
const a = {};
console.log(a.constructor); // "function Object() {[native code]}"
避免出现这类问题,采用与处理for in问题相同的方法,加一个过滤条件;
if(typeof count[word] === 'number'){
//...
}
11.慎重使用伪数组
JS没有真正的数组,因此typeof运算符不能辨别数组和对象,
判断是否为数组的方法:
function test(value) {
console.log(value.constructor);
if(value && typeof value === 'object' && value.constructor === Array) return true;
return false;
}
console.log(test(arguments))
// [Function: Object]
// false
console.log(test([]));
// [Function: Array]
// true
12.避免使用with
with语句的语法如下:
with(expression){
statement
}
with会把由expression计算出来的对象添加到当前执行上下文的作用域链的前面,然后使用这个扩大的作用域链来执行语句statement,最后恢复作用域链。不管其中的语句是否正常退出,作用域链都会被恢复。
由于with会把额外的对象添加到作用域链的前面,因此使用with可能会影响性能,并造成难以发现的错误。
13.养成优化表达式的思维方式
加小括号
使用小括号分隔符来优化表达式内部的逻辑层次,提高代码可阅读性。
- bad case。可能被&&和||的优先级所迷惑。
(a + b > c && a - b < c || a > b > c)
- good case。加入小括号,逻辑优先级的顺序就会非常清晰。
((a + b > c) && ((a - b < c) || (a > b > c)))
改变表达式结构顺序
随意混用大于号和小于号等运算符,会造成逻辑结构错乱。
- bad case
if((age >= 6 && age < 18) || age >= 65){
}
采用统一的小于号,按照从小到大的思维顺序进行排列,阅读起来就非常清晰了。
- good case
if((6 <= age && age < 18) || 65<= age){
}
避免布尔表达式的叠加
- bad case
if(!(!isA || !isB)){
}
- good case
(!isA || !isB) = !(isA && isB)
(!isA && !isB) = !(isA || isB)
if语句分解
对于使用?的复杂表达式,如果不仔细进行分析,很难理清他的逻辑顺序。
- bad case
const a.b = new c(a.d ? a.e(1) : a.f(1));
使用if条件语句后,逻辑结构就变得非常清晰了
- good case
if(a.d){
var a.b = new c(a.e(1));
}else {
var a.b = new c(a.f(0));
}