运行时类型是代码实际执行过程中我们用到的类型。所有的类型数据都会属于 7 个类型之一。从变量、参数、返回值到表达式中间结果,任何 JavaScript 代码运行过程中产生的数据,都具有运行时类型。
问题
- 为什么有的编程规范要求用 void 0 代替 undefined?
- 字符串有最大长度吗?
- 0.1 + 0.2 不是等于 0.3 么?为什么 JavaScript 里不是这样的?
- ES6 新加入的 Symbol 是个什么东西?
- 为什么给对象添加的方法能用在基本类型上?
为什么有的编程规范要求用 void 0 代替 undefined?
因为 JavaScript 的代码 undefined 是一个变量,而并非是一个关键字,这是 JavaScript 语言公认的设计失误之一,所以,我们为了避免无意中被篡改,我建议使用 void 0 来获取 undefined 值。
字符串有最大长度吗?
String 有最大长度是 2^53 - 1,这在一般开发中都是够用的,但是有趣的是,这个所谓最大长度,并不完全是你理解中的字符数。因为 String 的意义并非“字符串”,而是字符串的 UTF16 编码,我们字符串的操作 charAt、charCodeAt、length 等方法针对的都是 UTF16 编码。所以,字符串的最大长度,实际上是受字符串的编码长度影响的。
0.1 + 0.2 不是等于 0.3 么?为什么 JavaScript 里不是这样的?
console.log(0.1 + 0.2 == 0.3);
这里输出的结果是 false,说明两边不相等的,这是浮点运算的特点,也是很多同学疑惑的来源,浮点数运算的精度问题导致等式左右的结果并不是严格相等,而是相差了个微小的值。
所以实际上,这里错误的不是结论,而是比较的方法,正确的比较方法是使用 JavaScript 提供的最小精度值:
console.log(Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON);
检查等式左右两边差的绝对值是否小于最小精度,才是正确的比较浮点数的方法。这段代码结果就是 true 了。
ES6 新加入的 Symbol 是个什么东西?
Symbol 是 ES6 中引入的新类型,它是一切非字符串的对象 key 的集合,在 ES6 规范中,整个对象系统被用 Symbol 重塑。
我们创建 Symbol 的方式是使用全局的 Symbol 函数。例如:
var mySymbol = Symbol("my symbol");
一些标准中提到的 Symbol,可以在全局的 Symbol 函数的属性中找到。例如,我们可以使用 Symbol.iterator 来自定义 for…of 在对象上的行为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var o = new Object
o[Symbol.iterator] = function() {
var v = 0
return {
next: function() {
return { value: v++, done: v > 10 }
}
}
};
for(var v of o)
console.log(v); // 0 1 2 3 ... 9
为什么给对象添加的方法能用在基本类型上?
Object 是 JavaScript 中最复杂的类型,也是 JavaScript 的核心机制之一。
在 JavaScript 中,对象的定义是“属性的集合”
JavaScript 中的几个基本类型,都在对象类型中有一个“亲戚”。它们是:
- Number;
- String;
- Boolean;
- Symbol。
运算符提供了装箱操作,它会根据基础类型构造一个临时对象,使得我们能在基础类型上调用对应对象的方法。
类型转换
最为臭名昭著的是 JavaScript 中的“ == ”运算,因为试图实现跨类型的比较,它的规则复杂到几乎没人可以记住。
这里我们当然也不打算讲解 == 的规则,它属于设计失误,并非语言中有价值的部分,很多实践中推荐禁止使用“ ==”,而要求程序员进行显式地类型转换后,用 === 比较。
所谓装箱转换,正是把基本类型转换为对应的对象,它是类型转换中一种相当重要的种类。
我们定义一个函数,函数里面只有 return this,然后我们调用函数的 call 方法到一个 Symbol 类型的值上,这样就会产生一个 symbolObject。
1
2
3
4
5
6
var symbolObject = (function(){ return this; }).call(Symbol("a"));
console.log(typeof symbolObject); //object
console.log(symbolObject instanceof Symbol); //true
console.log(symbolObject.constructor == Symbol); //true
拆箱转换在 JavaScript 标准中,规定了 ToPrimitive 函数,它是对象类型到基本类型的转换(即,拆箱转换)。
Symbol.toPrimitive 是一个内置的 Symbol 值,它是作为对象的函数值属性存在的,当一个对象转换为对应的原始值时,会调用此函数。——MDN
在 ES6 之后,还允许对象通过显式指定 @@toPrimitive Symbol 来覆盖原有的行为。
1
2
3
4
5
6
7
8
9
10
11
12
var o = {
valueOf : () => {console.log("valueOf"); return {}},
toString : () => {console.log("toString"); return {}}
}
o[Symbol.toPrimitive] = () => {console.log("toPrimitive"); return "hello"}
console.log(o + "")
// toPrimitive
// hello
有一个说法是:程序 = 算法 + 数据结构
,运行时类型包含了所有 JavaScript 执行时所需要的数据结构的定义,所以我们要对它格外重视。
不用原生的Number和parseInt实现StringToNumber
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let StringToNumber = str => {
let arr = str.trim().split('');
let sign = arr[0] === '-' ? -1 : 1;
if (arr[0] === '-' || arr[0] === '+') {
arr.shift()
}
return sign * arr.reduce((total, cur)=>(
total * 10 + (cur >= '0' && cur <= '9' ? (cur - '0') : NaN)
));
}
let res = []
res.push(StringToNumber(' 123456 '))
res.push(StringToNumber(' -9237 '))
res.push(StringToNumber(' + 9237 '))
res.push(StringToNumber(' 7 545 '))
res.push(StringToNumber(' 34 234 '))
res.push(StringToNumber(' 12@3 '))
res.push(StringToNumber(' 234*235 '))
console.log(res) // [123456, -9237, 9237, NaN, NaN, NaN, NaN]