JSON 对象JSON 格式JSON 格式（JavaScript Object Notation 的缩写）是一种用于数据交换的文本格式，2001年由 Douglas Crockford 提出，目的是取代繁琐笨重的 XML 格式。相比 XML 格式，JSON 格式有两个显著的优点：书写简单，一目了然；符合 JavaScript 原生语法，可以由解释引擎直接处理，不用另外添加解析代码。所以，JSON 迅速被接受，已经成为各大网站交换数据的标准格式，并被写入标准。每个 JSON 对象就是一个值，可能是一个数组或对象，也可能是一个原始类型的值。总之，只能是一个值，不能是两个或更多的值。JSON 对值的类型和格式有严格的规定。复合类型的值只能是数组或对象，不能是函数、正则表达式对象、日期对象。原始类型的值只有四种：字符串、数值（必须以十进制表示）、布尔值和null（不能使用NaN, Infinity, -Infinity和undefined）。字符串必须使用双引号表示，不能使用单引号。对象的键名必须放在双引号里面。数组或对象最后一个成员的后面，不能加逗号。以下都是合法的 JSON。["one", "two", "three"] { "one": 1, "two": 2, "three": 3 } {"names": ["张三", "李四"] } [ { "name": "张三"}, {"name": "李四"} ]以下都是不合法的 JSON。{ name: "张三", 'age': 32 } // 属性名必须使用双引号 [32, 64, 128, 0xFFF] // 不能使用十六进制值 { "name": "张三", "age": undefined } // 不能使用 undefined { "name": "张三", "birthday": new Date('Fri, 26 Aug 2011 07:13:10 GMT'), "getName": function () { return this.name; } } // 属性值不能使用函数和日期对象注意，null、空数组和空对象都是合法的 JSON 值。JSON 对象JSON对象是 JavaScript 的原生对象，用来处理 JSON 格式数据。它有两个静态方法：JSON.stringify()和JSON.parse()。JSON.stringify()基本用法JSON.stringify()方法用于将一个值转为 JSON 字符串。该字符串符合 JSON 格式，并且可以被JSON.parse()方法还原。JSON.stringify('abc') // ""abc"" JSON.stringify(1) // "1" JSON.stringify(false) // "false" JSON.stringify([]) // "[]" JSON.stringify({}) // "{}" JSON.stringify([1, "false", false]) // '[1,"false",false]' JSON.stringify({ name: "张三" }) // '{"name":"张三"}'上面代码将各种类型的值，转成 JSON 字符串。注意，对于原始类型的字符串，转换结果会带双引号。JSON.stringify('foo') === "foo" // false JSON.stringify('foo') === "\"foo\"" // true上面代码中，字符串foo，被转成了"\"foo\""。这是因为将来还原的时候，内层双引号可以让 JavaScript 引擎知道，这是一个字符串，而不是其他类型的值。JSON.stringify(false) // "false" JSON.stringify('false') // "\"false\""上面代码中，如果不是内层的双引号，将来还原的时候，引擎就无法知道原始值是布尔值还是字符串。如果对象的属性是undefined、函数或 XML 对象，该属性会被JSON.stringify()过滤。var obj = { a: undefined, b: function () {} }; JSON.stringify(obj) // "{}"上面代码中，对象obj的a属性是undefined，而b属性是一个函数，结果都被JSON.stringify过滤。如果数组的成员是undefined、函数或 XML 对象，则这些值被转成null。var arr = [undefined, function () {}]; JSON.stringify(arr) // "[null,null]"上面代码中，数组arr的成员是undefined和函数，它们都被转成了null。正则对象会被转成空对象。JSON.stringify(/foo/) // "{}"JSON.stringify()方法会忽略对象的不可遍历的属性。var obj = {}; Object.defineProperties(obj, { 'foo': { value: 1, enumerable: true }, 'bar': { value: 2, enumerable: false } }); JSON.stringify(obj); // "{"foo":1}"上面代码中，bar是obj对象的不可遍历属性，JSON.stringify方法会忽略这个属性。第二个参数JSON.stringify()方法还可以接受一个数组，作为第二个参数，指定参数对象的哪些属性需要转成字符串。var obj = { 'prop1': 'value1', 'prop2': 'value2', 'prop3': 'value3' }; var selectedProperties = ['prop1', 'prop2']; JSON.stringify(obj, selectedProperties) // "{"prop1":"value1","prop2":"value2"}"上面代码中，JSON.stringify()方法的第二个参数指定，只转prop1和prop2两个属性。这个类似白名单的数组，只对对象的属性有效，对数组无效。JSON.stringify(['a', 'b'], ['0']) // "["a","b"]" JSON.stringify({0: 'a', 1: 'b'}, ['0']) // "{"0":"a"}"上面代码中，第二个参数指定 JSON 格式只转0号属性，实际上对数组是无效的，只对对象有效。第二个参数还可以是一个函数，用来更改JSON.stringify()的返回值。function f(key, value) { if (typeof value === "number") { value = 2 * value; } return value; } JSON.stringify({ a: 1, b: 2 }, f) // '{"a": 2,"b": 4}'上面代码中的f函数，接受两个参数，分别是被转换的对象的键名和键值。如果键值是数值，就将它乘以2，否则就原样返回。注意，这个处理函数是递归处理所有的键。var obj = {a: {b: 1}}; function f(key, value) { console.log("["+ key +"]:" + value); return value; } JSON.stringify(obj, f) // []:[object Object] // [a]:[object Object] // [b]:1 // '{"a":{"b":1}}'上面代码中，对象obj一共会被f函数处理三次，输出的最后那行是JSON.stringify()的默认输出。第一次键名为空，键值是整个对象obj；第二次键名为a，键值是{b: 1}；第三次键名为b，键值为1。递归处理中，每一次处理的对象，都是前一次返回的值。var obj = {a: 1}; function f(key, value) { if (typeof value === 'object') { return {b: 2}; } return value * 2; } JSON.stringify(obj, f) // "{"b": 4}"上面代码中，f函数修改了对象obj，接着JSON.stringify()方法就递归处理修改后的对象obj。如果处理函数返回undefined或没有返回值，则该属性会被忽略。function f(key, value) { if (typeof(value) === "string") { return undefined; } return value; } JSON.stringify({ a: "abc", b: 123 }, f) // '{"b": 123}'上面代码中，a属性经过处理后，返回undefined，于是该属性被忽略了。第三个参数JSON.stringify()还可以接受第三个参数，用于增加返回的 JSON 字符串的可读性。默认返回的是单行字符串，对于大型的 JSON 对象，可读性非常差。第三个参数使得每个属性单独占据一行，并且将每个属性前面添加指定的前缀（不超过10个字符）。// 默认输出 JSON.stringify({ p1: 1, p2: 2 }) // '{"p1":1,"p2":2}' // 分行输出 JSON.stringify({ p1: 1, p2: 2 }, null, '\t') // { // "p1": 1, // "p2": 2 // }上面例子中，第三个属性\t在每个属性前面添加一个制表符，然后分行显示。第三个属性如果是一个数字，则表示每个属性前面添加的空格（最多不超过10个）。JSON.stringify({ p1: 1, p2: 2 }, null, 2); /* "{ "p1": 1, "p2": 2 }" */参数对象的 toJSON() 方法如果参数对象有自定义的toJSON()方法，那么JSON.stringify()会使用这个方法的返回值作为参数，而忽略原对象的其他属性。下面是一个普通的对象。var user = { firstName: '三', lastName: '张', get fullName(){ return this.lastName + this.firstName; } }; JSON.stringify(user) // "{"firstName":"三","lastName":"张","fullName":"张三"}"现在，为这个对象加上toJSON()方法。var user = { firstName: '三', lastName: '张', get fullName(){ return this.lastName + this.firstName; }, toJSON: function () { return { name: this.lastName + this.firstName }; } }; JSON.stringify(user) // "{"name":"张三"}"上面代码中，JSON.stringify()发现参数对象有toJSON()方法，就直接使用这个方法的返回值作为参数，而忽略原对象的其他参数。Date对象就有一个自己的toJSON()方法。var date = new Date('2015-01-01'); date.toJSON() // "2015-01-01T00:00:00.000Z" JSON.stringify(date) // ""2015-01-01T00:00:00.000Z""上面代码中，JSON.stringify()发现处理的是Date对象实例，就会调用这个实例对象的toJSON()方法，将该方法的返回值作为参数。toJSON()方法的一个应用是，将正则对象自动转为字符串。因为JSON.stringify()默认不能转换正则对象，但是设置了toJSON()方法以后，就可以转换正则对象了。var obj = { reg: /foo/ }; // 不设置 toJSON 方法时 JSON.stringify(obj) // "{"reg":{}}" // 设置 toJSON 方法时 RegExp.prototype.toJSON = RegExp.prototype.toString; JSON.stringify(/foo/) // ""/foo/""上面代码在正则对象的原型上面部署了toJSON()方法，将其指向toString()方法，因此转换成 JSON 格式时，正则对象就先调用toJSON()方法转为字符串，然后再被JSON.stringify()方法处理。JSON.parse()JSON.parse()方法用于将 JSON 字符串转换成对应的值。JSON.parse('{}') // {} JSON.parse('true') // true JSON.parse('"foo"') // "foo" JSON.parse('[1, 5, "false"]') // [1, 5, "false"] JSON.parse('null') // null var o = JSON.parse('{"name": "张三"}'); o.name // 张三如果传入的字符串不是有效的 JSON 格式，JSON.parse()方法将报错。JSON.parse("'String'") // illegal single quotes // SyntaxError: Unexpected token ILLEGAL上面代码中，双引号字符串中是一个单引号字符串，因为单引号字符串不符合 JSON 格式，所以报错。为了处理解析错误，可以将JSON.parse()方法放在try...catch代码块中。try { JSON.parse("'String'"); } catch(e) { console.log('parsing error'); }JSON.parse()方法可以接受一个处理函数，作为第二个参数，用法与JSON.stringify()方法类似。function f(key, value) { if (key === 'a') { return value + 10; } return value; } JSON.parse('{"a": 1, "b": 2}', f) // {a: 11, b: 2}上面代码中，JSON.parse()的第二个参数是一个函数，如果键名是a，该函数会将键值加上10。参考链接MDN, Using native JSONMDN, JSON.parseDr. Axel Rauschmayer, JavaScript’s JSON APIJim Cowart, What You Might Not Know About JSON.stringify()Marco Rogers, What is JSON?
RegExp 对象RegExp对象提供正则表达式的功能。概述正则表达式（regular expression）是一种表达文本模式（即字符串结构）的方法，有点像字符串的模板，常常用来按照“给定模式”匹配文本。比如，正则表达式给出一个 Email 地址的模式，然后用它来确定一个字符串是否为 Email 地址。JavaScript 的正则表达式体系是参照 Perl 5 建立的。新建正则表达式有两种方法。一种是使用字面量，以斜杠表示开始和结束。var regex = /xyz/;另一种是使用RegExp构造函数。var regex = new RegExp('xyz');上面两种写法是等价的，都新建了一个内容为xyz的正则表达式对象。它们的主要区别是，第一种方法在引擎编译代码时，就会新建正则表达式，第二种方法在运行时新建正则表达式，所以前者的效率较高。而且，前者比较便利和直观，所以实际应用中，基本上都采用字面量定义正则表达式。RegExp构造函数还可以接受第二个参数，表示修饰符（详细解释见下文）。var regex = new RegExp('xyz', 'i'); // 等价于 var regex = /xyz/i;上面代码中，正则表达式/xyz/有一个修饰符i。实例属性正则对象的实例属性分成两类。一类是修饰符相关，用于了解设置了什么修饰符。RegExp.prototype.ignoreCase：返回一个布尔值，表示是否设置了i修饰符。RegExp.prototype.global：返回一个布尔值，表示是否设置了g修饰符。RegExp.prototype.multiline：返回一个布尔值，表示是否设置了m修饰符。RegExp.prototype.flags：返回一个字符串，包含了已经设置的所有修饰符，按字母排序。上面四个属性都是只读的。var r = /abc/igm; r.ignoreCase // true r.global // true r.multiline // true r.flags // 'gim'另一类是与修饰符无关的属性，主要是下面两个。RegExp.prototype.lastIndex：返回一个整数，表示下一次开始搜索的位置。该属性可读写，但是只在进行连续搜索时有意义，详细介绍请看后文。RegExp.prototype.source：返回正则表达式的字符串形式（不包括反斜杠），该属性只读。var r = /abc/igm; r.lastIndex // 0 r.source // "abc"实例方法RegExp.prototype.test()正则实例对象的test方法返回一个布尔值，表示当前模式是否能匹配参数字符串。/cat/.test('cats and dogs') // true上面代码验证参数字符串之中是否包含cat，结果返回true。如果正则表达式带有g修饰符，则每一次test方法都从上一次结束的位置开始向后匹配。var r = /x/g; var s = '_x_x'; r.lastIndex // 0 r.test(s) // true r.lastIndex // 2 r.test(s) // true r.lastIndex // 4 r.test(s) // false上面代码的正则表达式使用了g修饰符，表示是全局搜索，会有多个结果。接着，三次使用test方法，每一次开始搜索的位置都是上一次匹配的后一个位置。带有g修饰符时，可以通过正则对象的lastIndex属性指定开始搜索的位置。var r = /x/g; var s = '_x_x'; r.lastIndex = 4; r.test(s) // false r.lastIndex // 0 r.test(s) // true上面代码指定从字符串的第五个位置开始搜索，这个位置为空，所以返回false。同时，lastIndex属性重置为0，所以第二次执行r.test(s)会返回true。注意，带有g修饰符时，正则表达式内部会记住上一次的lastIndex属性，这时不应该更换所要匹配的字符串，否则会有一些难以察觉的错误。var r = /bb/g; r.test('bb') // true r.test('-bb-') // false上面代码中，由于正则表达式r是从上一次的lastIndex位置开始匹配，导致第二次执行test方法时出现预期以外的结果。lastIndex属性只对同一个正则表达式有效，所以下面这样写是错误的。var count = 0; while (/a/g.test('babaa')) count++;上面代码会导致无限循环，因为while循环的每次匹配条件都是一个新的正则表达式，导致lastIndex属性总是等于0。如果正则模式是一个空字符串，则匹配所有字符串。new RegExp('').test('abc') // trueRegExp.prototype.exec()正则实例对象的exec()方法，用来返回匹配结果。如果发现匹配，就返回一个数组，成员是匹配成功的子字符串，否则返回null。var s = '_x_x'; var r1 = /x/; var r2 = /y/; r1.exec(s) // ["x"] r2.exec(s) // null上面代码中，正则对象r1匹配成功，返回一个数组，成员是匹配结果；正则对象r2匹配失败，返回null。如果正则表示式包含圆括号（即含有“组匹配”），则返回的数组会包括多个成员。第一个成员是整个匹配成功的结果，后面的成员就是圆括号对应的匹配成功的组。也就是说，第二个成员对应第一个括号，第三个成员对应第二个括号，以此类推。整个数组的length属性等于组匹配的数量再加1。var s = '_x_x'; var r = /_(x)/; r.exec(s) // ["_x", "x"]上面代码的exec()方法，返回一个数组。第一个成员是整个匹配的结果，第二个成员是圆括号匹配的结果。exec()方法的返回数组还包含以下两个属性：input：整个原字符串。index：模式匹配成功的开始位置（从0开始计数）。var r = /a(b+)a/; var arr = r.exec('_abbba_aba_'); arr // ["abbba", "bbb"] arr.index // 1 arr.input // "_abbba_aba_"上面代码中的index属性等于1，是因为从原字符串的第二个位置开始匹配成功。如果正则表达式加上g修饰符，则可以使用多次exec()方法，下一次搜索的位置从上一次匹配成功结束的位置开始。var reg = /a/g; var str = 'abc_abc_abc' var r1 = reg.exec(str); r1 // ["a"] r1.index // 0 reg.lastIndex // 1 var r2 = reg.exec(str); r2 // ["a"] r2.index // 4 reg.lastIndex // 5 var r3 = reg.exec(str); r3 // ["a"] r3.index // 8 reg.lastIndex // 9 var r4 = reg.exec(str); r4 // null reg.lastIndex // 0上面代码连续用了四次exec()方法，前三次都是从上一次匹配结束的位置向后匹配。当第三次匹配结束以后，整个字符串已经到达尾部，匹配结果返回null，正则实例对象的lastIndex属性也重置为0，意味着第四次匹配将从头开始。利用g修饰符允许多次匹配的特点，可以用一个循环完成全部匹配。var reg = /a/g; var str = 'abc_abc_abc' while(true) { var match = reg.exec(str); if (!match) break; console.log('#' + match.index + ':' + match[0]); } // #0:a // #4:a // #8:a上面代码中，只要exec()方法不返回null，就会一直循环下去，每次输出匹配的位置和匹配的文本。正则实例对象的lastIndex属性不仅可读，还可写。设置了g修饰符的时候，只要手动设置了lastIndex的值，就会从指定位置开始匹配。字符串的实例方法字符串的实例方法之中，有4种与正则表达式有关。String.prototype.match()：返回一个数组，成员是所有匹配的子字符串。String.prototype.search()：按照给定的正则表达式进行搜索，返回一个整数，表示匹配开始的位置。String.prototype.replace()：按照给定的正则表达式进行替换，返回替换后的字符串。String.prototype.split()：按照给定规则进行字符串分割，返回一个数组，包含分割后的各个成员。String.prototype.match()字符串实例对象的match方法对字符串进行正则匹配，返回匹配结果。var s = '_x_x'; var r1 = /x/; var r2 = /y/; s.match(r1) // ["x"] s.match(r2) // null从上面代码可以看到，字符串的match方法与正则对象的exec方法非常类似：匹配成功返回一个数组，匹配失败返回null。如果正则表达式带有g修饰符，则该方法与正则对象的exec方法行为不同，会一次性返回所有匹配成功的结果。var s = 'abba'; var r = /a/g; s.match(r) // ["a", "a"] r.exec(s) // ["a"]设置正则表达式的lastIndex属性，对match方法无效，匹配总是从字符串的第一个字符开始。var r = /a|b/g; r.lastIndex = 7; 'xaxb'.match(r) // ['a', 'b'] r.lastIndex // 0上面代码表示，设置正则对象的lastIndex属性是无效的。String.prototype.search()字符串对象的search方法，返回第一个满足条件的匹配结果在整个字符串中的位置。如果没有任何匹配，则返回-1。'_x_x'.search(/x/) // 1上面代码中，第一个匹配结果出现在字符串的1号位置。String.prototype.replace()字符串对象的replace方法可以替换匹配的值。它接受两个参数，第一个是正则表达式，表示搜索模式，第二个是替换的内容。str.replace(search, replacement)正则表达式如果不加g修饰符，就替换第一个匹配成功的值，否则替换所有匹配成功的值。'aaa'.replace('a', 'b') // "baa" 'aaa'.replace(/a/, 'b') // "baa" 'aaa'.replace(/a/g, 'b') // "bbb"上面代码中，最后一个正则表达式使用了g修饰符，导致所有的a都被替换掉了。replace方法的一个应用，就是消除字符串首尾两端的空格。var str = ' #id div.class '; str.replace(/^\s+|\s+$/g, '') // "#id div.class"replace方法的第二个参数可以使用美元符号$，用来指代所替换的内容。$&：匹配的子字符串。$` ：匹配结果前面的文本。$'：匹配结果后面的文本。$n：匹配成功的第n组内容，n是从1开始的自然数。$$：指代美元符号$。'hello world'.replace(/(\w+)\s(\w+)/, '$2 $1') // "world hello" 'abc'.replace('b', '[$`-$&-$\']') // "a[a-b-c]c"上面代码中，第一个例子是将匹配的组互换位置，第二个例子是改写匹配的值。replace方法的第二个参数还可以是一个函数，将每一个匹配内容替换为函数返回值。'3 and 5'.replace(/[0-9]+/g, function (match) { return 2 * match; }) // "6 and 10" var a = 'The quick brown fox jumped over the lazy dog.'; var pattern = /quick|brown|lazy/ig; a.replace(pattern, function replacer(match) { return match.toUpperCase(); }); // The QUICK BROWN fox jumped over the LAZY dog.作为replace方法第二个参数的替换函数，可以接受多个参数。其中，第一个参数是捕捉到的内容，第二个参数是捕捉到的组匹配（有多少个组匹配，就有多少个对应的参数）。此外，最后还可以添加两个参数，倒数第二个参数是捕捉到的内容在整个字符串中的位置（比如从第五个位置开始），最后一个参数是原字符串。下面是一个网页模板替换的例子。var prices = { 'p1': '$1.99', 'p2': '$9.99', 'p3': '$5.00' }; var template = '' + '' + ''; template.replace( /()(<\/span>)/g, function(match, $1, $2, $3, $4){ return $1 + $2 + $3 + prices[$2] + $4; } ); // "$1.99$9.99$5.00"上面代码的捕捉模式中，有四个括号，所以会产生四个组匹配，在匹配函数中用$1到$4表示。匹配函数的作用是将价格插入模板中。String.prototype.split()字符串对象的split方法按照正则规则分割字符串，返回一个由分割后的各个部分组成的数组。str.split(separator, [limit])该方法接受两个参数，第一个参数是正则表达式，表示分隔规则，第二个参数是返回数组的最大成员数。// 非正则分隔 'a, b,c, d'.split(',') // [ 'a', ' b', 'c', ' d' ] // 正则分隔，去除多余的空格 'a, b,c, d'.split(/, */) // [ 'a', 'b', 'c', 'd' ] // 指定返回数组的最大成员 'a, b,c, d'.split(/, */, 2) [ 'a', 'b' ]上面代码使用正则表达式，去除了子字符串的逗号后面的空格。// 例一 'aaa*a*'.split(/a*/) // [ '', '*', '*' ] // 例二 'aaa**a*'.split(/a*/) // ["", "*", "*", "*"]上面代码的分割规则是0次或多次的a，由于正则默认是贪婪匹配，所以例一的第一个分隔符是aaa，第二个分割符是a，将字符串分成三个部分，包含开始处的空字符串。例二的第一个分隔符是aaa，第二个分隔符是0个a（即空字符），第三个分隔符是a，所以将字符串分成四个部分。如果正则表达式带有括号，则括号匹配的部分也会作为数组成员返回。'aaa*a*'.split(/(a*)/) // [ '', 'aaa', '*', 'a', '*' ]上面代码的正则表达式使用了括号，第一个组匹配是aaa，第二个组匹配是a，它们都作为数组成员返回。匹配规则正则表达式的规则很复杂，下面一一介绍这些规则。字面量字符和元字符大部分字符在正则表达式中，就是字面的含义，比如/a/匹配a，/b/匹配b。如果在正则表达式之中，某个字符只表示它字面的含义（就像前面的a和b），那么它们就叫做“字面量字符”（literal characters）。/dog/.test('old dog') // true上面代码中正则表达式的dog，就是字面量字符，所以/dog/匹配old dog，因为它就表示d、o、g三个字母连在一起。除了字面量字符以外，还有一部分字符有特殊含义，不代表字面的意思。它们叫做“元字符”（metacharacters），主要有以下几个。（1）点字符（.)点字符（.）匹配除回车（\r）、换行(\n) 、行分隔符（\u2028）和段分隔符（\u2029）以外的所有字符。注意，对于码点大于0xFFFF字符，点字符不能正确匹配，会认为这是两个字符。/c.t/上面代码中，c.t匹配c和t之间包含任意一个字符的情况，只要这三个字符在同一行，比如cat、c2t、c-t等等，但是不匹配coot。（2）位置字符位置字符用来提示字符所处的位置，主要有两个字符。^ 表示字符串的开始位置$ 表示字符串的结束位置// test必须出现在开始位置 /^test/.test('test123') // true // test必须出现在结束位置 /test$/.test('new test') // true // 从开始位置到结束位置只有test /^test$/.test('test') // true /^test$/.test('test test') // false（3）选择符（|）竖线符号（|）在正则表达式中表示“或关系”（OR），即cat|dog表示匹配cat或dog。/11|22/.test('911') // true上面代码中，正则表达式指定必须匹配11或22。多个选择符可以联合使用。// 匹配fred、barney、betty之中的一个 /fred|barney|betty/选择符会包括它前后的多个字符，比如/ab|cd/指的是匹配ab或者cd，而不是指匹配b或者c。如果想修改这个行为，可以使用圆括号。/a( |\t)b/.test('a\tb') // true上面代码指的是，a和b之间有一个空格或者一个制表符。其他的元字符还包括\、*、+、?、()、[]、{}等，将在下文解释。转义符正则表达式中那些有特殊含义的元字符，如果要匹配它们本身，就需要在它们前面要加上反斜杠。比如要匹配+，就要写成\+。/1+1/.test('1+1') // false /1\+1/.test('1+1') // true上面代码中，第一个正则表达式之所以不匹配，因为加号是元字符，不代表自身。第二个正则表达式使用反斜杠对加号转义，就能匹配成功。正则表达式中，需要反斜杠转义的，一共有12个字符：^、.、[、$、(、)、|、*、+、?、{和\。需要特别注意的是，如果使用RegExp方法生成正则对象，转义需要使用两个斜杠，因为字符串内部会先转义一次。(new RegExp('1\+1')).test('1+1') // false (new RegExp('1\\+1')).test('1+1') // true上面代码中，RegExp作为构造函数，参数是一个字符串。但是，在字符串内部，反斜杠也是转义字符，所以它会先被反斜杠转义一次，然后再被正则表达式转义一次，因此需要两个反斜杠转义。特殊字符正则表达式对一些不能打印的特殊字符，提供了表达方法。\cX 表示Ctrl-[X]，其中的X是A-Z之中任一个英文字母，用来匹配控制字符。[\b] 匹配退格键(U+0008)，不要与\b混淆。\n 匹配换行键。\r 匹配回车键。\t 匹配制表符 tab（U+0009）。\v 匹配垂直制表符（U+000B）。\f 匹配换页符（U+000C）。\0 匹配null字符（U+0000）。\xhh 匹配一个以两位十六进制数（\x00-\xFF）表示的字符。\uhhhh 匹配一个以四位十六进制数（\u0000-\uFFFF）表示的 Unicode 字符。字符类字符类（class）表示有一系列字符可供选择，只要匹配其中一个就可以了。所有可供选择的字符都放在方括号内，比如[xyz] 表示x、y、z之中任选一个匹配。/[abc]/.test('hello world') // false /[abc]/.test('apple') // true上面代码中，字符串hello world不包含a、b、c这三个字母中的任一个，所以返回false；字符串apple包含字母a，所以返回true。有两个字符在字符类中有特殊含义。（1）脱字符（^）如果方括号内的第一个字符是[^]，则表示除了字符类之中的字符，其他字符都可以匹配。比如，[^xyz]表示除了x、y、z之外都可以匹配。/[^abc]/.test('bbc news') // true /[^abc]/.test('bbc') // false上面代码中，字符串bbc news包含a、b、c以外的其他字符，所以返回true；字符串bbc不包含a、b、c以外的其他字符，所以返回false。如果方括号内没有其他字符，即只有[^]，就表示匹配一切字符，其中包括换行符。相比之下，点号作为元字符（.）是不包括换行符的。var s = 'Please yes\nmake my day!'; s.match(/yes.*day/) // null s.match(/yes[^]*day/) // [ 'yes\nmake my day']上面代码中，字符串s含有一个换行符，点号不包括换行符，所以第一个正则表达式匹配失败；第二个正则表达式[^]包含一切字符，所以匹配成功。注意，脱字符只有在字符类的第一个位置才有特殊含义，否则就是字面含义。（2）连字符（-）某些情况下，对于连续序列的字符，连字符（-）用来提供简写形式，表示字符的连续范围。比如，[abc]可以写成[a-c]，[0123456789]可以写成[0-9]，同理[A-Z]表示26个大写字母。/a-z/.test('b') // false /[a-z]/.test('b') // true上面代码中，当连字号（dash）不出现在方括号之中，就不具备简写的作用，只代表字面的含义，所以不匹配字符b。只有当连字号用在方括号之中，才表示连续的字符序列。以下都是合法的字符类简写形式。[0-9.,] [0-9a-fA-F] [a-zA-Z0-9-] [1-31]上面代码中最后一个字符类[1-31]，不代表1到31，只代表1到3。连字符还可以用来指定 Unicode 字符的范围。var str = "\u0130\u0131\u0132"; /[\u0128-\uFFFF]/.test(str) // true上面代码中，\u0128-\uFFFF表示匹配码点在0128到FFFF之间的所有字符。另外，不要过分使用连字符，设定一个很大的范围，否则很可能选中意料之外的字符。最典型的例子就是[A-z]，表面上它是选中从大写的A到小写的z之间52个字母，但是由于在 ASCII 编码之中，大写字母与小写字母之间还有其他字符，结果就会出现意料之外的结果。/[A-z]/.test('\\') // true上面代码中，由于反斜杠（'\'）的ASCII码在大写字母与小写字母之间，结果会被选中。预定义模式预定义模式指的是某些常见模式的简写方式。\d 匹配0-9之间的任一数字，相当于[0-9]。\D 匹配所有0-9以外的字符，相当于[^0-9]。\w 匹配任意的字母、数字和下划线，相当于[A-Za-z0-9_]。\W 除所有字母、数字和下划线以外的字符，相当于[^A-Za-z0-9_]。\s 匹配空格（包括换行符、制表符、空格符等），相等于[ \t\r\n\v\f]。\S 匹配非空格的字符，相当于[^ \t\r\n\v\f]。\b 匹配词的边界。\B 匹配非词边界，即在词的内部。下面是一些例子。// \s 的例子 /\s\w*/.exec('hello world') // [" world"] // \b 的例子 /\bworld/.test('hello world') // true /\bworld/.test('hello-world') // true /\bworld/.test('helloworld') // false // \B 的例子 /\Bworld/.test('hello-world') // false /\Bworld/.test('helloworld') // true上面代码中，\s表示空格，所以匹配结果会包括空格。\b表示词的边界，所以world的词首必须独立（词尾是否独立未指定），才会匹配。同理，\B表示非词的边界，只有world的词首不独立，才会匹配。通常，正则表达式遇到换行符（\n）就会停止匹配。var html = "Hello\nworld!"; /.*/.exec(html)[0] // "Hello"上面代码中，字符串html包含一个换行符，结果点字符（.）不匹配换行符，导致匹配结果可能不符合原意。这时使用\s字符类，就能包括换行符。var html = "Hello\nworld!"; /[\S\s]*/.exec(html)[0] // "Hello\nworld!"上面代码中，[\S\s]指代一切字符。重复类模式的精确匹配次数，使用大括号（{}）表示。{n}表示恰好重复n次，{n,}表示至少重复n次，{n,m}表示重复不少于n次，不多于m次。/lo{2}k/.test('look') // true /lo{2,5}k/.test('looook') // true上面代码中，第一个模式指定o连续出现2次，第二个模式指定o连续出现2次到5次之间。量词符量词符用来设定某个模式出现的次数。? 问号表示某个模式出现0次或1次，等同于{0, 1}。* 星号表示某个模式出现0次或多次，等同于{0,}。+ 加号表示某个模式出现1次或多次，等同于{1,}。// t 出现0次或1次 /t?est/.test('test') // true /t?est/.test('est') // true // t 出现1次或多次 /t+est/.test('test') // true /t+est/.test('ttest') // true /t+est/.test('est') // false // t 出现0次或多次 /t*est/.test('test') // true /t*est/.test('ttest') // true /t*est/.test('tttest') // true /t*est/.test('est') // true贪婪模式上一小节的三个量词符，默认情况下都是最大可能匹配，即匹配到下一个字符不满足匹配规则为止。这被称为贪婪模式。var s = 'aaa'; s.match(/a+/) // ["aaa"]上面代码中，模式是/a+/，表示匹配1个a或多个a，那么到底会匹配几个a呢？因为默认是贪婪模式，会一直匹配到字符a不出现为止，所以匹配结果是3个a。除了贪婪模式，还有非贪婪模式，即最小可能匹配。只要一发现匹配，就返回结果，不要往下检查。如果想将贪婪模式改为非贪婪模式，可以在量词符后面加一个问号。var s = 'aaa'; s.match(/a+?/) // ["a"]上面例子中，模式结尾添加了一个问号/a+?/，这时就改为非贪婪模式，一旦条件满足，就不再往下匹配，+?表示只要发现一个a，就不再往下匹配了。除了非贪婪模式的加号（+?），还有非贪婪模式的星号（*?）和非贪婪模式的问号（??）。+?：表示某个模式出现1次或多次，匹配时采用非贪婪模式。*?：表示某个模式出现0次或多次，匹配时采用非贪婪模式。??：表格某个模式出现0次或1次，匹配时采用非贪婪模式。'abb'.match(/ab*/) // ["abb"] 'abb'.match(/ab*?/) // ["a"] 'abb'.match(/ab?/) // ["ab"] 'abb'.match(/ab??/) // ["a"]上面例子中，/ab*/表示如果a后面有多个b，那么匹配尽可能多的b；/ab*?/表示匹配尽可能少的b，也就是0个b。修饰符修饰符（modifier）表示模式的附加规则，放在正则模式的最尾部。修饰符可以单个使用，也可以多个一起使用。// 单个修饰符 var regex = /test/i; // 多个修饰符 var regex = /test/ig;（1）g 修饰符默认情况下，第一次匹配成功后，正则对象就停止向下匹配了。g修饰符表示全局匹配（global），加上它以后，正则对象将匹配全部符合条件的结果，主要用于搜索和替换。var regex = /b/; var str = 'abba'; regex.test(str); // true regex.test(str); // true regex.test(str); // true上面代码中，正则模式不含g修饰符，每次都是从字符串头部开始匹配。所以，连续做了三次匹配，都返回true。var regex = /b/g; var str = 'abba'; regex.test(str); // true regex.test(str); // true regex.test(str); // false上面代码中，正则模式含有g修饰符，每次都是从上一次匹配成功处，开始向后匹配。因为字符串abba只有两个b，所以前两次匹配结果为true，第三次匹配结果为false。（2）i 修饰符默认情况下，正则对象区分字母的大小写，加上i修饰符以后表示忽略大小写（ignoreCase）。/abc/.test('ABC') // false /abc/i.test('ABC') // true上面代码表示，加了i修饰符以后，不考虑大小写，所以模式abc匹配字符串ABC。（3）m 修饰符m修饰符表示多行模式（multiline），会修改^和$的行为。默认情况下（即不加m修饰符时），^和$匹配字符串的开始处和结尾处，加上m修饰符以后，^和$还会匹配行首和行尾，即^和$会识别换行符（\n）。/world$/.test('hello world\n') // false /world$/m.test('hello world\n') // true上面的代码中，字符串结尾处有一个换行符。如果不加m修饰符，匹配不成功，因为字符串的结尾不是world；加上以后，$可以匹配行尾。/^b/m.test('a\nb') // true上面代码要求匹配行首的b，如果不加m修饰符，就相当于b只能处在字符串的开始处。加上m修饰符以后，换行符\n也会被认为是一行的开始。组匹配（1）概述正则表达式的括号表示分组匹配，括号中的模式可以用来匹配分组的内容。/fred+/.test('fredd') // true /(fred)+/.test('fredfred') // true上面代码中，第一个模式没有括号，结果+只表示重复字母d，第二个模式有括号，结果+就表示匹配fred这个词。下面是另外一个分组捕获的例子。var m = 'abcabc'.match(/(.)b(.)/); m // ['abc', 'a', 'c']上面代码中，正则表达式/(.)b(.)/一共使用两个括号，第一个括号捕获a，第二个括号捕获c。注意，使用组匹配时，不宜同时使用g修饰符，否则match方法不会捕获分组的内容。var m = 'abcabc'.match(/(.)b(.)/g); m // ['abc', 'abc']上面代码使用带g修饰符的正则表达式，结果match方法只捕获了匹配整个表达式的部分。这时必须使用正则表达式的exec方法，配合循环，才能读到每一轮匹配的组捕获。var str = 'abcabc'; var reg = /(.)b(.)/g; while (true) { var result = reg.exec(str); if (!result) break; console.log(result); } // ["abc", "a", "c"] // ["abc", "a", "c"]正则表达式内部，还可以用\n引用括号匹配的内容，n是从1开始的自然数，表示对应顺序的括号。/(.)b(.)\1b\2/.test("abcabc") // true上面的代码中，\1表示第一个括号匹配的内容（即a），\2表示第二个括号匹配的内容（即c）。下面是另外一个例子。/y(..)(.)\2\1/.test('yabccab') // true括号还可以嵌套。/y((..)\2)\1/.test('yabababab') // true上面代码中，\1指向外层括号，\2指向内层括号。组匹配非常有用，下面是一个匹配网页标签的例子。var tagName = /<([^>]+)>[^<]*<\/\1>/; tagName.exec("bold")[1] // 'b'上面代码中，圆括号匹配尖括号之中的标签，而\1就表示对应的闭合标签。上面代码略加修改，就能捕获带有属性的标签。var html = 'Helloworld'; var tag = /<(\w+)([^>]*)>(.*?)<\/\1>/g; var match = tag.exec(html); match[1] // "b" match[2] // " class="hello"" match[3] // "Hello" match = tag.exec(html); match[1] // "i" match[2] // "" match[3] // "world"注意：当(.*?)的右边仍然有匹配内容时，那么(.*?)所匹配的内容为到右边匹配规则出现截止的最小匹配内容。var html = 'Hello'; var tag = /<(\w+)([^>]*)>(.*?)/; //(.*?)右边没有匹配规则时，不匹配。 var match = tag.exec(html); undefined match[1] 'b' match[2] ' class="hello"' match[3] '' var html = 'Hello>'; var tag = /<(\w+)([^>]*)>(.*?)>/; //(.*?)右边有匹配规则时，(.*?)截取最小匹配内容。 var match = tag.exec(html); undefined match[1] 'b' match[2] ' class="hello"' match[3] 'Hello'（2）非捕获组(?:x)称为非捕获组（Non-capturing group），表示不返回该组匹配的内容，即匹配的结果中不计入这个括号。非捕获组的作用请考虑这样一个场景，假定需要匹配foo或者foofoo，正则表达式就应该写成/(foo){1, 2}/，但是这样会占用一个组匹配。这时，就可以使用非捕获组，将正则表达式改为/(?:foo){1, 2}/，它的作用与前一个正则是一样的，但是不会单独输出括号内部的内容。请看下面的例子。var m = 'abc'.match(/(?:.)b(.)/); m // ["abc", "c"]上面代码中的模式，一共使用了两个括号。其中第一个括号是非捕获组，所以最后返回的结果中没有第一个括号，只有第二个括号匹配的内容。下面是用来分解网址的正则表达式。// 正常匹配 var url = /(http|ftp):\/\/([^/\r\n]+)(\/[^\r\n]*)?/; url.exec('http://google.com/'); // ["http://google.com/", "http", "google.com", "/"] // 非捕获组匹配 var url = /(?:http|ftp):\/\/([^/\r\n]+)(\/[^\r\n]*)?/; url.exec('http://google.com/'); // ["http://google.com/", "google.com", "/"]上面的代码中，前一个正则表达式是正常匹配，第一个括号返回网络协议；后一个正则表达式是非捕获匹配，返回结果中不包括网络协议。（3）先行断言x(?=y)称为先行断言（Positive look-ahead），x只有在y前面才匹配，y不会被计入返回结果。比如，要匹配后面跟着百分号的数字，可以写成/\d+(?=%)/。“先行断言”中，括号里的部分是不会返回的。var m = 'abc'.match(/b(?=c)/); m // ["b"]上面的代码使用了先行断言，b在c前面所以被匹配，但是括号对应的c不会被返回。（4）先行否定断言x(?!y)称为先行否定断言（Negative look-ahead），x只有不在y前面才匹配，y不会被计入返回结果。比如，要匹配后面跟的不是百分号的数字，就要写成/\d+(?!%)/。/\d+(?!\.)/.exec('3.14') // ["14"]上面代码中，正则表达式指定，只有不在小数点前面的数字才会被匹配，因此返回的结果就是14。“先行否定断言”中，括号里的部分是不会返回的。var m = 'abd'.match(/b(?!c)/); m // ['b']上面的代码使用了先行否定断言，b不在c前面所以被匹配，而且括号对应的d不会被返回。参考链接Axel Rauschmayer, JavaScript: an overview of the regular expression APIMozilla Developer Network, Regular ExpressionsAxel Rauschmayer, The flag /g of JavaScript’s regular expressionsSam Hughes, Learn regular expressions in about 55 minutes
Date 对象Date对象是 JavaScript 原生的时间库。它以国际标准时间（UTC）1970年1月1日00:00:00作为时间的零点，可以表示的时间范围是前后各1亿天（单位为毫秒）。普通函数的用法Date对象可以作为普通函数直接调用，返回一个代表当前时间的字符串。Date() // "Tue Dec 01 2015 09:34:43 GMT+0800 (CST)"注意，即使带有参数，Date作为普通函数使用时，返回的还是当前时间。Date(2000, 1, 1) // "Tue Dec 01 2015 09:34:43 GMT+0800 (CST)"上面代码说明，无论有没有参数，直接调用Date总是返回当前时间。构造函数的用法Date还可以当作构造函数使用。对它使用new命令，会返回一个Date对象的实例。如果不加参数，实例代表的就是当前时间。var today = new Date();Date实例有一个独特的地方。其他对象求值的时候，都是默认调用.valueOf()方法，但是Date实例求值的时候，默认调用的是toString()方法。这导致对Date实例求值，返回的是一个字符串，代表该实例对应的时间。var today = new Date(); today // "Tue Dec 01 2015 09:34:43 GMT+0800 (CST)" // 等同于 today.toString() // "Tue Dec 01 2015 09:34:43 GMT+0800 (CST)"上面代码中，today是Date的实例，直接求值等同于调用toString方法。作为构造函数时，Date对象可以接受多种格式的参数，返回一个该参数对应的时间实例。// 参数为时间零点开始计算的毫秒数 new Date(1378218728000) // Tue Sep 03 2013 22:32:08 GMT+0800 (CST) // 参数为日期字符串 new Date('January 6, 2013'); // Sun Jan 06 2013 00:00:00 GMT+0800 (CST) // 参数为多个整数， // 代表年、月、日、小时、分钟、秒、毫秒 new Date(2013, 0, 1, 0, 0, 0, 0) // Tue Jan 01 2013 00:00:00 GMT+0800 (CST)关于Date构造函数的参数，有几点说明。第一点，参数可以是负整数，代表1970年元旦之前的时间。new Date(-1378218728000) // Fri Apr 30 1926 17:27:52 GMT+0800 (CST)第二点，只要是能被Date.parse()方法解析的字符串，都可以当作参数。new Date('2013-2-15') new Date('2013/2/15') new Date('02/15/2013') new Date('2013-FEB-15') new Date('FEB, 15, 2013') new Date('FEB 15, 2013') new Date('February, 15, 2013') new Date('February 15, 2013') new Date('15 Feb 2013') new Date('15, February, 2013') // Fri Feb 15 2013 00:00:00 GMT+0800 (CST)上面多种日期字符串的写法，返回的都是同一个时间。第三，参数为年、月、日等多个整数时，年和月是不能省略的，其他参数都可以省略的。也就是说，这时至少需要两个参数，因为如果只使用“年”这一个参数，Date会将其解释为毫秒数。new Date(2013) // Thu Jan 01 1970 08:00:02 GMT+0800 (CST)上面代码中，2013被解释为毫秒数，而不是年份。new Date(2013, 0) // Tue Jan 01 2013 00:00:00 GMT+0800 (CST) new Date(2013, 0, 1) // Tue Jan 01 2013 00:00:00 GMT+0800 (CST) new Date(2013, 0, 1, 0) // Tue Jan 01 2013 00:00:00 GMT+0800 (CST) new Date(2013, 0, 1, 0, 0, 0, 0) // Tue Jan 01 2013 00:00:00 GMT+0800 (CST)上面代码中，不管有几个参数，返回的都是2013年1月1日零点。最后，各个参数的取值范围如下。年：使用四位数年份，比如2000。如果写成两位数或个位数，则加上1900，即10代表1910年。如果是负数，表示公元前。月：0表示一月，依次类推，11表示12月。日：1到31。小时：0到23。分钟：0到59。秒：0到59毫秒：0到999。注意，月份从0开始计算，但是，天数从1开始计算。另外，除了日期的默认值为1，小时、分钟、秒钟和毫秒的默认值都是0。这些参数如果超出了正常范围，会被自动折算。比如，如果月设为15，就折算为下一年的4月。new Date(2013, 15) // Tue Apr 01 2014 00:00:00 GMT+0800 (CST) new Date(2013, 0, 0) // Mon Dec 31 2012 00:00:00 GMT+0800 (CST)上面代码的第二个例子，日期设为0，就代表上个月的最后一天。参数还可以使用负数，表示扣去的时间。new Date(2013, -1) // Sat Dec 01 2012 00:00:00 GMT+0800 (CST) new Date(2013, 0, -1) // Sun Dec 30 2012 00:00:00 GMT+0800 (CST)上面代码中，分别对月和日使用了负数，表示从基准日扣去相应的时间。日期的运算类型自动转换时，Date实例如果转为数值，则等于对应的毫秒数；如果转为字符串，则等于对应的日期字符串。所以，两个日期实例对象进行减法运算时，返回的是它们间隔的毫秒数；进行加法运算时，返回的是两个字符串连接而成的新字符串。var d1 = new Date(2000, 2, 1); var d2 = new Date(2000, 3, 1); d2 - d1 // 2678400000 d2 + d1 // "Sat Apr 01 2000 00:00:00 GMT+0800 (CST)Wed Mar 01 2000 00:00:00 GMT+0800 (CST)"静态方法Date.now()Date.now方法返回当前时间距离时间零点（1970年1月1日 00:00:00 UTC）的毫秒数，相当于 Unix 时间戳乘以1000。Date.now() // 1364026285194Date.parse()Date.parse方法用来解析日期字符串，返回该时间距离时间零点（1970年1月1日 00:00:00）的毫秒数。日期字符串应该符合 RFC 2822 和 ISO 8061 这两个标准，即YYYY-MM-DDTHH:mm:ss.sssZ格式，其中最后的Z表示时区。但是，其他格式也可以被解析，请看下面的例子。Date.parse('Aug 9, 1995') Date.parse('January 26, 2011 13:51:50') Date.parse('Mon, 25 Dec 1995 13:30:00 GMT') Date.parse('Mon, 25 Dec 1995 13:30:00 +0430') Date.parse('2011-10-10') Date.parse('2011-10-10T14:48:00')上面的日期字符串都可以解析。如果解析失败，返回NaN。Date.parse('xxx') // NaNDate.UTC()Date.UTC方法接受年、月、日等变量作为参数，返回该时间距离时间零点（1970年1月1日 00:00:00 UTC）的毫秒数。// 格式 Date.UTC(year, month[, date[, hrs[, min[, sec[, ms]]]]]) // 用法 Date.UTC(2011, 0, 1, 2, 3, 4, 567) // 1293847384567该方法的参数用法与Date构造函数完全一致，比如月从0开始计算，日期从1开始计算。区别在于Date.UTC方法的参数，会被解释为 UTC 时间（世界标准时间），Date构造函数的参数会被解释为当前时区的时间。实例方法Date的实例对象，有几十个自己的方法，除了valueOf和toString，可以分为以下三类。to类：从Date对象返回一个字符串，表示指定的时间。get类：获取Date对象的日期和时间。set类：设置Date对象的日期和时间。Date.prototype.valueOf()valueOf方法返回实例对象距离时间零点（1970年1月1日00:00:00 UTC）对应的毫秒数，该方法等同于getTime方法。var d = new Date(); d.valueOf() // 1362790014817 d.getTime() // 1362790014817预期为数值的场合，Date实例会自动调用该方法，所以可以用下面的方法计算时间的间隔。var start = new Date(); // ... var end = new Date(); var elapsed = end - start;to 类方法（1）Date.prototype.toString()toString方法返回一个完整的日期字符串。var d = new Date(2013, 0, 1); d.toString() // "Tue Jan 01 2013 00:00:00 GMT+0800 (CST)" d // "Tue Jan 01 2013 00:00:00 GMT+0800 (CST)"因为toString是默认的调用方法，所以如果直接读取Date实例，就相当于调用这个方法。（2）Date.prototype.toUTCString()toUTCString方法返回对应的 UTC 时间，也就是比北京时间晚8个小时。var d = new Date(2013, 0, 1); d.toUTCString() // "Mon, 31 Dec 2012 16:00:00 GMT"（3）Date.prototype.toISOString()toISOString方法返回对应时间的 ISO8601 写法。var d = new Date(2013, 0, 1); d.toISOString() // "2012-12-31T16:00:00.000Z"注意，toISOString方法返回的总是 UTC 时区的时间。（4）Date.prototype.toJSON()toJSON方法返回一个符合 JSON 格式的 ISO 日期字符串，与toISOString方法的返回结果完全相同。var d = new Date(2013, 0, 1); d.toJSON() // "2012-12-31T16:00:00.000Z"（5）Date.prototype.toDateString()toDateString方法返回日期字符串（不含小时、分和秒）。var d = new Date(2013, 0, 1); d.toDateString() // "Tue Jan 01 2013"（6）Date.prototype.toTimeString()toTimeString方法返回时间字符串（不含年月日）。var d = new Date(2013, 0, 1); d.toTimeString() // "00:00:00 GMT+0800 (CST)"（7）本地时间以下三种方法，可以将 Date 实例转为表示本地时间的字符串。Date.prototype.toLocaleString()：完整的本地时间。Date.prototype.toLocaleDateString()：本地日期（不含小时、分和秒）。Date.prototype.toLocaleTimeString()：本地时间（不含年月日）。下面是用法实例。var d = new Date(2013, 0, 1); d.toLocaleString() // 中文版浏览器为"2013年1月1日 上午12:00:00" // 英文版浏览器为"1/1/2013 12:00:00 AM" d.toLocaleDateString() // 中文版浏览器为"2013年1月1日" // 英文版浏览器为"1/1/2013" d.toLocaleTimeString() // 中文版浏览器为"上午12:00:00" // 英文版浏览器为"12:00:00 AM"这三个方法都有两个可选的参数。dateObj.toLocaleString([locales[, options]]) dateObj.toLocaleDateString([locales[, options]]) dateObj.toLocaleTimeString([locales[, options]])这两个参数中，locales是一个指定所用语言的字符串，options是一个配置对象。下面是locales的例子，分别采用en-US和zh-CN语言设定。var d = new Date(2013, 0, 1); d.toLocaleString('en-US') // "1/1/2013, 12:00:00 AM" d.toLocaleString('zh-CN') // "2013/1/1 上午12:00:00" d.toLocaleDateString('en-US') // "1/1/2013" d.toLocaleDateString('zh-CN') // "2013/1/1" d.toLocaleTimeString('en-US') // "12:00:00 AM" d.toLocaleTimeString('zh-CN') // "上午12:00:00"options配置对象有以下属性。dateStyle：可能的值为full、long、medium、short。timeStyle：可能的值为full、long、medium、short。month：可能的值为numeric、2-digit、long、short、narrow。year：可能的值为numeric、2-digit。weekday：可能的值为long、short、narrow。day、hour、minute、second：可能的值为numeric、2-digit。timeZone：可能的值为 IANA 的时区数据库。timeZoneName：可能的值为long、short。hour12：24小时周期还是12小时周期，可能的值为true、false。下面是用法实例。var d = new Date(2013, 0, 1); d.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }) // "Tuesday, January 1, 2013" d.toLocaleDateString('en-US', { day: "2-digit", month: "long", year: "2-digit" }); // "January 01, 13" d.toLocaleTimeString('en-US', { timeZone: 'UTC', timeZoneName: 'short' }) // "4:00:00 PM UTC" d.toLocaleTimeString('en-US', { timeZone: 'Asia/Shanghai', timeZoneName: 'long' }) // "12:00:00 AM China Standard Time" d.toLocaleTimeString('en-US', { hour12: false }) // "00:00:00" d.toLocaleTimeString('en-US', { hour12: true }) // "12:00:00 AM"get 类方法Date对象提供了一系列get*方法，用来获取实例对象某个方面的值。getTime()：返回实例距离1970年1月1日00:00:00的毫秒数，等同于valueOf方法。getDate()：返回实例对象对应每个月的几号（从1开始）。getDay()：返回星期几，星期日为0，星期一为1，以此类推。getFullYear()：返回四位的年份。getMonth()：返回月份（0表示1月，11表示12月）。getHours()：返回小时（0-23）。getMilliseconds()：返回毫秒（0-999）。getMinutes()：返回分钟（0-59）。getSeconds()：返回秒（0-59）。getTimezoneOffset()：返回当前时间与 UTC 的时区差异，以分钟表示，返回结果考虑到了夏令时因素。所有这些get*方法返回的都是整数，不同方法返回值的范围不一样。分钟和秒：0 到 59小时：0 到 23星期：0（星期天）到 6（星期六）日期：1 到 31月份：0（一月）到 11（十二月）var d = new Date('January 6, 2013'); d.getDate() // 6 d.getMonth() // 0 d.getFullYear() // 2013 d.getTimezoneOffset() // -480上面代码中，最后一行返回-480，即 UTC 时间减去当前时间，单位是分钟。-480表示 UTC 比当前时间少480分钟，即当前时区比 UTC 早8个小时。下面是一个例子，计算本年度还剩下多少天。function leftDays() { var today = new Date(); var endYear = new Date(today.getFullYear(), 11, 31, 23, 59, 59, 999); var msPerDay = 24 * 60 * 60 * 1000; return Math.round((endYear.getTime() - today.getTime()) / msPerDay); }上面这些get*方法返回的都是当前时区的时间，Date对象还提供了这些方法对应的 UTC 版本，用来返回 UTC 时间。getUTCDate()getUTCFullYear()getUTCMonth()getUTCDay()getUTCHours()getUTCMinutes()getUTCSeconds()getUTCMilliseconds()var d = new Date('January 6, 2013'); d.getDate() // 6 d.getUTCDate() // 5上面代码中，实例对象d表示当前时区（东八时区）的1月6日0点0分0秒，这个时间对于当前时区来说是1月6日，所以getDate方法返回6，对于 UTC 时区来说是1月5日，所以getUTCDate方法返回5。set 类方法Date对象提供了一系列set*方法，用来设置实例对象的各个方面。setDate(date)：设置实例对象对应的每个月的几号（1-31），返回改变后毫秒时间戳。setFullYear(year [, month, date])：设置四位年份。setHours(hour [, min, sec, ms])：设置小时（0-23）。setMilliseconds(ms)：设置毫秒（0-999）。setMinutes(min [, sec, ms])：设置分钟（0-59）。setMonth(month [, date])：设置月份（0-11）。setSeconds(sec [, ms])：设置秒（0-59）。setTime(milliseconds)：设置毫秒时间戳。这些方法基本是跟get*方法一一对应的，但是没有setDay方法，因为星期几是计算出来的，而不是设置的。另外，需要注意的是，凡是涉及到设置月份，都是从0开始算的，即0是1月，11是12月。var d = new Date ('January 6, 2013'); d // Sun Jan 06 2013 00:00:00 GMT+0800 (CST) d.setDate(9) // 1357660800000 d // Wed Jan 09 2013 00:00:00 GMT+0800 (CST)set*方法的参数都会自动折算。以setDate()为例，如果参数超过当月的最大天数，则向下一个月顺延，如果参数是负数，表示从上个月的最后一天开始减去的天数。var d1 = new Date('January 6, 2013'); d1.setDate(32) // 1359648000000 d1 // Fri Feb 01 2013 00:00:00 GMT+0800 (CST) var d2 = new Date ('January 6, 2013'); d2.setDate(-1) // 1356796800000 d2 // Sun Dec 30 2012 00:00:00 GMT+0800 (CST)上面代码中，d1.setDate(32)将日期设为1月份的32号，因为1月份只有31号，所以自动折算为2月1日。d2.setDate(-1)表示设为上个月的倒数第二天，即12月30日。set类方法和get类方法，可以结合使用，得到相对时间。var d = new Date(); // 将日期向后推1000天 d.setDate(d.getDate() + 1000); // 将时间设为6小时后 d.setHours(d.getHours() + 6); // 将年份设为去年 d.setFullYear(d.getFullYear() - 1);set*系列方法除了setTime()，都有对应的 UTC 版本，即设置 UTC 时区的时间。setUTCDate()setUTCFullYear()setUTCHours()setUTCMilliseconds()setUTCMinutes()setUTCMonth()setUTCSeconds()var d = new Date('January 6, 2013'); d.getUTCHours() // 16 d.setUTCHours(22) // 1357423200000 d // Sun Jan 06 2013 06:00:00 GMT+0800 (CST)上面代码中，本地时区（东八时区）的1月6日0点0分，是 UTC 时区的前一天下午16点。设为 UTC 时区的22点以后，就变为本地时区的上午6点。参考链接Rakhitha Nimesh，Getting Started with the Date ObjectIlya Kantor, Date/Time functions
Math 对象Math是 JavaScript 的原生对象，提供各种数学功能。该对象不是构造函数，不能生成实例，所有的属性和方法都必须在Math对象上调用。静态属性Math对象的静态属性，提供以下一些数学常数。Math.E：常数e。Math.LN2：2 的自然对数。Math.LN10：10 的自然对数。Math.LOG2E：以 2 为底的e的对数。Math.LOG10E：以 10 为底的e的对数。Math.PI：常数π。Math.SQRT1_2：0.5 的平方根。Math.SQRT2：2 的平方根。Math.E // 2.718281828459045 Math.LN2 // 0.6931471805599453 Math.LN10 // 2.302585092994046 Math.LOG2E // 1.4426950408889634 Math.LOG10E // 0.4342944819032518 Math.PI // 3.141592653589793 Math.SQRT1_2 // 0.7071067811865476 Math.SQRT2 // 1.4142135623730951这些属性都是只读的，不能修改。静态方法Math对象提供以下一些静态方法。Math.abs()：绝对值Math.ceil()：向上取整Math.floor()：向下取整Math.max()：最大值Math.min()：最小值Math.pow()：幂运算Math.sqrt()：平方根Math.log()：自然对数Math.exp()：e的指数Math.round()：四舍五入Math.random()：随机数Math.abs()Math.abs方法返回参数值的绝对值。Math.abs(1) // 1 Math.abs(-1) // 1Math.max()，Math.min()Math.max方法返回参数之中最大的那个值，Math.min返回最小的那个值。如果参数为空, Math.min返回Infinity, Math.max返回-Infinity。Math.max(2, -1, 5) // 5 Math.min(2, -1, 5) // -1 Math.min() // Infinity Math.max() // -InfinityMath.floor()，Math.ceil()Math.floor方法返回小于或等于参数值的最大整数（地板值）。Math.floor(3.2) // 3 Math.floor(-3.2) // -4Math.ceil方法返回大于或等于参数值的最小整数（天花板值）。Math.ceil(3.2) // 4 Math.ceil(-3.2) // -3这两个方法可以结合起来，实现一个总是返回数值的整数部分的函数。function ToInteger(x) { x = Number(x); return x < 0 ? Math.ceil(x) : Math.floor(x); } ToInteger(3.2) // 3 ToInteger(3.5) // 3 ToInteger(3.8) // 3 ToInteger(-3.2) // -3 ToInteger(-3.5) // -3 ToInteger(-3.8) // -3上面代码中，不管正数或负数，ToInteger函数总是返回一个数值的整数部分。Math.round()Math.round方法用于四舍五入。Math.round(0.1) // 0 Math.round(0.5) // 1 Math.round(0.6) // 1 // 等同于 Math.floor(x + 0.5)注意，它对负数的处理（主要是对0.5的处理）。Math.round(-1.1) // -1 Math.round(-1.5) // -1 Math.round(-1.6) // -2Math.pow()Math.pow方法返回以第一个参数为底数、第二个参数为指数的幂运算值。// 等同于 2 ** 2 Math.pow(2, 2) // 4 // 等同于 2 ** 3 Math.pow(2, 3) // 8下面是计算圆面积的方法。var radius = 20; var area = Math.PI * Math.pow(radius, 2);Math.sqrt()Math.sqrt方法返回参数值的平方根。如果参数是一个负值，则返回NaN。Math.sqrt(4) // 2 Math.sqrt(-4) // NaNMath.log()Math.log方法返回以e为底的自然对数值。Math.log(Math.E) // 1 Math.log(10) // 2.302585092994046如果要计算以10为底的对数，可以先用Math.log求出自然对数，然后除以Math.LN10；求以2为底的对数，可以除以Math.LN2。Math.log(100)/Math.LN10 // 2 Math.log(8)/Math.LN2 // 3Math.exp()Math.exp方法返回常数e的参数次方。Math.exp(1) // 2.718281828459045 Math.exp(3) // 20.085536923187668Math.random()Math.random()返回0到1之间的一个伪随机数，可能等于0，但是一定小于1。Math.random() // 0.7151307314634323任意范围的随机数生成函数如下。function getRandomArbitrary(min, max) { return Math.random() * (max - min) + min; } getRandomArbitrary(1.5, 6.5) // 2.4942810038223864任意范围的随机整数生成函数如下。function getRandomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } getRandomInt(1, 6) // 5返回随机字符的例子如下。function random_str(length) { var ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; ALPHABET += 'abcdefghijklmnopqrstuvwxyz'; ALPHABET += '0123456789-_'; var str = ''; for (var i = 0; i < length; ++i) { var rand = Math.floor(Math.random() * ALPHABET.length); str += ALPHABET.substring(rand, rand + 1); } return str; } random_str(6) // "NdQKOr"上面代码中，random_str函数接受一个整数作为参数，返回变量ALPHABET内的随机字符所组成的指定长度的字符串。三角函数方法Math对象还提供一系列三角函数方法。Math.sin()：返回参数的正弦（参数为弧度值）Math.cos()：返回参数的余弦（参数为弧度值）Math.tan()：返回参数的正切（参数为弧度值）Math.asin()：返回参数的反正弦（返回值为弧度值）Math.acos()：返回参数的反余弦（返回值为弧度值）Math.atan()：返回参数的反正切（返回值为弧度值）Math.sin(0) // 0 Math.cos(0) // 1 Math.tan(0) // 0 Math.sin(Math.PI / 2) // 1 Math.asin(1) // 1.5707963267948966 Math.acos(1) // 0 Math.atan(1) // 0.7853981633974483
String 对象概述String对象是 JavaScript 原生提供的三个包装对象之一，用来生成字符串对象。var s1 = 'abc'; var s2 = new String('abc'); typeof s1 // "string" typeof s2 // "object" s2.valueOf() // "abc"上面代码中，变量s1是字符串，s2是对象。由于s2是字符串对象，s2.valueOf方法返回的就是它所对应的原始字符串。字符串对象是一个类似数组的对象（很像数组，但不是数组）。new String('abc') // String {0: "a", 1: "b", 2: "c", length: 3} (new String('abc'))[1] // "b"上面代码中，字符串abc对应的字符串对象，有数值键（0、1、2）和length属性，所以可以像数组那样取值。除了用作构造函数，String对象还可以当作工具方法使用，将任意类型的值转为字符串。String(true) // "true" String(5) // "5"上面代码将布尔值true和数值5，分别转换为字符串。静态方法String.fromCharCode()String对象提供的静态方法（即定义在对象本身，而不是定义在对象实例的方法），主要是String.fromCharCode()。该方法的参数是一个或多个数值，代表 Unicode 码点，返回值是这些码点组成的字符串。String.fromCharCode() // "" String.fromCharCode(97) // "a" String.fromCharCode(104, 101, 108, 108, 111) // "hello"上面代码中，String.fromCharCode方法的参数为空，就返回空字符串；否则，返回参数对应的 Unicode 字符串。注意，该方法不支持 Unicode 码点大于0xFFFF的字符，即传入的参数不能大于0xFFFF（即十进制的 65535）。String.fromCharCode(0x20BB7) // "ஷ" String.fromCharCode(0x20BB7) === String.fromCharCode(0x0BB7) // true上面代码中，String.fromCharCode参数0x20BB7大于0xFFFF，导致返回结果出错。0x20BB7对应的字符是汉字告，但是返回结果却是另一个字符（码点0x0BB7）。这是因为String.fromCharCode发现参数值大于0xFFFF，就会忽略多出的位（即忽略0x20BB7里面的2）。这种现象的根本原因在于，码点大于0xFFFF的字符占用四个字节，而 JavaScript 默认支持两个字节的字符。这种情况下，必须把0x20BB7拆成两个字符表示。String.fromCharCode(0xD842, 0xDFB7) // "告"上面代码中，0x20BB7拆成两个字符0xD842和0xDFB7（即两个两字节字符，合成一个四字节字符），就能得到正确的结果。码点大于0xFFFF的字符的四字节表示法，由 UTF-16 编码方法决定。实例属性String.prototype.length字符串实例的length属性返回字符串的长度。'abc'.length // 3实例方法String.prototype.charAt()charAt方法返回指定位置的字符，参数是从0开始编号的位置。var s = new String('abc'); s.charAt(1) // "b" s.charAt(s.length - 1) // "c"这个方法完全可以用数组下标替代。'abc'.charAt(1) // "b" 'abc'[1] // "b"如果参数为负数，或大于等于字符串的长度，charAt返回空字符串。'abc'.charAt(-1) // "" 'abc'.charAt(3) // ""String.prototype.charCodeAt()charCodeAt()方法返回字符串指定位置的 Unicode 码点（十进制表示），相当于String.fromCharCode()的逆操作。'abc'.charCodeAt(1) // 98上面代码中，abc的1号位置的字符是b，它的 Unicode 码点是98。如果没有任何参数，charCodeAt返回首字符的 Unicode 码点。'abc'.charCodeAt() // 97如果参数为负数，或大于等于字符串的长度，charCodeAt返回NaN。'abc'.charCodeAt(-1) // NaN 'abc'.charCodeAt(4) // NaN注意，charCodeAt方法返回的 Unicode 码点不会大于65536（0xFFFF），也就是说，只返回两个字节的字符的码点。如果遇到码点大于 65536 的字符（四个字节的字符），必须连续使用两次charCodeAt，不仅读入charCodeAt(i)，还要读入charCodeAt(i+1)，将两个值放在一起，才能得到准确的字符。String.prototype.concat()concat方法用于连接两个字符串，返回一个新字符串，不改变原字符串。var s1 = 'abc'; var s2 = 'def'; s1.concat(s2) // "abcdef" s1 // "abc"该方法可以接受多个参数。'a'.concat('b', 'c') // "abc"如果参数不是字符串，concat方法会将其先转为字符串，然后再连接。var one = 1; var two = 2; var three = '3'; ''.concat(one, two, three) // "123" one + two + three // "33"上面代码中，concat方法将参数先转成字符串再连接，所以返回的是一个三个字符的字符串。作为对比，加号运算符在两个运算数都是数值时，不会转换类型，所以返回的是一个两个字符的字符串。String.prototype.slice()slice()方法用于从原字符串取出子字符串并返回，不改变原字符串。它的第一个参数是子字符串的开始位置，第二个参数是子字符串的结束位置（不含该位置）。'JavaScript'.slice(0, 4) // "Java"如果省略第二个参数，则表示子字符串一直到原字符串结束。'JavaScript'.slice(4) // "Script"如果参数是负值，表示从结尾开始倒数计算的位置，即该负值加上字符串长度。'JavaScript'.slice(-6) // "Script" 'JavaScript'.slice(0, -6) // "Java" 'JavaScript'.slice(-2, -1) // "p"如果第一个参数大于第二个参数（正数情况下），slice()方法返回一个空字符串。'JavaScript'.slice(2, 1) // ""String.prototype.substring()substring方法用于从原字符串取出子字符串并返回，不改变原字符串，跟slice方法很相像。它的第一个参数表示子字符串的开始位置，第二个位置表示结束位置（返回结果不含该位置）。'JavaScript'.substring(0, 4) // "Java"如果省略第二个参数，则表示子字符串一直到原字符串的结束。'JavaScript'.substring(4) // "Script"如果第一个参数大于第二个参数，substring方法会自动更换两个参数的位置。'JavaScript'.substring(10, 4) // "Script" // 等同于 'JavaScript'.substring(4, 10) // "Script"上面代码中，调换substring方法的两个参数，都得到同样的结果。如果参数是负数，substring方法会自动将负数转为0。'JavaScript'.substring(-3) // "JavaScript" 'JavaScript'.substring(4, -3) // "Java"上面代码中，第二个例子的参数-3会自动变成0，等同于'JavaScript'.substring(4, 0)。由于第二个参数小于第一个参数，会自动互换位置，所以返回Java。由于这些规则违反直觉，因此不建议使用substring方法，应该优先使用slice。String.prototype.substr()substr方法用于从原字符串取出子字符串并返回，不改变原字符串，跟slice和substring方法的作用相同。substr方法的第一个参数是子字符串的开始位置（从0开始计算），第二个参数是子字符串的长度。'JavaScript'.substr(4, 6) // "Script"如果省略第二个参数，则表示子字符串一直到原字符串的结束。'JavaScript'.substr(4) // "Script"如果第一个参数是负数，表示倒数计算的字符位置。如果第二个参数是负数，将被自动转为0，因此会返回空字符串。'JavaScript'.substr(-6) // "Script" 'JavaScript'.substr(4, -1) // ""上面代码中，第二个例子的参数-1自动转为0，表示子字符串长度为0，所以返回空字符串。String.prototype.indexOf()，String.prototype.lastIndexOf()indexOf方法用于确定一个字符串在另一个字符串中第一次出现的位置，返回结果是匹配开始的位置。如果返回-1，就表示不匹配。'hello world'.indexOf('o') // 4 'JavaScript'.indexOf('script') // -1indexOf方法还可以接受第二个参数，表示从该位置开始向后匹配。'hello world'.indexOf('o', 6) // 7lastIndexOf方法的用法跟indexOf方法一致，主要的区别是lastIndexOf从尾部开始匹配，indexOf则是从头部开始匹配。'hello world'.lastIndexOf('o') // 7另外，lastIndexOf的第二个参数表示从该位置起向前匹配。'hello world'.lastIndexOf('o', 6) // 4String.prototype.trim()trim方法用于去除字符串两端的空格，返回一个新字符串，不改变原字符串。' hello world '.trim() // "hello world"该方法去除的不仅是空格，还包括制表符（\t、\v）、换行符（\n）和回车符（\r）。'\r\nabc \t'.trim() // 'abc'String.prototype.toLowerCase()，String.prototype.toUpperCase()toLowerCase方法用于将一个字符串全部转为小写，toUpperCase则是全部转为大写。它们都返回一个新字符串，不改变原字符串。'Hello World'.toLowerCase() // "hello world" 'Hello World'.toUpperCase() // "HELLO WORLD"String.prototype.match()match方法用于确定原字符串是否匹配某个子字符串，返回一个数组，成员为匹配的第一个字符串。如果没有找到匹配，则返回null。'cat, bat, sat, fat'.match('at') // ["at"] 'cat, bat, sat, fat'.match('xt') // null返回的数组还有index属性和input属性，分别表示匹配字符串开始的位置和原始字符串。var matches = 'cat, bat, sat, fat'.match('at'); matches.index // 1 matches.input // "cat, bat, sat, fat"match方法还可以使用正则表达式作为参数，详见《正则表达式》一章。String.prototype.search()，String.prototype.replace()search方法的用法基本等同于match，但是返回值为匹配的第一个位置。如果没有找到匹配，则返回-1。'cat, bat, sat, fat'.search('at') // 1search方法还可以使用正则表达式作为参数，详见《正则表达式》一节。replace方法用于替换匹配的子字符串，一般情况下只替换第一个匹配（除非使用带有g修饰符的正则表达式）。'aaa'.replace('a', 'b') // "baa"replace方法还可以使用正则表达式作为参数，详见《正则表达式》一节。String.prototype.split()split方法按照给定规则分割字符串，返回一个由分割出来的子字符串组成的数组。'a|b|c'.split('|') // ["a", "b", "c"]如果分割规则为空字符串，则返回数组的成员是原字符串的每一个字符。'a|b|c'.split('') // ["a", "|", "b", "|", "c"]如果省略参数，则返回数组的唯一成员就是原字符串。'a|b|c'.split() // ["a|b|c"]如果满足分割规则的两个部分紧邻着（即两个分割符中间没有其他字符），则返回数组之中会有一个空字符串。'a||c'.split('|') // ['a', '', 'c']如果满足分割规则的部分处于字符串的开头或结尾（即它的前面或后面没有其他字符），则返回数组的第一个或最后一个成员是一个空字符串。'|b|c'.split('|') // ["", "b", "c"] 'a|b|'.split('|') // ["a", "b", ""]split方法还可以接受第二个参数，限定返回数组的最大成员数。'a|b|c'.split('|', 0) // [] 'a|b|c'.split('|', 1) // ["a"] 'a|b|c'.split('|', 2) // ["a", "b"] 'a|b|c'.split('|', 3) // ["a", "b", "c"] 'a|b|c'.split('|', 4) // ["a", "b", "c"]上面代码中，split方法的第二个参数，决定了返回数组的成员数。split方法还可以使用正则表达式作为参数，详见《正则表达式》一节。String.prototype.localeCompare()localeCompare方法用于比较两个字符串。它返回一个整数，如果小于0，表示第一个字符串小于第二个字符串；如果等于0，表示两者相等；如果大于0，表示第一个字符串大于第二个字符串。'apple'.localeCompare('banana') // -1 'apple'.localeCompare('apple') // 0该方法的最大特点，就是会考虑自然语言的顺序。举例来说，正常情况下，大写的英文字母小于小写字母。'B' > 'a' // false上面代码中，字母B小于字母a。因为 JavaScript 采用的是 Unicode 码点比较，B的码点是66，而a的码点是97。但是，localeCompare方法会考虑自然语言的排序情况，将B排在a的前面。'B'.localeCompare('a') // 1上面代码中，localeCompare方法返回整数1，表示B较大。localeCompare还可以有第二个参数，指定所使用的语言（默认是英语），然后根据该语言的规则进行比较。'ä'.localeCompare('z', 'de') // -1 'ä'.localeCompare('z', 'sv') // 1上面代码中，de表示德语，sv表示瑞典语。德语中，ä小于z，所以返回-1；瑞典语中，ä大于z，所以返回1。参考链接Ariya Hidayat, JavaScript String: substring, substr, slice
Number 对象概述Number对象是数值对应的包装对象，可以作为构造函数使用，也可以作为工具函数使用。作为构造函数时，它用于生成值为数值的对象。var n = new Number(1); typeof n // "object"上面代码中，Number对象作为构造函数使用，返回一个值为1的对象。作为工具函数时，它可以将任何类型的值转为数值。Number(true) // 1上面代码将布尔值true转为数值1。Number作为工具函数的用法，详见《数据类型转换》一章。静态属性Number对象拥有以下一些静态属性（即直接定义在Number对象上的属性，而不是定义在实例上的属性）。Number.POSITIVE_INFINITY：正的无限，指向Infinity。Number.NEGATIVE_INFINITY：负的无限，指向-Infinity。Number.NaN：表示非数值，指向NaN。Number.MIN_VALUE：表示最小的正数（即最接近0的正数，在64位浮点数体系中为5e-324），相应的，最接近0的负数为-Number.MIN_VALUE。Number.MAX_SAFE_INTEGER：表示能够精确表示的最大整数，即9007199254740991。Number.MIN_SAFE_INTEGER：表示能够精确表示的最小整数，即-9007199254740991。Number.POSITIVE_INFINITY // Infinity Number.NEGATIVE_INFINITY // -Infinity Number.NaN // NaN Number.MAX_VALUE // 1.7976931348623157e+308 Number.MAX_VALUE < Infinity // true Number.MIN_VALUE // 5e-324 Number.MIN_VALUE > 0 // true Number.MAX_SAFE_INTEGER // 9007199254740991 Number.MIN_SAFE_INTEGER // -9007199254740991实例方法Number对象有4个实例方法，都跟将数值转换成指定格式有关。Number.prototype.toString()Number对象部署了自己的toString方法，用来将一个数值转为字符串形式。(10).toString() // "10"toString方法可以接受一个参数，表示输出的进制。如果省略这个参数，默认将数值先转为十进制，再输出字符串；否则，就根据参数指定的进制，将一个数字转化成某个进制的字符串。(10).toString(2) // "1010" (10).toString(8) // "12" (10).toString(16) // "a"上面代码中，10一定要放在括号里，这样表明后面的点表示调用对象属性。如果不加括号，这个点会被 JavaScript 引擎解释成小数点，从而报错。10.toString(2) // SyntaxError: Unexpected token ILLEGAL只要能够让 JavaScript 引擎不混淆小数点和对象的点运算符，各种写法都能用。除了为10加上括号，还可以在10后面加两个点，JavaScript 会把第一个点理解成小数点（即10.0），把第二个点理解成调用对象属性，从而得到正确结果。10..toString(2) // "1010" // 其他方法还包括 10 .toString(2) // "1010" 10.0.toString(2) // "1010"这实际上意味着，可以直接对一个小数使用toString方法。10.5.toString() // "10.5" 10.5.toString(2) // "1010.1" 10.5.toString(8) // "12.4" 10.5.toString(16) // "a.8"通过方括号运算符也可以调用toString方法。10['toString'](2) // "1010"toString方法只能将十进制的数，转为其他进制的字符串。如果要将其他进制的数，转回十进制，需要使用parseInt方法。Number.prototype.toFixed()toFixed()方法先将一个数转为指定位数的小数，然后返回这个小数对应的字符串。(10).toFixed(2) // "10.00" 10.005.toFixed(2) // "10.01"上面代码中，10和10.005先转成2位小数，然后转成字符串。其中10必须放在括号里，否则后面的点会被处理成小数点。toFixed()方法的参数为小数位数，有效范围为0到100，超出这个范围将抛出 RangeError 错误。由于浮点数的原因，小数5的四舍五入是不确定的，使用的时候必须小心。(10.055).toFixed(2) // 10.05 (10.005).toFixed(2) // 10.01Number.prototype.toExponential()toExponential方法用于将一个数转为科学计数法形式。(10).toExponential() // "1e+1" (10).toExponential(1) // "1.0e+1" (10).toExponential(2) // "1.00e+1" (1234).toExponential() // "1.234e+3" (1234).toExponential(1) // "1.2e+3" (1234).toExponential(2) // "1.23e+3"toExponential方法的参数是小数点后有效数字的位数，范围为0到100，超出这个范围，会抛出一个 RangeError 错误。Number.prototype.toPrecision()Number.prototype.toPrecision()方法用于将一个数转为指定位数的有效数字。(12.34).toPrecision(1) // "1e+1" (12.34).toPrecision(2) // "12" (12.34).toPrecision(3) // "12.3" (12.34).toPrecision(4) // "12.34" (12.34).toPrecision(5) // "12.340"该方法的参数为有效数字的位数，范围是1到100，超出这个范围会抛出 RangeError 错误。该方法用于四舍五入时不太可靠，跟浮点数不是精确储存有关。(12.35).toPrecision(3) // "12.3" (12.25).toPrecision(3) // "12.3" (12.15).toPrecision(3) // "12.2" (12.45).toPrecision(3) // "12.4"Number.prototype.toLocaleString()Number.prototype.toLocaleString()方法接受一个地区码作为参数，返回一个字符串，表示当前数字在该地区的当地书写形式。(123).toLocaleString('zh-Hans-CN-u-nu-hanidec') // "一二三"该方法还可以接受第二个参数配置对象，用来定制指定用途的返回字符串。该对象的style属性指定输出样式，默认值是decimal，表示输出十进制形式。如果值为percent，表示输出百分数。(123).toLocaleString('zh-Hans-CN', { style: 'percent' }) // "12,300%"如果style属性的值为currency，则可以搭配currency属性，输出指定格式的货币字符串形式。(123).toLocaleString('zh-Hans-CN', { style: 'currency', currency: 'CNY' }) // "￥123.00" (123).toLocaleString('de-DE', { style: 'currency', currency: 'EUR' }) // "123,00 €" (123).toLocaleString('en-US', { style: 'currency', currency: 'USD' }) // "$123.00"如果Number.prototype.toLocaleString()省略了参数，则由浏览器自行决定如何处理，通常会使用操作系统的地区设定。注意，该方法如果使用浏览器不认识的地区码，会抛出一个错误。(123).toLocaleString('123') // 出错自定义方法与其他对象一样，Number.prototype对象上面可以自定义方法，被Number的实例继承。Number.prototype.add = function (x) { return this + x; }; 8['add'](2) // 10上面代码为Number对象实例定义了一个add方法。在数值上调用某个方法，数值会自动转为Number的实例对象，所以就可以调用add方法了。由于add方法返回的还是数值，所以可以链式运算。Number.prototype.subtract = function (x) { return this - x; }; (8).add(2).subtract(4) // 6上面代码在Number对象的实例上部署了subtract方法，它可以与add方法链式调用。我们还可以部署更复杂的方法。Number.prototype.iterate = function () { var result = []; for (var i = 0; i <= this; i++) { result.push(i); } return result; }; (8).iterate() // [0, 1, 2, 3, 4, 5, 6, 7, 8]上面代码在Number对象的原型上部署了iterate方法，将一个数值自动遍历为一个数组。注意，数值的自定义方法，只能定义在它的原型对象Number.prototype上面，数值本身是无法自定义属性的。var n = 1; n.x = 1; n.x // undefined上面代码中，n是一个原始类型的数值。直接在它上面新增一个属性x，不会报错，但毫无作用，总是返回undefined。这是因为一旦被调用属性，n就自动转为Number的实例对象，调用结束后，该对象自动销毁。所以，下一次调用n的属性时，实际取到的是另一个对象，属性x当然就读不出来。
Boolean 对象概述Boolean对象是 JavaScript 的三个包装对象之一。作为构造函数，它主要用于生成布尔值的包装对象实例。var b = new Boolean(true); typeof b // "object" b.valueOf() // true上面代码的变量b是一个Boolean对象的实例，它的类型是对象，值为布尔值true。注意，false对应的包装对象实例，布尔运算结果也是true。if (new Boolean(false)) { console.log('true'); } // true if (new Boolean(false).valueOf()) { console.log('true'); } // 无输出上面代码的第一个例子之所以得到true，是因为false对应的包装对象实例是一个对象，进行逻辑运算时，被自动转化成布尔值true（因为所有对象对应的布尔值都是true）。而实例的valueOf方法，则返回实例对应的原始值，本例为false。Boolean 函数的类型转换作用Boolean对象除了可以作为构造函数，还可以单独使用，将任意值转为布尔值。这时Boolean就是一个单纯的工具方法。Boolean(undefined) // false Boolean(null) // false Boolean(0) // false Boolean('') // false Boolean(NaN) // false Boolean(1) // true Boolean('false') // true Boolean([]) // true Boolean({}) // true Boolean(function () {}) // true Boolean(/foo/) // true上面代码中几种得到true的情况，都值得认真记住。顺便提一下，使用双重的否运算符（!）也可以将任意值转为对应的布尔值。!!undefined // false !!null // false !!0 // false !!'' // false !!NaN // false !!1 // true !!'false' // true !![] // true !!{} // true !!function(){} // true !!/foo/ // true最后，对于一些特殊值，Boolean对象前面加不加new，会得到完全相反的结果，必须小心。if (Boolean(false)) { console.log('true'); } // 无输出 if (new Boolean(false)) { console.log('true'); } // true if (Boolean(null)) { console.log('true'); } // 无输出 if (new Boolean(null)) { console.log('true'); } // true
包装对象定义对象是 JavaScript 语言最主要的数据类型，三种原始类型的值——数值、字符串、布尔值——在一定条件下，也会自动转为对象，也就是原始类型的“包装对象”（wrapper）。所谓“包装对象”，指的是与数值、字符串、布尔值分别相对应的Number、String、Boolean三个原生对象。这三个原生对象可以把原始类型的值变成（包装成）对象。var v1 = new Number(123); var v2 = new String('abc'); var v3 = new Boolean(true); typeof v1 // "object" typeof v2 // "object" typeof v3 // "object" v1 === 123 // false v2 === 'abc' // false v3 === true // false上面代码中，基于原始类型的值，生成了三个对应的包装对象。可以看到，v1、v2、v3都是对象，且与对应的简单类型值不相等。包装对象的设计目的，首先是使得“对象”这种类型可以覆盖 JavaScript 所有的值，整门语言有一个通用的数据模型，其次是使得原始类型的值也有办法调用自己的方法。Number、String和Boolean这三个原生对象，如果不作为构造函数调用（即调用时不加new），而是作为普通函数调用，常常用于将任意类型的值转为数值、字符串和布尔值。// 字符串转为数值 Number('123') // 123 // 数值转为字符串 String(123) // "123" // 数值转为布尔值 Boolean(123) // true上面这种数据类型的转换，详见《数据类型转换》一节。总结一下，这三个对象作为构造函数使用（带有new）时，可以将原始类型的值转为对象；作为普通函数使用时（不带有new），可以将任意类型的值，转为原始类型的值。实例方法三种包装对象各自提供了许多实例方法，详见后文。这里介绍两种它们共同具有、从Object对象继承的方法：valueOf()和toString()。valueOf()valueOf()方法返回包装对象实例对应的原始类型的值。new Number(123).valueOf() // 123 new String('abc').valueOf() // "abc" new Boolean(true).valueOf() // truetoString()toString()方法返回对应的字符串形式。new Number(123).toString() // "123" new String('abc').toString() // "abc" new Boolean(true).toString() // "true"原始类型与实例对象的自动转换某些场合，原始类型的值会自动当作包装对象调用，即调用包装对象的属性和方法。这时，JavaScript 引擎会自动将原始类型的值转为包装对象实例，并在使用后立刻销毁实例。比如，字符串可以调用length属性，返回字符串的长度。'abc'.length // 3上面代码中，abc是一个字符串，本身不是对象，不能调用length属性。JavaScript 引擎自动将其转为包装对象，在这个对象上调用length属性。调用结束后，这个临时对象就会被销毁。这就叫原始类型与实例对象的自动转换。var str = 'abc'; str.length // 3 // 等同于 var strObj = new String(str) // String { // 0: "a", 1: "b", 2: "c", length: 3, PrimitiveValue: "abc" // } strObj.length // 3上面代码中，字符串abc的包装对象提供了多个属性，length只是其中之一。自动转换生成的包装对象是只读的，无法修改。所以，字符串无法添加新属性。var s = 'Hello World'; s.x = 123; s.x // undefined上面代码为字符串s添加了一个x属性，结果无效，总是返回undefined。另一方面，调用结束后，包装对象实例会自动销毁。这意味着，下一次调用字符串的属性时，实际是调用一个新生成的对象，而不是上一次调用时生成的那个对象，所以取不到赋值在上一个对象的属性。如果要为字符串添加属性，只有在它的原型对象String.prototype上定义（参见《面向对象编程》章节）。自定义方法除了原生的实例方法，包装对象还可以自定义方法和属性，供原始类型的值直接调用。比如，我们可以新增一个double方法，使得字符串和数字翻倍。String.prototype.double = function () { return this.valueOf() + this.valueOf(); }; 'abc'.double() // abcabc Number.prototype.double = function () { return this.valueOf() + this.valueOf(); }; (123).double() // 246上面代码在String和Number这两个对象的原型上面，分别自定义了一个方法，从而可以在所有实例对象上调用。注意，最后一行的123外面必须要加上圆括号，否则后面的点运算符（.）会被解释成小数点。
Array 对象构造函数Array是 JavaScript 的原生对象，同时也是一个构造函数，可以用它生成新的数组。var arr = new Array(2); arr.length // 2 arr // [ empty x 2 ]上面代码中，Array()构造函数的参数2，表示生成一个两个成员的数组，每个位置都是空值。如果没有使用new关键字，运行结果也是一样的。var arr = Array(2); // 等同于 var arr = new Array(2);考虑到语义性，以及与其他构造函数用法保持一致，建议总是加上new。Array()构造函数有一个很大的缺陷，不同的参数个数会导致不一致的行为。// 无参数时，返回一个空数组 new Array() // [] // 单个正整数参数，表示返回的新数组的长度 new Array(1) // [ empty ] new Array(2) // [ empty x 2 ] // 非正整数的数值作为参数，会报错 new Array(3.2) // RangeError: Invalid array length new Array(-3) // RangeError: Invalid array length // 单个非数值（比如字符串、布尔值、对象等）作为参数， // 则该参数是返回的新数组的成员 new Array('abc') // ['abc'] new Array([1]) // [Array[1]] // 多参数时，所有参数都是返回的新数组的成员 new Array(1, 2) // [1, 2] new Array('a', 'b', 'c') // ['a', 'b', 'c']可以看到，Array()作为构造函数，行为很不一致。因此，不建议使用它生成新数组，直接使用数组字面量是更好的做法。// bad var arr = new Array(1, 2); // good var arr = [1, 2];注意，如果参数是一个正整数，返回数组的成员都是空位。虽然读取的时候返回undefined，但实际上该位置没有任何值。虽然这时可以读取到length属性，但是取不到键名。var a = new Array(3); var b = [undefined, undefined, undefined]; a.length // 3 b.length // 3 a[0] // undefined b[0] // undefined 0 in a // false 0 in b // true上面代码中，a是Array()生成的一个长度为3的空数组，b是一个三个成员都是undefined的数组，这两个数组是不一样的。读取键值的时候，a和b都返回undefined，但是a的键名（成员的序号）都是空的，b的键名是有值的。静态方法Array.isArray()Array.isArray方法返回一个布尔值，表示参数是否为数组。它可以弥补typeof运算符的不足。var arr = [1, 2, 3]; typeof arr // "object" Array.isArray(arr) // true上面代码中，typeof运算符只能显示数组的类型是Object，而Array.isArray方法可以识别数组。实例方法valueOf()，toString()valueOf方法是一个所有对象都拥有的方法，表示对该对象求值。不同对象的valueOf方法不尽一致，数组的valueOf方法返回数组本身。var arr = [1, 2, 3]; arr.valueOf() // [1, 2, 3]toString方法也是对象的通用方法，数组的toString方法返回数组的字符串形式。var arr = [1, 2, 3]; arr.toString() // "1,2,3" var arr = [1, 2, 3, [4, 5, 6]]; arr.toString() // "1,2,3,4,5,6"push()，pop()push方法用于在数组的末端添加一个或多个元素，并返回添加新元素后的数组长度。注意，该方法会改变原数组。var arr = []; arr.push(1) // 1 arr.push('a') // 2 arr.push(true, {}) // 4 arr // [1, 'a', true, {}]上面代码使用push方法，往数组中添加了四个成员。pop方法用于删除数组的最后一个元素，并返回该元素。注意，该方法会改变原数组。var arr = ['a', 'b', 'c']; arr.pop() // 'c' arr // ['a', 'b']对空数组使用pop方法，不会报错，而是返回undefined。[].pop() // undefinedpush和pop结合使用，就构成了“后进先出”的栈结构（stack）。var arr = []; arr.push(1, 2); arr.push(3); arr.pop(); arr // [1, 2]上面代码中，3是最后进入数组的，但是最早离开数组。shift()，unshift()shift()方法用于删除数组的第一个元素，并返回该元素。注意，该方法会改变原数组。var a = ['a', 'b', 'c']; a.shift() // 'a' a // ['b', 'c']上面代码中，使用shift()方法以后，原数组就变了。shift()方法可以遍历并清空一个数组。var list = [1, 2, 3, 4]; var item; while (item = list.shift()) { console.log(item); } list // []上面代码通过list.shift()方法每次取出一个元素，从而遍历数组。它的前提是数组元素不能是0或任何布尔值等于false的元素，因此这样的遍历不是很可靠。push()和shift()结合使用，就构成了“先进先出”的队列结构（queue）。unshift()方法用于在数组的第一个位置添加元素，并返回添加新元素后的数组长度。注意，该方法会改变原数组。var a = ['a', 'b', 'c']; a.unshift('x'); // 4 a // ['x', 'a', 'b', 'c']unshift()方法可以接受多个参数，这些参数都会添加到目标数组头部。var arr = [ 'c', 'd' ]; arr.unshift('a', 'b') // 4 arr // [ 'a', 'b', 'c', 'd' ]join()join()方法以指定参数作为分隔符，将所有数组成员连接为一个字符串返回。如果不提供参数，默认用逗号分隔。var a = [1, 2, 3, 4]; a.join(' ') // '1 2 3 4' a.join(' | ') // "1 | 2 | 3 | 4" a.join() // "1,2,3,4"如果数组成员是undefined或null或空位，会被转成空字符串。[undefined, null].join('#') // '#' ['a',, 'b'].join('-') // 'a--b'通过call方法，这个方法也可以用于字符串或类似数组的对象。Array.prototype.join.call('hello', '-') // "h-e-l-l-o" var obj = { 0: 'a', 1: 'b', length: 2 }; Array.prototype.join.call(obj, '-') // 'a-b'concat()concat方法用于多个数组的合并。它将新数组的成员，添加到原数组成员的后部，然后返回一个新数组，原数组不变。['hello'].concat(['world']) // ["hello", "world"] ['hello'].concat(['world'], ['!']) // ["hello", "world", "!"] [].concat({a: 1}, {b: 2}) // [{ a: 1 }, { b: 2 }] [2].concat({a: 1}) // [2, {a: 1}]除了数组作为参数，concat也接受其他类型的值作为参数，添加到目标数组尾部。[1, 2, 3].concat(4, 5, 6) // [1, 2, 3, 4, 5, 6]如果数组成员包括对象，concat方法返回当前数组的一个浅拷贝。所谓“浅拷贝”，指的是新数组拷贝的是对象的引用。var obj = { a: 1 }; var oldArray = [obj]; var newArray = oldArray.concat(); obj.a = 2; newArray[0].a // 2上面代码中，原数组包含一个对象，concat方法生成的新数组包含这个对象的引用。所以，改变原对象以后，新数组跟着改变。reverse()reverse方法用于颠倒排列数组元素，返回改变后的数组。注意，该方法将改变原数组。var a = ['a', 'b', 'c']; a.reverse() // ["c", "b", "a"] a // ["c", "b", "a"]slice()slice()方法用于提取目标数组的一部分，返回一个新数组，原数组不变。arr.slice(start, end);它的第一个参数为起始位置（从0开始，会包括在返回的新数组之中），第二个参数为终止位置（但该位置的元素本身不包括在内）。如果省略第二个参数，则一直返回到原数组的最后一个成员。var a = ['a', 'b', 'c']; a.slice(0) // ["a", "b", "c"] a.slice(1) // ["b", "c"] a.slice(1, 2) // ["b"] a.slice(2, 6) // ["c"] a.slice() // ["a", "b", "c"]上面代码中，最后一个例子slice()没有参数，实际上等于返回一个原数组的拷贝。如果slice()方法的参数是负数，则表示倒数计算的位置。var a = ['a', 'b', 'c']; a.slice(-2) // ["b", "c"] a.slice(-2, -1) // ["b"]上面代码中，-2表示倒数计算的第二个位置，-1表示倒数计算的第一个位置。如果第一个参数大于等于数组长度，或者第二个参数小于第一个参数，则返回空数组。var a = ['a', 'b', 'c']; a.slice(4) // [] a.slice(2, 1) // []slice()方法的一个重要应用，是将类似数组的对象转为真正的数组。Array.prototype.slice.call({ 0: 'a', 1: 'b', length: 2 }) // ['a', 'b'] Array.prototype.slice.call(document.querySelectorAll("div")); Array.prototype.slice.call(arguments);上面代码的参数都不是数组，但是通过call方法，在它们上面调用slice()方法，就可以把它们转为真正的数组。splice()splice()方法用于删除原数组的一部分成员，并可以在删除的位置添加新的数组成员，返回值是被删除的元素。注意，该方法会改变原数组。arr.splice(start, count, addElement1, addElement2, ...);splice的第一个参数是删除的起始位置（从0开始），第二个参数是被删除的元素个数。如果后面还有更多的参数，则表示这些就是要被插入数组的新元素。var a = ['a', 'b', 'c', 'd', 'e', 'f']; a.splice(4, 2) // ["e", "f"] a // ["a", "b", "c", "d"]上面代码从原数组4号位置，删除了两个数组成员。var a = ['a', 'b', 'c', 'd', 'e', 'f']; a.splice(4, 2, 1, 2) // ["e", "f"] a // ["a", "b", "c", "d", 1, 2]上面代码除了删除成员，还插入了两个新成员。起始位置如果是负数，就表示从倒数位置开始删除。var a = ['a', 'b', 'c', 'd', 'e', 'f']; a.splice(-4, 2) // ["c", "d"]上面代码表示，从倒数第四个位置c开始删除两个成员。如果只是单纯地插入元素，splice方法的第二个参数可以设为0。var a = [1, 1, 1]; a.splice(1, 0, 2) // [] a // [1, 2, 1, 1]如果只提供第一个参数，等同于将原数组在指定位置拆分成两个数组。var a = [1, 2, 3, 4]; a.splice(2) // [3, 4] a // [1, 2]sort()sort方法对数组成员进行排序，默认是按照字典顺序排序。排序后，原数组将被改变。['d', 'c', 'b', 'a'].sort() // ['a', 'b', 'c', 'd'] [4, 3, 2, 1].sort() // [1, 2, 3, 4] [11, 101].sort() // [101, 11] [10111, 1101, 111].sort() // [10111, 1101, 111]上面代码的最后两个例子，需要特殊注意。sort()方法不是按照大小排序，而是按照字典顺序。也就是说，数值会被先转成字符串，再按照字典顺序进行比较，所以101排在11的前面。如果想让sort方法按照自定义方式排序，可以传入一个函数作为参数。[10111, 1101, 111].sort(function (a, b) { return a - b; }) // [111, 1101, 10111]上面代码中，sort的参数函数本身接受两个参数，表示进行比较的两个数组成员。如果该函数的返回值大于0，表示第一个成员排在第二个成员后面；其他情况下，都是第一个元素排在第二个元素前面。[ { name: "张三", age: 30 }, { name: "李四", age: 24 }, { name: "王五", age: 28 } ].sort(function (o1, o2) { return o1.age - o2.age; }) // [ // { name: "李四", age: 24 }, // { name: "王五", age: 28 }, // { name: "张三", age: 30 } // ]注意，自定义的排序函数应该返回数值，否则不同的浏览器可能有不同的实现，不能保证结果都一致。// bad [1, 4, 2, 6, 0, 6, 2, 6].sort((a, b) => a > b) // good [1, 4, 2, 6, 0, 6, 2, 6].sort((a, b) => a - b)上面代码中，前一种排序算法返回的是布尔值，这是不推荐使用的。后一种是数值，才是更好的写法。map()map()方法将数组的所有成员依次传入参数函数，然后把每一次的执行结果组成一个新数组返回。var numbers = [1, 2, 3]; numbers.map(function (n) { return n + 1; }); // [2, 3, 4] numbers // [1, 2, 3]上面代码中，numbers数组的所有成员依次执行参数函数，运行结果组成一个新数组返回，原数组没有变化。map()方法接受一个函数作为参数。该函数调用时，map()方法向它传入三个参数：当前成员、当前位置和数组本身。[1, 2, 3].map(function(elem, index, arr) { return elem * index; }); // [0, 2, 6]上面代码中，map()方法的回调函数有三个参数，elem为当前成员的值，index为当前成员的位置，arr为原数组（[1, 2, 3]）。map()方法还可以接受第二个参数，用来绑定回调函数内部的this变量（详见《this 变量》一章）。var arr = ['a', 'b', 'c']; [1, 2].map(function (e) { return this[e]; }, arr) // ['b', 'c']上面代码通过map()方法的第二个参数，将回调函数内部的this对象，指向arr数组。如果数组有空位，map()方法的回调函数在这个位置不会执行，会跳过数组的空位。var f = function (n) { return 'a' }; [1, undefined, 2].map(f) // ["a", "a", "a"] [1, null, 2].map(f) // ["a", "a", "a"] [1, , 2].map(f) // ["a", , "a"]上面代码中，map()方法不会跳过undefined和null，但是会跳过空位。forEach()forEach()方法与map()方法很相似，也是对数组的所有成员依次执行参数函数。但是，forEach()方法不返回值，只用来操作数据。这就是说，如果数组遍历的目的是为了得到返回值，那么使用map()方法，否则使用forEach()方法。forEach()的用法与map()方法一致，参数是一个函数，该函数同样接受三个参数：当前值、当前位置、整个数组。function log(element, index, array) { console.log('[' + index + '] = ' + element); } [2, 5, 9].forEach(log); // [0] = 2 // [1] = 5 // [2] = 9上面代码中，forEach()遍历数组不是为了得到返回值，而是为了在屏幕输出内容，所以不必使用map()方法。forEach()方法也可以接受第二个参数，绑定参数函数的this变量。var out = []; [1, 2, 3].forEach(function(elem) { this.push(elem * elem); }, out); out // [1, 4, 9]上面代码中，空数组out是forEach()方法的第二个参数，结果，回调函数内部的this关键字就指向out。注意，forEach()方法无法中断执行，总是会将所有成员遍历完。如果希望符合某种条件时，就中断遍历，要使用for循环。var arr = [1, 2, 3]; for (var i = 0; i < arr.length; i++) { if (arr[i] === 2) break; console.log(arr[i]); } // 1上面代码中，执行到数组的第二个成员时，就会中断执行。forEach()方法做不到这一点。forEach()方法也会跳过数组的空位。var log = function (n) { console.log(n + 1); }; [1, undefined, 2].forEach(log) // 2 // NaN // 3 [1, null, 2].forEach(log) // 2 // 1 // 3 [1, , 2].forEach(log) // 2 // 3上面代码中，forEach()方法不会跳过undefined和null，但会跳过空位。filter()filter()方法用于过滤数组成员，满足条件的成员组成一个新数组返回。它的参数是一个函数，所有数组成员依次执行该函数，返回结果为true的成员组成一个新数组返回。该方法不会改变原数组。[1, 2, 3, 4, 5].filter(function (elem) { return (elem > 3); }) // [4, 5]上面代码将大于3的数组成员，作为一个新数组返回。var arr = [0, 1, 'a', false]; arr.filter(Boolean) // [1, "a"]上面代码中，filter()方法返回数组arr里面所有布尔值为true的成员。filter()方法的参数函数可以接受三个参数：当前成员，当前位置和整个数组。[1, 2, 3, 4, 5].filter(function (elem, index, arr) { return index % 2 === 0; }); // [1, 3, 5]上面代码返回偶数位置的成员组成的新数组。filter()方法还可以接受第二个参数，用来绑定参数函数内部的this变量。var obj = { MAX: 3 }; var myFilter = function (item) { if (item > this.MAX) return true; }; var arr = [2, 8, 3, 4, 1, 3, 2, 9]; arr.filter(myFilter, obj) // [8, 4, 9]上面代码中，过滤器myFilter()内部有this变量，它可以被filter()方法的第二个参数obj绑定，返回大于3的成员。some()，every()这两个方法类似“断言”（assert），返回一个布尔值，表示判断数组成员是否符合某种条件。它们接受一个函数作为参数，所有数组成员依次执行该函数。该函数接受三个参数：当前成员、当前位置和整个数组，然后返回一个布尔值。some方法是只要一个成员的返回值是true，则整个some方法的返回值就是true，否则返回false。var arr = [1, 2, 3, 4, 5]; arr.some(function (elem, index, arr) { return elem >= 3; }); // true上面代码中，如果数组arr有一个成员大于等于3，some方法就返回true。every方法是所有成员的返回值都是true，整个every方法才返回true，否则返回false。var arr = [1, 2, 3, 4, 5]; arr.every(function (elem, index, arr) { return elem >= 3; }); // false上面代码中，数组arr并非所有成员大于等于3，所以返回false。注意，对于空数组，some方法返回false，every方法返回true，回调函数都不会执行。function isEven(x) { return x % 2 === 0 } [].some(isEven) // false [].every(isEven) // truesome和every方法还可以接受第二个参数，用来绑定参数函数内部的this变量。reduce()，reduceRight()reduce()方法和reduceRight()方法依次处理数组的每个成员，最终累计为一个值。它们的差别是，reduce()是从左到右处理（从第一个成员到最后一个成员），reduceRight()则是从右到左（从最后一个成员到第一个成员），其他完全一样。[1, 2, 3, 4, 5].reduce(function (a, b) { console.log(a, b); return a + b; }) // 1 2 // 3 3 // 6 4 // 10 5 //最后结果：15上面代码中，reduce()方法用来求出数组所有成员的和。reduce()的参数是一个函数，数组每个成员都会依次执行这个函数。如果数组有 n 个成员，这个参数函数就会执行 n - 1 次。第一次执行：a是数组的第一个成员1，b是数组的第二个成员2。第二次执行：a为上一轮的返回值3，b为第三个成员3。第三次执行：a为上一轮的返回值6，b为第四个成员4。第四次执行：a为上一轮返回值10，b为第五个成员5。至此所有成员遍历完成，整个方法的返回值就是最后一轮的返回值15。reduce()方法和reduceRight()方法的第一个参数都是一个函数。该函数接受以下四个参数。累积变量。第一次执行时，默认为数组的第一个成员；以后每次执行时，都是上一轮的返回值。当前变量。第一次执行时，默认为数组的第二个成员；以后每次执行时，都是下一个成员。当前位置。一个整数，表示第二个参数（当前变量）的位置，默认为1。原数组。这四个参数之中，只有前两个是必须的，后两个则是可选的。[1, 2, 3, 4, 5].reduce(function ( a, // 累积变量，必须 b, // 当前变量，必须 i, // 当前位置，可选 arr // 原数组，可选 ) { // ... ...如果要对累积变量指定初值，可以把它放在reduce()方法和reduceRight()方法的第二个参数。[1, 2, 3, 4, 5].reduce(function (a, b) { return a + b; }, 10); // 25上面代码指定参数a的初值为10，所以数组从10开始累加，最终结果为25。注意，这时b是从数组的第一个成员开始遍历，参数函数会执行5次。建议总是加上第二个参数，这样比较符合直觉，每个数组成员都会依次执行reduce()方法的参数函数。另外，第二个参数可以防止空数组报错。function add(prev, cur) { return prev + cur; } [].reduce(add) // TypeError: Reduce of empty array with no initial value [].reduce(add, 1) // 1上面代码中，由于空数组取不到累积变量的初始值，reduce()方法会报错。这时，加上第二个参数，就能保证总是会返回一个值。下面是一个reduceRight()方法的例子。function subtract(prev, cur) { return prev - cur; } [3, 2, 1].reduce(subtract) // 0 [3, 2, 1].reduceRight(subtract) // -4上面代码中，reduce()方法相当于3减去2再减去1，reduceRight方法相当于1减去2再减去3。由于这两个方法会遍历数组，所以实际上可以用来做一些遍历相关的操作。比如，找出字符长度最长的数组成员。function findLongest(entries) { return entries.reduce(function (longest, entry) { return entry.length > longest.length ? entry : longest; }, ''); } findLongest(['aaa', 'bb', 'c']) // "aaa"上面代码中，reduce()的参数函数会将字符长度较长的那个数组成员，作为累积值。这导致遍历所有成员之后，累积值就是字符长度最长的那个成员。indexOf()，lastIndexOf()indexOf方法返回给定元素在数组中第一次出现的位置，如果没有出现则返回-1。var a = ['a', 'b', 'c']; a.indexOf('b') // 1 a.indexOf('y') // -1indexOf方法还可以接受第二个参数，表示搜索的开始位置。['a', 'b', 'c'].indexOf('a', 1) // -1上面代码从1号位置开始搜索字符a，结果为-1，表示没有搜索到。lastIndexOf方法返回给定元素在数组中最后一次出现的位置，如果没有出现则返回-1。var a = [2, 5, 9, 2]; a.lastIndexOf(2) // 3 a.lastIndexOf(7) // -1注意，这两个方法不能用来搜索NaN的位置，即它们无法确定数组成员是否包含NaN。[NaN].indexOf(NaN) // -1 [NaN].lastIndexOf(NaN) // -1这是因为这两个方法内部，使用严格相等运算符（===）进行比较，而NaN是唯一一个不等于自身的值。链式使用上面这些数组方法之中，有不少返回的还是数组，所以可以链式使用。var users = [ {name: 'tom', email: 'tom@example.com'}, {name: 'peter', email: 'peter@example.com'} ]; users .map(function (user) { return user.email; }) .filter(function (email) { return /^t/.test(email); }) .forEach(function (email) { console.log(email); }); // "tom@example.com"上面代码中，先产生一个所有 Email 地址组成的数组，然后再过滤出以t开头的 Email 地址，最后将它打印出来。参考链接Nicolas Bevacqua, Fun with JavaScript Native Array Functions
属性描述对象概述JavaScript 提供了一个内部数据结构，用来描述对象的属性，控制它的行为，比如该属性是否可写、可遍历等等。这个内部数据结构称为“属性描述对象”（attributes object）。每个属性都有自己对应的属性描述对象，保存该属性的一些元信息。下面是属性描述对象的一个例子。{ value: 123, writable: false, enumerable: true, configurable: false, get: undefined, set: undefined }属性描述对象提供6个元属性。（1）valuevalue是该属性的属性值，默认为undefined。（2）writablewritable是一个布尔值，表示属性值（value）是否可改变（即是否可写），默认为true。（3）enumerableenumerable是一个布尔值，表示该属性是否可遍历，默认为true。如果设为false，会使得某些操作（比如for...in循环、Object.keys()）跳过该属性。（4）configurableconfigurable是一个布尔值，表示属性的可配置性，默认为true。如果设为false，将阻止某些操作改写属性描述对象，比如无法删除该属性，也不得改变各种元属性（value属性除外）。也就是说，configurable属性控制了属性描述对象的可写性。（5）getget是一个函数，表示该属性的取值函数（getter），默认为undefined。（6）setset是一个函数，表示该属性的存值函数（setter），默认为undefined。Object.getOwnPropertyDescriptor()Object.getOwnPropertyDescriptor()方法可以获取属性描述对象。它的第一个参数是目标对象，第二个参数是一个字符串，对应目标对象的某个属性名。var obj = { p: 'a' }; Object.getOwnPropertyDescriptor(obj, 'p') // Object { value: "a", // writable: true, // enumerable: true, // configurable: true // }上面代码中，Object.getOwnPropertyDescriptor()方法获取obj.p的属性描述对象。注意，Object.getOwnPropertyDescriptor()方法只能用于对象自身的属性，不能用于继承的属性。var obj = { p: 'a' }; Object.getOwnPropertyDescriptor(obj, 'toString') // undefined上面代码中，toString是obj对象继承的属性，Object.getOwnPropertyDescriptor()无法获取。Object.getOwnPropertyNames()Object.getOwnPropertyNames方法返回一个数组，成员是参数对象自身的全部属性的属性名，不管该属性是否可遍历。var obj = Object.defineProperties({}, { p1: { value: 1, enumerable: true }, p2: { value: 2, enumerable: false } }); Object.getOwnPropertyNames(obj) // ["p1", "p2"]上面代码中，obj.p1是可遍历的，obj.p2是不可遍历的。Object.getOwnPropertyNames会将它们都返回。这跟Object.keys的行为不同，Object.keys只返回对象自身的可遍历属性的全部属性名。Object.keys([]) // [] Object.getOwnPropertyNames([]) // [ 'length' ] Object.keys(Object.prototype) // [] Object.getOwnPropertyNames(Object.prototype) // ['hasOwnProperty', // 'valueOf', // 'constructor', // 'toLocaleString', // 'isPrototypeOf', // 'propertyIsEnumerable', // 'toString']上面代码中，数组自身的length属性是不可遍历的，Object.keys不会返回该属性。第二个例子的Object.prototype也是一个对象，所有实例对象都会继承它，它自身的属性都是不可遍历的。Object.defineProperty()，Object.defineProperties()Object.defineProperty()方法允许通过属性描述对象，定义或修改一个属性，然后返回修改后的对象，它的用法如下。Object.defineProperty(object, propertyName, attributesObject)Object.defineProperty方法接受三个参数，依次如下。object：属性所在的对象propertyName：字符串，表示属性名attributesObject：属性描述对象举例来说，定义obj.p可以写成下面这样。var obj = Object.defineProperty({}, 'p', { value: 123, writable: false, enumerable: true, configurable: false }); obj.p // 123 obj.p = 246; obj.p // 123上面代码中，Object.defineProperty()方法定义了obj.p属性。由于属性描述对象的writable属性为false，所以obj.p属性不可写。注意，这里的Object.defineProperty方法的第一个参数是{}（一个新建的空对象），p属性直接定义在这个空对象上面，然后返回这个对象，这是Object.defineProperty()的常见用法。如果属性已经存在，Object.defineProperty()方法相当于更新该属性的属性描述对象。如果一次性定义或修改多个属性，可以使用Object.defineProperties()方法。var obj = Object.defineProperties({}, { p1: { value: 123, enumerable: true }, p2: { value: 'abc', enumerable: true }, p3: { get: function () { return this.p1 + this.p2 }, enumerable:true, configurable:true } }); obj.p1 // 123 obj.p2 // "abc" obj.p3 // "123abc"上面代码中，Object.defineProperties()同时定义了obj对象的三个属性。其中，p3属性定义了取值函数get，即每次读取该属性，都会调用这个取值函数。注意，一旦定义了取值函数get（或存值函数set），就不能将writable属性设为true，或者同时定义value属性，否则会报错。var obj = {}; Object.defineProperty(obj, 'p', { value: 123, get: function() { return 456; } }); // TypeError: Invalid property. // A property cannot both have accessors and be writable or have a value Object.defineProperty(obj, 'p', { writable: true, get: function() { return 456; } }); // TypeError: Invalid property descriptor. // Cannot both specify accessors and a value or writable attribute上面代码中，同时定义了get属性和value属性，以及将writable属性设为true，就会报错。Object.defineProperty()和Object.defineProperties()参数里面的属性描述对象，writable、configurable、enumerable这三个属性的默认值都为false。var obj = {}; Object.defineProperty(obj, 'foo', {}); Object.getOwnPropertyDescriptor(obj, 'foo') // { // value: undefined, // writable: false, // enumerable: false, // configurable: false // }上面代码中，定义obj.foo时用了一个空的属性描述对象，就可以看到各个元属性的默认值。Object.prototype.propertyIsEnumerable()实例对象的propertyIsEnumerable()方法返回一个布尔值，用来判断某个属性是否可遍历。注意，这个方法只能用于判断对象自身的属性，对于继承的属性一律返回false。var obj = {}; obj.p = 123; obj.propertyIsEnumerable('p') // true obj.propertyIsEnumerable('toString') // false上面代码中，obj.p是可遍历的，而obj.toString是继承的属性。元属性属性描述对象的各个属性称为“元属性”，因为它们可以看作是控制属性的属性。valuevalue属性是目标属性的值。var obj = {}; obj.p = 123; Object.getOwnPropertyDescriptor(obj, 'p').value // 123 Object.defineProperty(obj, 'p', { value: 246 }); obj.p // 246上面代码是通过value属性，读取或改写obj.p的例子。writablewritable属性是一个布尔值，决定了目标属性的值（value）是否可以被改变。var obj = {}; Object.defineProperty(obj, 'a', { value: 37, writable: false }); obj.a // 37 obj.a = 25; obj.a // 37上面代码中，obj.a的writable属性是false。然后，改变obj.a的值，不会有任何效果。注意，正常模式下，对writable为false的属性赋值不会报错，只会默默失败。但是，严格模式下会报错，即使对a属性重新赋予一个同样的值。'use strict'; var obj = {}; Object.defineProperty(obj, 'a', { value: 37, writable: false }); obj.a = 37; // Uncaught TypeError: Cannot assign to read only property 'a' of object上面代码是严格模式，对obj.a任何赋值行为都会报错。如果原型对象的某个属性的writable为false，那么子对象将无法自定义这个属性。var proto = Object.defineProperty({}, 'foo', { value: 'a', writable: false }); var obj = Object.create(proto); obj.foo = 'b'; obj.foo // 'a'上面代码中，proto是原型对象，它的foo属性不可写。obj对象继承proto，也不可以再自定义这个属性了。如果是严格模式，这样做还会抛出一个错误。但是，有一个规避方法，就是通过覆盖属性描述对象，绕过这个限制。原因是这种情况下，原型链会被完全忽视。var proto = Object.defineProperty({}, 'foo', { value: 'a', writable: false }); var obj = Object.create(proto); Object.defineProperty(obj, 'foo', { value: 'b' }); obj.foo // "b"enumerableenumerable（可遍历性）返回一个布尔值，表示目标属性是否可遍历。JavaScript 的早期版本，for...in循环是基于in运算符的。我们知道，in运算符不管某个属性是对象自身的还是继承的，都会返回true。var obj = {}; 'toString' in obj // true上面代码中，toString不是obj对象自身的属性，但是in运算符也返回true，这导致了toString属性也会被for...in循环遍历。这显然不太合理，后来就引入了“可遍历性”这个概念。只有可遍历的属性，才会被for...in循环遍历，同时还规定toString这一类实例对象继承的原生属性，都是不可遍历的，这样就保证了for...in循环的可用性。具体来说，如果一个属性的enumerable为false，下面三个操作不会取到该属性。for..in循环Object.keys方法JSON.stringify方法因此，enumerable可以用来设置“秘密”属性。var obj = {}; Object.defineProperty(obj, 'x', { value: 123, enumerable: false }); obj.x // 123 for (var key in obj) { console.log(key); } // undefined Object.keys(obj) // [] JSON.stringify(obj) // "{}"上面代码中，obj.x属性的enumerable为false，所以一般的遍历操作都无法获取该属性，使得它有点像“秘密”属性，但不是真正的私有属性，还是可以直接获取它的值。注意，for...in循环包括继承的属性，Object.keys方法不包括继承的属性。如果需要获取对象自身的所有属性，不管是否可遍历，可以使用Object.getOwnPropertyNames方法。另外，JSON.stringify方法会排除enumerable为false的属性，有时可以利用这一点。如果对象的 JSON 格式输出要排除某些属性，就可以把这些属性的enumerable设为false。configurableconfigurable(可配置性）返回一个布尔值，决定了是否可以修改属性描述对象。也就是说，configurable为false时，writable、enumerable和configurable都不能被修改了。var obj = Object.defineProperty({}, 'p', { value: 1, writable: false, enumerable: false, configurable: false }); Object.defineProperty(obj, 'p', {writable: true}) // TypeError: Cannot redefine property: p Object.defineProperty(obj, 'p', {enumerable: true}) // TypeError: Cannot redefine property: p Object.defineProperty(obj, 'p', {configurable: true}) // TypeError: Cannot redefine property: p Object.defineProperty(obj, 'p', {value: 2}) // TypeError: Cannot redefine property: p上面代码中，obj.p的configurable属性为false。然后，改动writable、enumerable、configurable，结果都报错。注意，writable属性只有在false改为true时会报错，true改为false是允许的。var obj = Object.defineProperty({}, 'p', { writable: true, configurable: false }); Object.defineProperty(obj, 'p', {writable: false}) // 修改成功value属性的情况比较特殊。只要writable和configurable有一个为true，就允许改动value。var o1 = Object.defineProperty({}, 'p', { value: 1, writable: true, configurable: false }); Object.defineProperty(o1, 'p', {value: 2}) // 修改成功 var o2 = Object.defineProperty({}, 'p', { value: 1, writable: false, configurable: true }); Object.defineProperty(o2, 'p', {value: 2}) // 修改成功另外，writable为false时，直接对目标属性赋值，不报错，但不会成功。var obj = Object.defineProperty({}, 'p', { value: 1, writable: false, configurable: false }); obj.p = 2; obj.p // 1上面代码中，obj.p的writable为false，对obj.p直接赋值不会生效。如果是严格模式，还会报错。可配置性决定了目标属性是否可以被删除（delete）。var obj = Object.defineProperties({}, { p1: { value: 1, configurable: true }, p2: { value: 2, configurable: false } }); delete obj.p1 // true delete obj.p2 // false obj.p1 // undefined obj.p2 // 2上面代码中，obj.p1的configurable是true，所以可以被删除，obj.p2就无法删除。存取器除了直接定义以外，属性还可以用存取器（accessor）定义。其中，存值函数称为setter，使用属性描述对象的set属性；取值函数称为getter，使用属性描述对象的get属性。一旦对目标属性定义了存取器，那么存取的时候，都将执行对应的函数。利用这个功能，可以实现许多高级特性，比如定制属性的读取和赋值行为。var obj = Object.defineProperty({}, 'p', { get: function () { return 'getter'; }, set: function (value) { console.log('setter: ' + value); } }); obj.p // "getter" obj.p = 123 // "setter: 123"上面代码中，obj.p定义了get和set属性。obj.p取值时，就会调用get；赋值时，就会调用set。JavaScript 还提供了存取器的另一种写法。// 写法二 var obj = { get p() { return 'getter'; }, set p(value) { console.log('setter: ' + value); } };上面两种写法，虽然属性p的读取和赋值行为是一样的，但是有一些细微的区别。第一种写法，属性p的configurable和enumerable都为false，从而导致属性p是不可遍历的；第二种写法，属性p的configurable和enumerable都为true，因此属性p是可遍历的。实际开发中，写法二更常用。注意，取值函数get不能接受参数，存值函数set只能接受一个参数（即属性的值）。存取器往往用于，属性的值依赖对象内部数据的场合。var obj ={ $n : 5, get next() { return this.$n++ }, set next(n) { if (n >= this.$n) this.$n = n; else throw new Error('新的值必须大于当前值'); } }; obj.next // 5 obj.next = 10; obj.next // 10 obj.next = 5; // Uncaught Error: 新的值必须大于当前值上面代码中，next属性的存值函数和取值函数，都依赖于内部属性$n。对象的拷贝有时，我们需要将一个对象的所有属性，拷贝到另一个对象，可以用下面的方法实现。var extend = function (to, from) { for (var property in from) { to[property] = from[property]; } return to; } extend({}, { a: 1 }) // {a: 1}上面这个方法的问题在于，如果遇到存取器定义的属性，会只拷贝值。extend({}, { get a() { return 1 } }) // {a: 1}为了解决这个问题，我们可以通过Object.defineProperty方法来拷贝属性。var extend = function (to, from) { for (var property in from) { if (!from.hasOwnProperty(property)) continue; Object.defineProperty( to, property, Object.getOwnPropertyDescriptor(from, property) ); } return to; } extend({}, { get a(){ return 1 } }) // { get a(){ return 1 } })上面代码中，hasOwnProperty那一行用来过滤掉继承的属性，否则可能会报错，因为Object.getOwnPropertyDescriptor读不到继承属性的属性描述对象。控制对象状态有时需要冻结对象的读写状态，防止对象被改变。JavaScript 提供了三种冻结方法，最弱的一种是Object.preventExtensions，其次是Object.seal，最强的是Object.freeze。Object.preventExtensions()Object.preventExtensions方法可以使得一个对象无法再添加新的属性。var obj = new Object(); Object.preventExtensions(obj); Object.defineProperty(obj, 'p', { value: 'hello' }); // TypeError: Cannot define property:p, object is not extensible. obj.p = 1; obj.p // undefined上面代码中，obj对象经过Object.preventExtensions以后，就无法添加新属性了。Object.isExtensible()Object.isExtensible方法用于检查一个对象是否使用了Object.preventExtensions方法。也就是说，检查是否可以为一个对象添加属性。var obj = new Object(); Object.isExtensible(obj) // true Object.preventExtensions(obj); Object.isExtensible(obj) // false上面代码中，对obj对象使用Object.preventExtensions方法以后，再使用Object.isExtensible方法，返回false，表示已经不能添加新属性了。Object.seal()Object.seal方法使得一个对象既无法添加新属性，也无法删除旧属性。var obj = { p: 'hello' }; Object.seal(obj); delete obj.p; obj.p // "hello" obj.x = 'world'; obj.x // undefined上面代码中，obj对象执行Object.seal方法以后，就无法添加新属性和删除旧属性了。Object.seal实质是把属性描述对象的configurable属性设为false，因此属性描述对象不再能改变了。var obj = { p: 'a' }; // seal方法之前 Object.getOwnPropertyDescriptor(obj, 'p') // Object { // value: "a", // writable: true, // enumerable: true, // configurable: true // } Object.seal(obj); // seal方法之后 Object.getOwnPropertyDescriptor(obj, 'p') // Object { // value: "a", // writable: true, // enumerable: true, // configurable: false // } Object.defineProperty(obj, 'p', { enumerable: false }) // TypeError: Cannot redefine property: p上面代码中，使用Object.seal方法之后，属性描述对象的configurable属性就变成了false，然后改变enumerable属性就会报错。Object.seal只是禁止新增或删除属性，并不影响修改某个属性的值。var obj = { p: 'a' }; Object.seal(obj); obj.p = 'b'; obj.p // 'b'上面代码中，Object.seal方法对p属性的value无效，是因为此时p属性的可写性由writable决定。Object.isSealed()Object.isSealed方法用于检查一个对象是否使用了Object.seal方法。var obj = { p: 'a' }; Object.seal(obj); Object.isSealed(obj) // true这时，Object.isExtensible方法也返回false。var obj = { p: 'a' }; Object.seal(obj); Object.isExtensible(obj) // falseObject.freeze()Object.freeze方法可以使得一个对象无法添加新属性、无法删除旧属性、也无法改变属性的值，使得这个对象实际上变成了常量。var obj = { p: 'hello' }; Object.freeze(obj); obj.p = 'world'; obj.p // "hello" obj.t = 'hello'; obj.t // undefined delete obj.p // false obj.p // "hello"上面代码中，对obj对象进行Object.freeze()以后，修改属性、新增属性、删除属性都无效了。这些操作并不报错，只是默默地失败。如果在严格模式下，则会报错。Object.isFrozen()Object.isFrozen方法用于检查一个对象是否使用了Object.freeze方法。var obj = { p: 'hello' }; Object.freeze(obj); Object.isFrozen(obj) // true使用Object.freeze方法以后，Object.isSealed将会返回true，Object.isExtensible返回false。var obj = { p: 'hello' }; Object.freeze(obj); Object.isSealed(obj) // true Object.isExtensible(obj) // falseObject.isFrozen的一个用途是，确认某个对象没有被冻结后，再对它的属性赋值。var obj = { p: 'hello' }; Object.freeze(obj); if (!Object.isFrozen(obj)) { obj.p = 'world'; }上面代码中，确认obj没有被冻结后，再对它的属性赋值，就不会报错了。局限性上面的三个方法锁定对象的可写性有一个漏洞：可以通过改变原型对象，来为对象增加属性。var obj = new Object(); Object.preventExtensions(obj); var proto = Object.getPrototypeOf(obj); proto.t = 'hello'; obj.t // hello上面代码中，对象obj本身不能新增属性，但是可以在它的原型对象上新增属性，就依然能够在obj上读到。一种解决方案是，把obj的原型也冻结住。var obj = new Object(); Object.preventExtensions(obj); var proto = Object.getPrototypeOf(obj); Object.preventExtensions(proto); proto.t = 'hello'; obj.t // undefined另外一个局限是，如果属性值是对象，上面这些方法只能冻结属性指向的对象，而不能冻结对象本身的内容。var obj = { foo: 1, bar: ['a', 'b'] }; Object.freeze(obj); obj.bar.push('c'); obj.bar // ["a", "b", "c"]上面代码中，obj.bar属性指向一个数组，obj对象被冻结以后，这个指向无法改变，即无法指向其他值，但是所指向的数组是可以改变的。