本文作为对本书的一些知识点的收集

正文

1. 类型

ECMAScript 语言类型包括 Undefined、Null、Boolean、String、Number 和 Object。

  1. 内置类型
  • JavaScript 有七种内置类型:
    • 空值(null)
    • 未定义(undefined)
    • 布尔值( boolean)
    • 数字(number)
    • 字符串(string)
    • 对象(object)
    • 符号(symbol,ES6 中新增)

除对象 object 之外,其他统称为“基本类型”。

  1. 小结

JavaScript 有 七 种 内 置 类 型:nullundefinedbooleannumberstringobjectsymbol,可以使用 typeof 运算符来查看。

变量没有类型,但它们持有的值有类型。类型定义了值的行为特征。
很多开发人员将 undefinedundeclared 混为一谈,但在 JavaScript 中它们是两码事。

undefined 是值的一种。undeclared 则表示变量还没有被声明过。
遗憾的是,JavaScript 却将它们混为一谈,在我们试图访问 “undeclared” 变量时这样报错:ReferenceError: a is not defined,并且 typeofundefinedundeclared 变量都返回 “undefined”。

然而,通过 typeof 的安全防范机制(阻止报错)来检查 undeclared 变量,有时是个不错的办法。

2. 值

  1. 数组

和其他强类型语言不同,在 JavaScript 中,数组可以容纳任何类型的值,可以是字符串、数字、对象(object),甚至是其他数组(多维数组就是通过这种方式来实现的):

使用 delete 运算符可以将单元从数组中删除,但是请注意,单元删除后,数组的 length 属性并不会发生变化。

  • 在创建“稀疏”数组(sparse array,即含有空白或空缺单元的数组)时要特别注意: ``` var a = [ ]; a[0] = 1; // 此处没有设置a[1]单元 a[2] = [ 3 ]; a[1]; // undefined a.length; // 3
  1. > 上面的代码可以正常运行,但其中的“空白单元”(empty slot)可能会导致出人意料的结果。a[1] 的值为 `undefined`,但这与将其显式赋值为 `undefined``a[1] = undefined`)还是有所区别。
  2. - 数组通过数字进行索引,但有趣的是它们也是对象,所以也可以包含字符串键值和属性(但这些并不计算在数组长度内):

var a = [ ]; a[0] = 1; a[“foobar”] = 2; a.length; // 1 a[“foobar”]; // 2 a.foobar; // 2

  1. > 这里有个问题需要特别注意,如果字符串键值能够被强制类型转换为十进制数字的话,它就会被当作数字索引来处理。

var a = [ ]; a[“13”] = 42; a.length; // 14

  1. > 在数组中加入字符串键值 / 属性并不是一个好主意。建议使用对象来存放键值 / 属性值,用数组来存放数字索引值。
  2. 2. 类数组
  3. - 有时需要将类数组(一组通过数字索引的值)转换为真正的数组

function foo() { var arr = Array.prototype.slice.call( arguments ); arr.push( “bam” ); console.log( arr ); } foo( “bar”, “baz” ); // [“bar”,”baz”,”bam”]

  1. > 如上所示,`slice()` 返回参数列表(上例中是一个类数组)的一个数组复本。
  2. - `ES6` 中的内置工具函数 `Array.from(..)` 也能实现同样的功能:

var arr = Array.from( arguments );

  1. > `Array.from(..)` 还有一些其他非常强大的功能
  2. 3. 数字
  3. - 应该怎样来判断 `0.1 + 0.2` `0.3` 是否相等呢?
  4. > 最常见的方法是设置一个误差范围值,通常称为“机器精度”(machine epsilon),对 `JavaScript` 的数字来说,这个值通常是 `2^-52 (2.220446049250313e-16)`
  5. > `ES6` 开始,该值定义在 `Number.EPSILON` 中,我们可以直接拿来用,也可以为 ES6 之前的版本写 `polyfill`

if (!Number.EPSILON) { Number.EPSILON = Math.pow(2,-52); }

  1. > 可以使用 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. > 能够呈现的最大浮点数大约是 `1.798e+308`(这是一个相当大的数字),它定义在 `Number.MAX_VALUE` 中。最小浮点数定义在 `Number.MIN_VALUE` 中,大约是 `5e-324`,它不是负数,但无限接近于 0
  2. - 整数检测
  3. > 要检测一个值是否是整数,可以使用 `ES6` 中的 `Number.isInteger(..)` 方法:

Number.isInteger( 42 ); // true Number.isInteger( 42.000 ); // true Number.isInteger( 42.3 ); // false

  1. > 也可以为 `ES6` 之前的版本 `polyfill` `Number.isInteger(..)` 方法:

if (!Number.isInteger) { Number.isInteger = function(num) { return typeof num == “number” && num % 1 == 0; }; }

  1. > 要检测一个值是否是安全的整数,可以使用 `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

  1. > 可以为 `ES6` 之前的版本 `polyfill` `Number.isSafeInteger(..)` 方法:

