## 3.2 值
### 3.2.1 数组
在JavaScript 中,数组可以容纳任何类型的值,可以是字符串、数字、对象(object),甚至是其他数组(多维数组就是通过这种方式来实现的):
~~~
var a = [ 1, "2", [3] ];
a.length; // 3
a[0] === 1; // true
a[2][0] === 3; // true
~~~
对数组声明后即可向其中加入值,不需要预先设定大小:
~~~
var a = [ ];
a.length; // 0
a[0] = 1;
a[1] = "2";
a[2] = [ 3 ];
a.length; // 3
~~~
(使用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
~~~
**建议尽量使用对象来存放键值/ 属性值,用数组来存放数字索引值。**
#### 类数组
有时需要将类数组(一组通过数字索引的值)转换为真正的数组,这一般通过数组工具函数(如`indexOf(..)、concat(..)、forEach(..)` 等)来实现。
一些DOM 查询操作会返回DOM 元素列表,它们并非真正意义上的数组,但十分类似。另一个例子是通过arguments 对象(类数组)将函数的参数当作列表来访问(从ES6 开始已废止)。
工具函数slice(..) 经常被用于这类转换:
~~~
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 );
...
~~~
### 3.2.2 字符串
JavaScript 中的字符串和字符数组并不是一回事,只是看上去相似而已。
~~~
var a = "foo";
var b = ["f","o","o"];
~~~
字符串和数组的确很相似,它们都是类数组,都有length 属性以及`indexOf(..)`(从ES5开始数组支持此方法)和`concat(..)` 方法:
~~~
a.length; // 3
b.length; // 3
a.indexOf( "o" ); // 1
b.indexOf( "o" ); // 1
var c = a.concat( "bar" ); // "foobar"
var d = b.concat( ["b","a","r"] ); // ["f","o","o","b","a","r"]
a === c; // false
b === d; // false
a; // "foo"
b; // ["f","o","o"]
~~~
但这并不意味着它们都是“字符的数组”,比如:
~~~
a[1] = "O";
b[1] = "O";
a; // "foo"
b; // ["f","O","o"]
~~~
JavaScript 中字符串是不可变的,而数组是可变的。,正确的方法应该是:`a.charAt(1)`
字符串不可变是指字符串的成员函数不会改变其原始值,而是创建并返回一个新的字符串。而数组的成员函数都是在其原始值上进行操作。
~~~
c = a.toUpperCase();
a === c; // false
a; // "foo"
c; // "FOO"
b.push( "!" );
b; // ["f","O","o","!"]
~~~
许多数组函数用来处理字符串很方便。虽然字符串没有这些函数,但可以通过“借用”数组的非变更方法来处理字符串:
~~~
a.join; // undefined
a.map; // undefined
var c = Array.prototype.join.call( a, "-" );
var d = Array.prototype.map.call( a, function(v){
return v.toUpperCase() + ".";
} ).join( "" );
c; // "f-o-o"
d; // "F.O.O."
~~~
另一个不同点在于字符串反转。数组有一个字符串没有的可变更成员函数`reverse()`:
~~~
a.reverse; // undefined
b.reverse(); // ["!","o","O","f"]
b; // ["f","O","o","!"]
~~~
可惜我们无法“借用”数组的可变更成员函数,因为字符串是不可变的:
~~~
Array.prototype.reverse.call( a );
// 返回值仍然是字符串"foo"的一个封装对象
~~~
一个变通(破解)的办法是先将字符串转换为数组,待处理完后再将结果转换回字符串:
~~~
var c = a
// 将a的值转换为字符数组
.split( "" )
// 将数组中的字符进行倒转
.reverse()
// 将数组中的字符拼接回字符串
.join( "" );
c; // "oof"
~~~
这种方法对简单的字符串完全适用,对于包含复杂字符(Unicode,如星号、多字节字符等)的字符串并不适用。
### 3.2.3 数字
JavaScript 只有一种数值类型:**number(数字)**,包括“整数”和带小数的十进制数。此处“整数”之所以加引号是因为和其他语言不同,JavaScript 没有真正意义上的整数。JavaScript 中的“整数”就是没有小数的十进制数。所以42.0 即等同于“整数”42。
JavaScript 中的数字类型是基于IEEE 754 标准来实现的,该标准通常也被称为“浮点数”。JavaScript 使用的是“双精度”格式(即64 位二进制)。
**1. 数字的语法**
JavaScript 中的数字常量一般用十进制表示。例如:
~~~
var a = 42;
var b = 42.3;
~~~
数字前面的0 可以省略:
~~~
var a = 0.42;
var b = .42;
~~~
小数点后小数部分最后面的0 也可以省略:
~~~
var a = 42.0;
var b = 42.; //不建议这样写
~~~
默认情况下大部分数字都以十进制显示,小数部分最后面的0 被省略,如:
~~~
var a = 42.300;
var b = 42.0;
a; // 42.3
b; // 42
~~~
特别大和特别小的数字默认用指数格式显示,与`toExponential() `函数的输出结果相同。
例如:
~~~
var a = 5E10;
a; //50000000000
a.toExponential(); // "5e+10"
var b = a * a;
b; // 2.5e+21
var c = 1 / a;
c; // 2e-11
~~~
由于数字值可以使用Number 对象进行封装,因此数字值可以调用`Number.prototype `中的方法。例如,`tofixed(..) `方法可指定小数部分的显示位数:
~~~
var a = 42.59;
a.toFixed( 0 ); // "43"
a.toFixed( 1 ); // "42.6"
a.toFixed( 2 ); // "42.59"
a.toFixed( 3 ); // "42.590"
a.toFixed( 4 ); // "42.5900"
~~~
上例中的输出结果实际上是给定数字的字符串形式。
`toPrecision(..) `方法用来指定有效数位的显示位数:
~~~
var a = 42.59;
a.toPrecision( 1 ); // "4e+1"
a.toPrecision( 2 ); // "43"
a.toPrecision( 3 ); // "42.6"
a.toPrecision( 4 ); // "42.59"
a.toPrecision( 5 ); // "42.590"
a.toPrecision( 6 ); // "42.5900"
~~~
上面的方法不仅适用于数字变量,也适用于数字常量。不过对于`. 运算符`需要给予特别注意,因为它是一个有效的数字字符,会被优先识别为数字常量的一部分,然后才是对象属性访问运算符。
~~~
// 无效语法:
42.toFixed( 3 ); // SyntaxError
// 下面的语法都有效:
(42).toFixed( 3 ); // "42.000"
0.42.toFixed( 3 ); // "0.420"
42..toFixed( 3 ); // "42.000"
~~~
下面的语法也是有效的(请注意其中的空格):
~~~
42 .toFixed(3); // "42.000" ,不建议使用
~~~
还可以用指数形式来表示较大的数字,如:
~~~
var onethousand = 1E3; // 即 1 * 10^3
var onemilliononehundredthousand = 1.1E6; // 即 1.1 * 10^6
~~~
数字常量还可以用其他格式来表示,如二进制、八进制和十六进制。当前的JavaScript 版本都支持这些格式:
~~~
0xf3; // 243的十六进制
0Xf3; // 同上
0363; // 243的八进制
~~~
从ES6 开始,严格模式(strict mode)不再支持0363 八进制格式(新格式如下)。0363 格式在非严格模式(non-strict mode)中仍然受支持,但是考虑到将来的兼容性,最好不要再使用。
ES6 支持以下新格式:
~~~
0o363; // 243的八进制
0O363; // 同上
0b11110011; // 243的二进制
0B11110011; // 同上
~~~
考虑到代码的易读性,不推荐使用0O363 格式,因为`0` 和大写字母`O` 在一起容易混淆。建议尽量使用小写的0x、0b 和0o。
**2. 较小的值**
二进制浮点数最大的问题是会出现如下情况:
~~~
0.1 + 0.2 === 0.3; // false
~~~
简单来说,二进制浮点数中的0.1 和0.2 并不是十分精确,它们相加的结果并非刚好等于0.3,而是一个比较接近的数字0.30000000000000004,所以条件判断结果为false。
那么应该怎样来判断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 !
**3. 整数的安全范围**
数字的呈现方式决定了“整数”的安全值范围远远小于`Number.MAX_VALUE`。能够被“安全”呈现的最大整数是2^53 - 1,即9007199254740991,在ES6 中被定义为`Number.MAX_SAFE_INTEGER`。最小整数是-9007199254740991,在ES6 中被定义为`Number.MIN_SAFE_INTEGER`。
有时JavaScript 程序需要处理一些比较大的数字, 如数据库中的64 位ID 等。由于JavaScript 的数字类型无法精确呈现64 位数值,所以必须将它们保存(转换)为字符串。好在大数值操作并不常见(它们的比较操作可以通过字符串来实现)。如果确实需要对大数值进行数学运算,目前还是需要借助相关的工具库。
**4. 整数检测**
要检测一个值是否是整数,可以使用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;
};
}
~~~
**5. 32位有符号整数**
虽然整数最大能够达到53 位,但是有些数字操作(如数位操作)只适用于32 位数字,所以这些操作中数字的安全范围就要小很多,变成从Math.pow(-2,31)(-2147483648,约-21 亿)到Math.pow(2,31) - 1(2147483647,约21 亿)。
`a | 0 `可以将变量a 中的数值转换为32 位有符号整数,因为`数位运算符| `只适用于32 位整数(它只关心32 位以内的值,其他的数位将被忽略)。因此与0 进行操作即可截取a 中的32 位数位。
某些特殊的值并不是32 位安全范围的,如`NaN `和`Infinity`,此时会对它们执行虚拟操作(abstract operation)`ToInt32`,以便转换为符合数位运算符要求的`+0 `值。
### 3.2.4 特殊的值
**1. 不是值的值**
undefined 类型只有一个值,即undefined。null 类型也只有一个值,即null。它们的名称既是类型也是值。
undefined 和null 常被用来表示“空的”值或“不是值”的值。二者之间有一些细微的差别。例如:
• null 指空值(empty value)
• undefined 指没有值(missing value)
或者:
• undefined 指从未赋值
• null 指曾赋过值,但是目前没有值
`null` 是一个特殊关键字,不是标识符,不能将其当作变量来使用和赋值。然而`undefined `却是一个标识符,可以被当作变量来使用和赋值。
**2. undefined**
在非严格模式下,可以为全局标识符undefined 赋值:
~~~
function foo() {
undefined = 2; // 非常糟糕的做法!
}
foo();
function foo() {
"use strict";
undefined = 2; // TypeError!
}
foo();
~~~
在非严格和严格两种模式下,我们可以声明一个名为undefined 的局部变量。
~~~
function foo() {
"use strict";
var undefined = 2;
console.log( undefined ); // 2
}
foo();
~~~
**永远不要重新定义undefined!**
#### void运算符
undefined 是一个内置标识符(除非被重新定义),它的值为undefined,通过`void 运算符`即可得到该值。
表达式`void ___ `没有返回值,因此返回结果是undefined。void 并不改变表达式的结果,只是让表达式不返回值:
~~~
var a = 42;
console.log( void a, a ); // undefined 42
~~~
按惯例我们用`void 0` 来获得`undefined`。`void 0、void 1 ` 和 `undefined `之间并没有实质上的区别。
void 运算符在其他地方也能派上用场,比如不让表达式返回任何结果(即使其有副作用)。
例如:
~~~
function doSomething() {
// 注: APP.ready 由程序自己定义
if (!APP.ready) {
// 稍后再试
return void setTimeout( doSomething,100 );
}
var result;
// 其他
return result;
}
// 现在可以了吗?
if (doSomething()) {
// 立即执行下一个任务
}
~~~
这里`setTimeout(..) `函数返回一个数值(计时器间隔的唯一标识符,用来取消计时),但是为了确保if 语句不产生误报(false positive),要void 掉它。
分开操作效果一样,只是没有使用void 运算符:
~~~
if (!APP.ready) {
// 稍后再试
setTimeout( doSomething,100 );
return;
}
~~~
总之,如果要将代码中的值(如表达式的返回值)设为undefined,就可以使用void。这种做法并不多见,但在某些情况下却很有用。
**3. 特殊的数字**
* (1)不是数字的数字
如果数学运算的操作数不是数字类型(或者无法解析为常规的十进制或十六进制数字),就无法返回一个有效的数字,这种情况下返回值为`NaN`。NaN 意指“不是一个数字”(not a number),将它理解为“无效数值”“失败数值”或者“坏数值”可能更准确些。
例如:
~~~
var a = 2 / "foo"; // NaN
typeof a === "number"; // true
~~~
换句话说,“不是数字的数字”仍然是数字类型。
NaN 是一个“警戒值”(sentinel value,有特殊用途的常规值),用于指出数字类型中的错误情况,即“执行数学运算没有成功,这是失败后返回的结果”。
NaN 是一个特殊值,它和自身不相等,是唯一一个非自反(自反,reflexive,即x === x 不成立)的值,而`NaN != NaN 为true`。
因此,我们无法对NaN进行比较(结果永远为false):
~~~
var a = 2 / "foo";
a == NaN; // false
a === NaN; // false
~~~
但可以使用内建的全局工具函数`isNaN(..) `来判断一个值是否是NaN。
~~~
var a = 2 / "foo";
isNaN( a ); // true
~~~
isNaN(..) 有一个严重的缺陷,它的检查方式过于死板,就是“检查参数是否不是NaN,也不是数字”。但是这样做的结果并不太准确:
~~~
var a = 2 / "foo";
var b = "foo";
a; // NaN
b; "foo"
window.isNaN( a ); // true
window.isNaN( b ); // true!
~~~
从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 不等于自身这个特点。NaN 是JavaScript 中**唯一**一个不等于自身的值。
于是我们可以这样:
~~~
if (!Number.isNaN) {
Number.isNaN = function(n) {
return n !== n;
};
}
~~~
很多JavaScript 程序都可能存在NaN 方面的问题,所以应该尽量使用`Number.isNaN(..)`这样可靠的方法,无论是系统内置还是polyfill。
* (2)无穷数
熟悉传统编译型语言(如C)的开发人员可能都遇到过编译错误(compiler error)或者运行时错误(runtime exception),例如“除以0”:
~~~
var a = 1 / 0;
~~~
然而在JavaScript 中上例的结果为`Infinity`(即`Number.POSITIVE_INfiNITY`)。同样:
~~~
var a = 1 / 0; // Infinity
var b = -1 / 0; // -Infinity
~~~
如果除法运算中的一个操作数为负数, 则结果为-Infinity( 即`Number.NEGATIVE_INfiNITY`)。
JavaScript 使用有限数字表示法(finite numeric representation),所以和纯粹的数学运算不同,JavaScript 的运算结果有可能溢出,此时结果为Infinity 或者-Infinity。
例如:
~~~
var a = Number.MAX_VALUE; // 1.7976931348623157e+308
a + a; // Infinity
a + Math.pow( 2, 970 ); // Infinity
a + Math.pow( 2, 969 ); // 1.7976931348623157e+308
~~~
计算结果一旦溢出为无穷数(infinity)就无法再得到有穷数。换句话说,就是可以从有穷走向无穷,但无法从无穷回到有穷。
从数学运算和JavaScript 语言的角度来说,`Infinity/Infinity` 是一个未定义操作,结果为`NaN`。有穷正数除以Infinity 结果是0。
* (3)零值
JavaScript 有一个常规的0(也叫作+0)和一个-0。
-0 除了可以用作常量以外,也可以是某些数学运算的返回值。例如:
~~~
var a = 0 / -3; // -0
var b = 0 * -3; // -0
~~~
加法和减法运算不会得到负零(negative zero)。
负零在开发调试控制台中通常显示为-0,但在一些老版本的浏览器中仍然会显示为0。根据规范,对负零进行字符串化会返回"0":
~~~
var a = 0 / -3;
a; // -0
// 但是规范定义的返回结果是这样!
a.toString(); // "0"
a + ""; // "0"
String( a ); // "0"
// JSON也如此,很奇怪
JSON.stringify( a ); // "0"
~~~
有意思的是,如果反过来将其从字符串转换为数字,得到的结果是准确的:
~~~
+"-0"; // -0
Number( "-0" ); // -0
JSON.parse( "-0" ); // -0
~~~
负零转换为字符串的结果令人费解,它的比较操作也是如此:
~~~
var a = 0;
var b = 0 / -3;
a == b; // true
-0 == 0; // true
a === b; // true
-0 === 0; // true
0 > -0; // false
a > b; // false
~~~
要区分-0 和0,不能仅仅依赖开发调试窗口的显示结果,还需要做一些特殊处理:
~~~
function isNegZero(n) {
n = Number( n );
return (n === 0) && (1 / n === -Infinity);
}
isNegZero( -0 ); // true
isNegZero( 0 / -3 ); // true
isNegZero( 0 ); // false
~~~
**4. 特殊等式**
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;
};
}
~~~
能使用`==` 和`===`时就尽量不要使用Object.is(..),因为前者效率更高、更为通用。`Object.is(..) `主要用来处理那些特殊的相等比较。
### 3.2.5 值和引用
JavaScript 中**没有指针**,引用的工作机制也不尽相同。在JavaScript 中变量不可能成为指向另一个变量的引用。
JavaScript 引用指向的是值。如果一个值有10 个引用,这些引用指向的都是同一个值,它们**相互之间没有引用/ 指向关系**。
JavaScript 对值和引用的赋值/ 传递在语法上没有区别,完全根据值的类型来决定。
~~~
var a = 2;
var b = a; // b是a的值的一个副本
b++;
a; // 2
b; // 3
var c = [1,2,3];
var d = c; // d是[1,2,3]的一个引用
d.push( 4 );
c; // [1,2,3,4]
d; // [1,2,3,4]
~~~
**简单值**(即标量基本类型值,scalar primitive)总是通过值复制的方式来赋值/ 传递,包括null、undefined、字符串、数字、布尔和ES6 中的symbol。
**复合值**(compound value)——对象(包括数组和封装对象)和函数,则总是通过引用复制的方式来赋值/ 传递。
由于引用指向的是值本身而非变量,所以一个引用无法更改另一个引用的指向。
~~~
var a = [1,2,3];
var b = a;
a; // [1,2,3]
b; // [1,2,3]
// 然后
b = [4,5,6];
a; // [1,2,3]
b; // [4,5,6]
~~~
b=[4,5,6] 并不影响a 指向值[1,2,3],除非b 不是指向数组的引用,而是指向a 的指针,但在JavaScript 中不存在这种情况!
函数参数就经常让人产生这样的困惑:
~~~
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]。在函数中我们可以通过引用x 来更改数组的值(push(4) 之后变为[1,2,3,4])。但x =[4,5,6] 并不影响a 的指向,所以a 仍然指向[1,2,3,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]
~~~
如果通过值复制的方式来传递复合值(如数组),就需要为其创建一个副本,这样传递的就不再是原始值。例如:
~~~
foo( a.slice() );
~~~
`slice(..) `不带参数会返回当前数组的一个浅复本(shallow copy)。由于传递给函数的是指向该复本的引用,所以foo(..) 中的操作不会影响a 指向的数组。
相反,如果要将标量基本类型值传递到函数内并进行更改,就需要将该值封装到一个复合值(对象、数组等)中,然后通过引用复制的方式传递。
~~~
function foo(wrapper) {
wrapper.a = 42;
}
var obj = {
a: 2
};
foo( obj );
obj.a; // 42
~~~
这里obj 是一个封装了标量基本类型值a 的封装对象。obj 引用的一个副本作为参数wrapper 被传递到foo(..) 中。这样就可以通过wrapper 来访问该对象并更改它的属性。函数执行结束后obj.a 将变成42。
- 前言
- 第一章 JavaScript简介
- 第三章 基本概念
- 3.1-3.3 语法、关键字和变量
- 3.4 数据类型
- 3.5-3.6 操作符、流控制语句(暂略)
- 3.7函数
- 第四章 变量的值、作用域与内存问题
- 第五章 引用类型
- 5.1 Object类型
- 5.2 Array类型
- 5.3 Date类型
- 5.4 基本包装类型
- 5.5 单体内置对象
- 第六章 面向对象的程序设计
- 6.1 理解对象
- 6.2 创建对象
- 6.3 继承
- 第七章 函数
- 7.1 函数概述
- 7.2 闭包
- 7.3 私有变量
- 第八章 BOM
- 8.1 window对象
- 8.2 location对象
- 8.3 navigator、screen与history对象
- 第九章 DOM
- 9.1 节点层次
- 9.2 DOM操作技术
- 9.3 DOM扩展
- 9.4 DOM2和DOM3
- 第十章 事件
- 10.1 事件流
- 10.2 事件处理程序
- 10.3 事件对象
- 10.4 事件类型
- 第十一章 JSON
- 11.1-11.2 语法与序列化选项
- 第十二章 正则表达式
- 12.1 创建正则表达式
- 12.2-12.3 模式匹配与RegExp对象
- 第十三章 Ajax
- 13.1 XMLHttpRequest对象
- 你不知道的JavaScript
- 一、作用域与闭包
- 1.1 作用域
- 1.2 词法作用域
- 1.3 函数作用域与块作用域
- 1.4 提升
- 1.5 作用域闭包
- 二、this与对象原型
- 2.1 关于this
- 2.2 全面解析this
- 2.3 对象
- 2.4 混合对象“类”
- 2.5 原型
- 2.6 行为委托
- 三、类型与语法
- 3.1 类型
- 3.2 值
- 3.3 原生函数