本文作为对本书的一些知识点的收集
正文
1. 类型
ECMAScript 语言类型包括 Undefined、Null、Boolean、String、Number 和 Object。
- 内置类型
- JavaScript 有七种内置类型:
• 空值(null)
• 未定义(undefined)
• 布尔值( boolean)
• 数字(number)
• 字符串(string)
• 对象(object)
• 符号(symbol,ES6 中新增)
除对象
object
之外,其他统称为“基本类型”。
- 小结
JavaScript
有 七 种 内 置 类 型:null
、undefined
、boolean
、number
、string
、object
和symbol
,可以使用typeof
运算符来查看。变量没有类型,但它们持有的值有类型。类型定义了值的行为特征。
很多开发人员将undefined
和undeclared
混为一谈,但在JavaScript
中它们是两码事。
undefined
是值的一种。undeclared
则表示变量还没有被声明过。
遗憾的是,JavaScript
却将它们混为一谈,在我们试图访问 “undeclared” 变量时这样报错:ReferenceError: a is not defined
,并且typeof
对undefined
和undeclared
变量都返回 “undefined”。然而,通过
typeof
的安全防范机制(阻止报错)来检查undeclared
变量,有时是个不错的办法。2. 值
- 数组
和其他强类型语言不同,在
JavaScript
中,数组可以容纳任何类型的值,可以是字符串、数字、对象(object),甚至是其他数组(多维数组就是通过这种方式来实现的):使用
delete
运算符可以将单元从数组中删除,但是请注意,单元删除后,数组的length
属性并不会发生变化。
- 在创建“稀疏”数组(sparse array,即含有空白或空缺单元的数组)时要特别注意: ``` var a = [ ]; a[0] = 1; // 此处没有设置a[1]单元 a[2] = [ 3 ]; a[1]; // undefined a.length; // 3
> 上面的代码可以正常运行,但其中的“空白单元”(empty slot)可能会导致出人意料的结果。a[1] 的值为 `undefined`,但这与将其显式赋值为 `undefined`(`a[1] = undefined`)还是有所区别。
- 数组通过数字进行索引,但有趣的是它们也是对象,所以也可以包含字符串键值和属性(但这些并不计算在数组长度内):
var a = [ ]; a[0] = 1; a[“foobar”] = 2; a.length; // 1 a[“foobar”]; // 2 a.foobar; // 2
> 这里有个问题需要特别注意,如果字符串键值能够被强制类型转换为十进制数字的话,它就会被当作数字索引来处理。
var a = [ ]; a[“13”] = 42; a.length; // 14
> 在数组中加入字符串键值 / 属性并不是一个好主意。建议使用对象来存放键值 / 属性值,用数组来存放数字索引值。
2. 类数组
- 有时需要将类数组(一组通过数字索引的值)转换为真正的数组
function foo() { var arr = Array.prototype.slice.call( arguments ); arr.push( “bam” ); console.log( arr ); } foo( “bar”, “baz” ); // [“bar”,”baz”,”bam”]
> 如上所示,`slice()` 返回参数列表(上例中是一个类数组)的一个数组复本。
- 用 `ES6` 中的内置工具函数 `Array.from(..)` 也能实现同样的功能:
var arr = Array.from( arguments );
> `Array.from(..)` 还有一些其他非常强大的功能
3. 数字
- 应该怎样来判断 `0.1 + 0.2` 和 `0.3` 是否相等呢?
> 最常见的方法是设置一个误差范围值,通常称为“机器精度”(machine epsilon),对 `JavaScript` 的数字来说,这个值通常是 `2^-52 (2.220446049250313e-16)`。
> 从 `ES6` 开始,该值定义在 `Number.EPSILON` 中,我们可以直接拿来用,也可以为 ES6 之前的版本写 `polyfill`:
if (!Number.EPSILON) { Number.EPSILON = Math.pow(2,-52); }
> 可以使用 Number.EPSILON 来比较两个数字是否相等(在指定的误差范围内):
function numbersCloseEnoughToEqual(n1,n2) { return Math.abs( n1 - n2 ) < Number.EPSILON; } var a = 0.1 + 0.2; var b = 0.3; numbersCloseEnoughToEqual( a, b ); // true numbersCloseEnoughToEqual( 0.0000001, 0.0000002 ); // false
> 能够呈现的最大浮点数大约是 `1.798e+308`(这是一个相当大的数字),它定义在 `Number.MAX_VALUE` 中。最小浮点数定义在 `Number.MIN_VALUE` 中,大约是 `5e-324`,它不是负数,但无限接近于 0 !
- 整数检测
> 要检测一个值是否是整数,可以使用 `ES6` 中的 `Number.isInteger(..)` 方法:
Number.isInteger( 42 ); // true Number.isInteger( 42.000 ); // true Number.isInteger( 42.3 ); // false
> 也可以为 `ES6` 之前的版本 `polyfill` `Number.isInteger(..)` 方法:
if (!Number.isInteger) { Number.isInteger = function(num) { return typeof num == “number” && num % 1 == 0; }; }
> 要检测一个值是否是安全的整数,可以使用 `ES6` 中的 `Number.isSafeInteger(..)` 方法:
Number.isSafeInteger( Number.MAX_SAFE_INTEGER ); // true Number.isSafeInteger( Math.pow( 2, 53 ) ); // false Number.isSafeInteger( Math.pow( 2, 53 ) - 1 ); // true
> 可以为 `ES6` 之前的版本 `polyfill` `Number.isSafeInteger(..)` 方法:
if (!Number.isSafeInteger) { Number.isSafeInteger = function(num) { return Number.isInteger( num ) && Math.abs( num ) <= Number.MAX_SAFE_INTEGER; }; }
- 不是值的值
> `undefined` 类型只有一个值,即 `undefined`。`null` 类型也只有一个值,即 `null`。它们的名称既是类型也是值。
> `undefined` 和 `null` 常被用来表示“空的”值或“不是值”的值。二者之间有一些细微的差别。例如:<br />
`null` 指空值(empty value)<br />
`undefined` 指没有值(missing value)<br />
或者:<br />
`undefined` 指从未赋值<br />
`null` 指曾赋过值,但是目前没有值
> `null` 是一个特殊关键字,不是标识符,我们不能将其当作变量来使用和赋值。然而<br />
`undefined` 却是一个标识符,可以被当作变量来使用和赋值。
- 特殊的数字 - `NaN`
> `NaN` 是一个特殊值,它和自身不相等,是唯一一个非自反(自反,`reflexive`,即 `x === x` 不成立)的值。
> 判断一个值是否是 `NaN`,从 `ES6` 开始我们可以使用工具函数 `Number.isNaN(..)`。`ES6` 之前的浏览器的 `polyfill` 如下:
if (!Number.isNaN) { Number.isNaN = function(n) { return ( typeof n === “number” && window.isNaN( n ) ); }; } var a = 2 / “foo”; var b = “foo”; Number.isNaN( a ); // true Number.isNaN( b ); // false——好!
- 特殊等式
> 如前所述,`NaN` 和 `-0` 在相等比较时的表现有些特别。由于 `NaN` 和自身不相等,所以必须使用 `ES6` 中的 `Number.isNaN(..)`(或者 `polyfill`)。而 `-0` 等于 `0`(对于 `===` 也是如此,参见第4 章),因此我们必须使用 `isNegZero(..)` 这样的工具函数。<br />
`ES6` 中新加入了一个工具方法 `Object.is(..)` 来判断两个值是否绝对相等,可以用来处理上述所有的特殊情况:
var a = 2 / “foo”; var b = -3 * 0; Object.is( a, NaN ); // true Object.is( b, -0 ); // true Object.is( b, 0 ); // false
> 对于 `ES6` 之前的版本,`Object.is(..)` 有一个简单的 `polyfill`:
if (!Object.is) { Object.is = function(v1, v2) { // 判断是否是-0 if (v1 === 0 && v2 === 0) { return 1 / v1 === 1 / v2; } // 判断是否是NaN if (v1 !== v1) { return v2 !== v2; } // 其他情况 return v1 === v2; }; }
> 能使用 `==` 和 `===`(参见第 4 章)时就尽量不要使用 `Object.is(..)`,因为前者效率更高、更为通用。`Object.is(..)` 主要用来处理那些特殊的相等比较。
4. 值和引用
> `JavaScript` 中没有指针,引用的工作机制也不尽相同。在 `JavaScript` 中变量不可能成为指向另一个变量的引用。
> `JavaScript` 引用指向的是值。如果一个值有 10 个引用,这些引用指向的都是同一个值,它们相互之间没有引用 / 指向关系。
> `JavaScript` 对值和引用的赋值 / 传递在语法上没有区别,完全根据值的类型来决定。
> 简单值(即标量基本类型值,`scalar primitive`)总是通过值复制的方式来赋值 / 传递,包括 `null`、`undefined`、字符串、数字、布尔和 `ES6` 中的 `symbol`。
> 复合值(`compound value`)——对象(包括数组和封装对象,参见第 3 章)和函数,则总是通过引用复制的方式来赋值 / 传递。
- 函数参数相关的代码示例
function foo(x) { x.push( 4 ); x; // [1,2,3,4] // 然后 x = [4,5,6]; x.push( 7 ); x; // [4,5,6,7] } var a = [1,2,3]; foo( a ); a; // 是[1,2,3,4],不是[4,5,6,7]
> 我们向函数传递 `a` 的时候,实际是将引用 `a` 的一个复本赋值给 `x`,而 `a` 仍然指向 `[1,2,3]`。<br />
在函数中我们可以通过引用 `x` 来更改数组的值(`push(4)` 之后变为 `[1,2,3,4])`。但 `x = [4,5,6]` 并不影响 `a` 的指向,所以 `a` 仍然指向 `[1,2,3,4]`。<br />
我们不能通过引用 `x` 来更改引用 `a` 的指向,只能更改 `a` 和 `x` 共同指向的值。
- 如果要将 `a` 的值变为 `[4,5,6,7]`,必须更改 `x` 指向的数组,而不是为 `x` 赋值一个新的数组。
function foo(x) { x.push( 4 ); x; // [1,2,3,4] // 然后 x.length = 0; // 清空数组 x.push( 4, 5, 6, 7 ); x; // [4,5,6,7] } var a = [1,2,3]; foo( a ); a; // 是[4,5,6,7],不是[1,2,3,4]
> 从上例可以看出,`x.length = 0` 和 `x.push(4,5,6,7)` 并没有创建一个新的数组,而是更改了当前的数组。于是 `a` 指向的值变成了 `[4,5,6,7]`。<br />
请记住:我们无法自行决定使用值复制还是引用复制,一切由值的类型来决定。
5. 小结
> `JavaScript` 中的数组是通过数字索引的一组任意类型的值。字符串和数组类似,但是它们的行为特征不同,在将字符作为数组来处理时需要特别小心。<br />
`JavaScript` 中的数字包括“整数”和“浮点型”。
> 基本类型中定义了几个特殊的值。<br />
`null` 类型只有一个值 `null`,`undefined` 类型也只有一个值 `undefined`。所有变量在赋值之前默认值都是 `undefined`。`void` 运算符返回 `undefined`。<br />
数 字 类 型 有 几 个 特 殊 值, 包 括 `NaN`( 意 指 “not a number”, 更 确 切 地 说 是 “invalid number”)、`+Infinity`、`-Infinity` 和 `-0`。
> 简单标量基本类型值(字符串和数字等)通过值复制来赋值 / 传递,而复合值(对象等)通过引用复制来赋值 / 传递。<br />
`JavaScript` 中的引用和其他语言中的引用 / 指针不同,它们不能指向别的变量 / 引用,只能指向值。
###3. 原生函数
> `JavaScript` 的内建函数(`built-in function`),也叫原生函数(`native function`),如 `String` 和 `Number`。
1. 常用的原生函数有:
- `String()`
- `Number()`
- `Boolean()`
- `Array()`
- `Object()`
- `Function()`
- `RegExp()`
- `Date()`
- `Error()`
- `Symbol()`——`ES6` 中新加入的!
> 实际上,它们就是内建函数。
- 代码示例
var a = new String( “abc” ); typeof a; // 是”object”,不是”String” a instanceof String; // true Object.prototype.toString.call( a ); // “[object String]”
> 通过构造函数(如 `new String("abc")`)创建出来的是封装了基本类型值(如 "abc")的封装对象。<br />
请注意:`typeof` 在这里返回的是对象(`String`)类型的子类型(`object`)。
2. 将原型作为默认值
> `Function.prototype` 是一个空函数,`RegExp.prototype` 是一个“空”的正则表达式(无任何匹配),而 `Array.prototype` 是一个空数组。对未赋值的变量来说,它们是很好的默认值。
function isThisCool(vals,fn,rx) { vals = vals || Array.prototype; fn = fn || Function.prototype; rx = rx || RegExp.prototype; return rx.test( vals.map( fn ).join( “” ) ); } isThisCool(); // true
isThisCool( [“a”,”b”,”c”], function(v){ return v.toUpperCase(); }, /D/ ); // false
> 这种方法的一个好处是 `.prototypes` 已被创建并且仅创建一次。相反,如果将 `[]`、<br />
`function(){}` 和 `/(?:)/` 作为默认值,则每次调用 isThisCool(..) 时它们都会被创建一次(具体创建与否取决于 `JavaScript` 引擎,稍后它们可能会被垃圾回收),这样无疑会造成内存和 CPU 资源的浪费。<br />
另外需要注意的一点是,如果默认值随后会被更改,那就不要使用 `Array.prototype`。上例中的 `vals` 是作为只读变量来使用,更改 `vals` 实际上就是更改 `Array.prototype`,而这样会导致前面提到过的一系列问题!
3. 小结
> `JavaScript` 为基本数据类型值提供了封装对象,称为原生函数(如 `String`、`Number`、`Boolean`等)。它们为基本数据类型值提供了该子类型所特有的方法和属性(如:`String.prototype.trim()` 和 `Array.prototype.concat(..)`)。
> 对于简单标量基本类型值,比如 "abc",如果要访问它的 `length` 属性或`String.prototype` 方法,`JavaScript` 引擎会自动对该值进行封装(即用相应类型的封装对象来包装它)来实现对这些属性和方法的访问。
###4. 强制类型转换
1. 值类型转换
> 也可以这样来区分:类型转换发生在静态类型语言的编译阶段,而强制类型转换则发生在、动态类型语言的运行时(`runtime`)。
> 然而在 `JavaScript` 中通常将它们统称为强制类型转换,我个人则倾向于用“隐式强制类型转换”(`implicit coercion`)和“显式强制类型转换”(`explicit coercion`)来区分。
> 二者的区别显而易见:我们能够从代码中看出哪些地方是显式强制类型转换,而隐式强制类型转换则不那么明显,通常是某些操作产生的副作用。
var a = 42; var b = a + “”; // 隐式强制类型转换 var c = String( a ); // 显式强制类型转换
> 对变量 `b` 而言,强制类型转换是隐式的;由于 `+` 运算符的其中一个操作数是字符串,所以是字符串拼接操作,结果是数字 `42` 被强制类型转换为相应的字符串 `"42"`。<br />
而 `String(..)` 则是将 `a` 显式强制类型转换为字符串。<br />
两者都是将数字 `42` 转换为字符串 `"42"`。然而它们各自不同的处理方式成为了争论的焦点。
2. 假值列表
- undefined
- null
- false
- +0、-0 和 NaN
- ""
3. 日期显式转换为数字
> 一元运算符 `+` 的另一个常见用途是将日期(`Date`)对象强制类型转换为数字,返回结果为 `Unix` 时间戳,以微秒为单位(从 1970 年 1 月 1 日 00:00:00 UTC 到当前时间):
var d = new Date( “Mon, 18 Aug 2014 08:53:06 CDT” ); +d; // 1408369986000
> 我们常用下面的方法来获得当前的时间戳,例如:
var timestamp = +new Date();
```