if (!Number.isSafeInteger) { Number.isSafeInteger = function(num) { return Number.isInteger( num ) && Math.abs( num ) <= Number.MAX_SAFE_INTEGER; }; }

  1. - 不是值的值
  2. > `undefined` 类型只有一个值,即 `undefined``null` 类型也只有一个值,即 `null`。它们的名称既是类型也是值。
  3. > `undefined` `null` 常被用来表示“空的”值或“不是值”的值。二者之间有一些细微的差别。例如:<br />
  4. `null` 指空值(empty value)<br />
  5. `undefined` 指没有值(missing value)<br />
  6. 或者:<br />
  7. `undefined` 指从未赋值<br />
  8. `null` 指曾赋过值,但是目前没有值
  9. > `null` 是一个特殊关键字,不是标识符,我们不能将其当作变量来使用和赋值。然而<br />
  10. `undefined` 却是一个标识符,可以被当作变量来使用和赋值。
  11. - 特殊的数字 - `NaN`
  12. > `NaN` 是一个特殊值,它和自身不相等,是唯一一个非自反(自反,`reflexive`,即 `x === x` 不成立)的值。
  13. > 判断一个值是否是 `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——好!

  1. - 特殊等式
  2. > 如前所述,`NaN` `-0` 在相等比较时的表现有些特别。由于 `NaN` 和自身不相等,所以必须使用 `ES6` 中的 `Number.isNaN(..)`(或者 `polyfill`)。而 `-0` 等于 `0`(对于 `===` 也是如此,参见第4 章),因此我们必须使用 `isNegZero(..)` 这样的工具函数。<br />
  3. `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

  1. > 对于 `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; }; }

  1. > 能使用 `==` `===`(参见第 4 章)时就尽量不要使用 `Object.is(..)`,因为前者效率更高、更为通用。`Object.is(..)` 主要用来处理那些特殊的相等比较。
  2. 4. 值和引用
  3. > `JavaScript` 中没有指针,引用的工作机制也不尽相同。在 `JavaScript` 中变量不可能成为指向另一个变量的引用。
  4. > `JavaScript` 引用指向的是值。如果一个值有 10 个引用,这些引用指向的都是同一个值,它们相互之间没有引用 / 指向关系。
  5. > `JavaScript` 对值和引用的赋值 / 传递在语法上没有区别,完全根据值的类型来决定。
  6. > 简单值(即标量基本类型值,`scalar primitive`)总是通过值复制的方式来赋值 / 传递,包括 `null``undefined`、字符串、数字、布尔和 `ES6` 中的 `symbol`
  7. > 复合值(`compound value`)——对象(包括数组和封装对象,参见第 3 章)和函数,则总是通过引用复制的方式来赋值 / 传递。
  8. - 函数参数相关的代码示例

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]

  1. > 我们向函数传递 `a` 的时候,实际是将引用 `a` 的一个复本赋值给 `x`,而 `a` 仍然指向 `[1,2,3]`。<br />
  2. 在函数中我们可以通过引用 `x` 来更改数组的值(`push(4)` 之后变为 `[1,2,3,4])`。但 `x = [4,5,6]` 并不影响 `a` 的指向,所以 `a` 仍然指向 `[1,2,3,4]`。<br />
  3. 我们不能通过引用 `x` 来更改引用 `a` 的指向,只能更改 `a` `x` 共同指向的值。
  4. - 如果要将 `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]

  1. > 从上例可以看出,`x.length = 0` `x.push(4,5,6,7)` 并没有创建一个新的数组,而是更改了当前的数组。于是 `a` 指向的值变成了 `[4,5,6,7]`。<br />
  2. 请记住:我们无法自行决定使用值复制还是引用复制,一切由值的类型来决定。
  3. 5. 小结
  4. > `JavaScript` 中的数组是通过数字索引的一组任意类型的值。字符串和数组类似,但是它们的行为特征不同,在将字符作为数组来处理时需要特别小心。<br />
  5. `JavaScript` 中的数字包括“整数”和“浮点型”。
  6. > 基本类型中定义了几个特殊的值。<br />
  7. `null` 类型只有一个值 `null``undefined` 类型也只有一个值 `undefined`。所有变量在赋值之前默认值都是 `undefined``void` 运算符返回 `undefined`。<br />
  8. 值, `NaN` not a number”, invalid number”)、`+Infinity``-Infinity` `-0`
  9. > 简单标量基本类型值(字符串和数字等)通过值复制来赋值 / 传递,而复合值(对象等)通过引用复制来赋值 / 传递。<br />
  10. `JavaScript` 中的引用和其他语言中的引用 / 指针不同,它们不能指向别的变量 / 引用,只能指向值。
  11. ###3. 原生函数
  12. > `JavaScript` 的内建函数(`built-in function`),也叫原生函数(`native function`),如 `String` `Number`
  13. 1. 常用的原生函数有:
  14. - `String()`
  15. - `Number()`
  16. - `Boolean()`
  17. - `Array()`
  18. - `Object()`
  19. - `Function()`
  20. - `RegExp()`
  21. - `Date()`
  22. - `Error()`
  23. - `Symbol()`——`ES6` 中新加入的!
  24. > 实际上,它们就是内建函数。
  25. - 代码示例

