类型

functionobject的子类型,但是函数对象function拥有自己的属性:

var a = function(a, b) {}
console.log(a.length)  // 2,参数个数

undefinedundeclared(未声明)

typeof处理未定义未声明时的方式一样的。

var a;
typeof a  // undefined
typeof b  // undefined;

这属于typeof的安全防范机制,在引用变量之前,有必要判断变量是否申明时很有用(避免产生ReferenceError错误)。也可以使用windows来实现相同的功能,但是不能兼容到非浏览器环境。

typeof func()  // ReferenceError: func is not defined

typeof一个未声明的函数时会报错。

在给代码写polyfill时,避免实用var来定义变量,即使if条件不成立也是如此,因为在编译阶段,var定义的变量已经全局声明了。

Javascript内置的七种类型:nullundefinedbooleannumberstringobjectsymbol

数组

创建“稀疏”数组时要注意:

var arr = [];
arr[0] = 1;
arr[2] = 2;
arr.length;  // 3
console.log(arr[1])  // undefined

此时的arr[1]和显示的指定arr[1] = undefined,是有区别的。

如果字符串键值能够被强制类型转换为十进制数字的话,它就会被当作数字索引来处理。

字符串

字符串属于类数组。和数组不相同的一点是,在JavaScript中字符串是不可变的,其中不可变是指字符串的成员函数不会改变其原始值,而是创建并返回一个新的字符串

字符串也有concat()方法。

数字

特别大和特别小的数字默认用指数格式显示,与toExponential()函数的输出结果相同。

var a = 5E10;  // 50000000000
a.toExponential();  // 5e+10

toFixed()toPrecision()等方法同样适用于数字常量,但是.运算符是一个有效的数字字符,会优先识别为数字常量,然后才是对象属性访问运算符,所以以下是无效语法:

42.toFixed(3);  // SyntaxError

以下语法才有效:

(42).toFixed(3);  // "42.000"
42..toFixed(3);  // "0.420"
0.42.toFixed(3);  // "42.000"

较小的数值

二进制浮点数(不仅JavaScript,所有遵循IEEE 754规范的语言都是如此)最大的问题是精度问题:

0.1 + 0.2 === 0.3;  // false

解决方案是设置一个误差范围,对于JavaScript的数字来说,这个值通常是2^-52 (2.220446049250313e-16)。从ES6始,该值定义在Number.EPSILON中。

// 给不支持es6的代码写polyfills
if (!Number.EPSILON) {
  Number.EPSILON = Math.pow(2,-52);
}
// 判断两个数的差是否在误差范围内
function numbersCloseEnoughToEqual(n1,n2) {
  return Math.abs( n1 - n2 ) < Number.EPSILON;  // 相减取绝对值后和‘机器误差’比较
}

能够呈现的最大浮点数:Number.MAX_VALUE,最小浮点数:Number.MIN_VALUE

整数的安全范围

能够“安全”呈现的最大整数:Number.MAX_SAFE_INTEGER,能够“安全”呈现的最小整数Number.MIN_SAFE_INTEGER

最大整数小于最大浮点数,最小整数(复数)要小于最小浮点数(最小正数)。

32位有符号整数

虽然整数最大能够达到53位,但是有些数字操作(如数位操作)只适用于32位数字,所以这些操作中数字的安全范围就要小很多,变成从Math.pow(-2,31)-2147483648, 约-21亿)到Math.pow(2,31) - 12147483647,约21亿)。

特殊数值

不是值的值

undefinednull的名称既是类型也是值。null是一个特殊关键字,不是标识符;而undefined则是一个内置的标识符,在非严格模式下可以给全局标识符undefined赋值(但是千万不要这么做)。

void运算符,表达式void ___没有返回值,因此返回结果就是undefined。**void并不改变表达式的结果,只是让表达式不返回值**。

function doSomething() {
  if (!APP.ready) {
    // setTimeout会返回一个标识符,会使下面的if语句误判
    return void setTimeout(doSomething, 100);
  }
  var result;
  // 其他
  return result;
}
if (doSomething()) {
  // 立即执行下一个任务
}

// 下面的代码也能有同样的效果
setTimeout(doSomething, 100);
return

总之,如果要将代码中的值(如表达式的返回值)设为undefined,就可以使用void。这种做法并不多见,但在某些情况下却很有用。

特殊的数字

不是数字的数字

NaN是一个“警戒值”(sentinel value有特殊用途的常规值),用于指出数字类型中的错误情况,即“执行数学运算没有成功,这是失败后返回的结果”。它仍然是数字类型。

window.isNaN有一个严重的缺陷,它的逻辑是——检查参数是否不是NaN,也不是数字(即会将参数使用Number()转换一次)。

isNaN('foo');  // true,很明显,foo并不是NaN

ES6开始我们可以使用Number.isNaN,弥补了这一缺陷。

可以给不兼容ES6的代码写polyfill

if (!Number.isNaN) {
  Number.isNaN = function(n) {
      // 利用了NaN不等于自身的特性
      return n !== n;
  };
}
零值

有些应用程序中的数据需要以级数形式来表示(比如动画帧的移动速度),数字的符号位(sign)用来代表其他信息(比如移动的方向)。此时如果一个值为0的变量失去了它的符号位,它的方向信息就会丢失。所以保留0值的符号位可以防止这类情况发生。

0乘以或除以一个负数会得到一个负零,加法和减法不会得到负零。

JSON处理负零的方式很奇怪:

JSON.stringify({"a": -0, "b": 0});  // "{"a":0,"b":0}"
JSON.parse('{"a":0,"b":0}');  // {"a":0,"b":0}
JSON.parse('{"a":-0,"b":0}');  // {"a":-0,"b":0}

在工作中尤其要注意!

特殊等式

NaN和自身不相等,而-0等于0ES6中新加入了一个工具方法Object.is(),可以判断两个值是否绝对相等:

var a = 1 / 'foo';
var b = 0 * -3;
a === NaN;  // false
Object.is(a, NaN);  // true
Object.is(b, 0);  // false
Object.is(b, -0);  // true

值和引用

简单值(即标量基本类型值,scalar primitive)总是通过值复制的方式来赋值 / 传递,包括nullundefined、字符串、数字、布尔和ES6中的symbol

复合值(compound value)——对象(包括数组和封装对象)和函数,则总是通过引用复制的方式来赋值 / 传递。

函数参数中的值引用
function foo(x) {
  x.push(4);  // 3.函数中我们可以通过引用改变数组的值,因为a和x指向的是同一个内存地址
  x; // 4.此时内存中的数组为[1,2,3,4]
  // 然后
  x = [4, 5, 6];  // 5.此时x这个变量更改了引用,这个动作不能修改a的引用,a还是指向[1, 2, 3, 4]
  x.push(7);  // 6.通过x这个引用修改了内存中新的数组,与[1, 2, 3, 4]一点关系都没有
  x; // [4,5,6,7]
}
var a = [1, 2, 3];  // 1.内存中创建了一个数组,a引用了这个数组的地址
foo(a);  // 2.传参时,实际是将引用a的一个副本赋值给x
console.log(a); // 7.是[1,2,3,4],不是[4,5,6,7]

总结:如果是对象通过参数的方式传入函数,传入的是这个对象的引用(通过函数参数变量引用),如果函数参数变量更改了引用(例如:重新给这个变量引用了另外一个对象),则之后对这个参数变量的操作将无法对传入的对象有丝毫影响。工作中特别要注意的是处理数组时,会返回新数组的方法赋值给参数变量后是无法修改传入的数组的!

我们无法自行决定使用值复制还是引用复制,一切由值的类型来决定。