var a = new String( “abc” ); typeof a; // 是”object”,不是”String” a instanceof String; // true Object.prototype.toString.call( a ); // “[object String]”

  1. > 通过构造函数(如 `new String("abc")`)创建出来的是封装了基本类型值(如 "abc")的封装对象。<br />
  2. 请注意:`typeof` 在这里返回的是对象(`String`)类型的子类型(`object`)。
  3. 2. 将原型作为默认值
  4. > `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

  1. > 这种方法的一个好处是 `.prototypes` 已被创建并且仅创建一次。相反,如果将 `[]`、<br />
  2. `function(){}` `/(?:)/` 作为默认值,则每次调用 isThisCool(..) 时它们都会被创建一次(具体创建与否取决于 `JavaScript` 引擎,稍后它们可能会被垃圾回收),这样无疑会造成内存和 CPU 资源的浪费。<br />
  3. 另外需要注意的一点是,如果默认值随后会被更改,那就不要使用 `Array.prototype`。上例中的 `vals` 是作为只读变量来使用,更改 `vals` 实际上就是更改 `Array.prototype`,而这样会导致前面提到过的一系列问题!
  4. 3. 小结
  5. > `JavaScript` 为基本数据类型值提供了封装对象,称为原生函数(如 `String``Number``Boolean`等)。它们为基本数据类型值提供了该子类型所特有的方法和属性(如:`String.prototype.trim()` `Array.prototype.concat(..)`)。
  6. > 对于简单标量基本类型值,比如 "abc",如果要访问它的 `length` 属性或`String.prototype` 方法,`JavaScript` 引擎会自动对该值进行封装(即用相应类型的封装对象来包装它)来实现对这些属性和方法的访问。
  7. ###4. 强制类型转换
  8. 1. 值类型转换
  9. > 也可以这样来区分:类型转换发生在静态类型语言的编译阶段,而强制类型转换则发生在、动态类型语言的运行时(`runtime`)。
  10. > 然而在 `JavaScript` 中通常将它们统称为强制类型转换,我个人则倾向于用“隐式强制类型转换”(`implicit coercion`)和“显式强制类型转换”(`explicit coercion`)来区分。
  11. > 二者的区别显而易见:我们能够从代码中看出哪些地方是显式强制类型转换,而隐式强制类型转换则不那么明显,通常是某些操作产生的副作用。

var a = 42; var b = a + “”; // 隐式强制类型转换 var c = String( a ); // 显式强制类型转换

  1. > 对变量 `b` 而言,强制类型转换是隐式的;由于 `+` 运算符的其中一个操作数是字符串,所以是字符串拼接操作,结果是数字 `42` 被强制类型转换为相应的字符串 `"42"`。<br />
  2. `String(..)` 则是将 `a` 显式强制类型转换为字符串。<br />
  3. 两者都是将数字 `42` 转换为字符串 `"42"`。然而它们各自不同的处理方式成为了争论的焦点。
  4. 2. 假值列表
  5. - undefined
  6. - null
  7. - false
  8. - +0、-0 NaN
  9. - ""
  10. 3. 日期显式转换为数字
  11. > 一元运算符 `+` 的另一个常见用途是将日期(`Date`)对象强制类型转换为数字,返回结果为 `Unix` 时间戳,以微秒为单位(从 1970 1 1 00:00:00 UTC 到当前时间):

var d = new Date( “Mon, 18 Aug 2014 08:53:06 CDT” ); +d; // 1408369986000

  1. > 我们常用下面的方法来获得当前的时间戳,例如:

var timestamp = +new Date();

```