来自内蒙古的田园蒙牛

来自内蒙古的田园蒙牛

五、标准库

1,Object 对象

JavaScript 原生提供 Object 对象(注意起首的 O 是大写),本章介绍该对象原生的各种方 法。 JavaScript 的所有其他对象都继承自 Object 对象,即那些对象都是 Object 的实例。 Object 对象的原生方法分成两类: Object 本身的方法与 Object 的实例方法。

(1) Object 对象本身的方法

所谓“本身的方法”就是直接定义在 Object 对象的方法。

1. Object.print = function (o) { console.log(o) };

(2) Object 的实例方法

所谓实例方法就是定义在 Object 原型对象 Object.prototype 上的方法。它可以被 Object 实 例直接使用。

1. Object.prototype.print = function () {
2.     console.log(this);
3. };
4.
5. var obj = new Object();
6. obj.print() // Object

上面代码中, Object.prototype 定义了一个 print 方法,然后生成一个 Object 的实 例 obj 。 obj 直接继承了 Object.prototype 的属性和方法,可以直接使用 obj.print 调 用 print 方法。也就是说, obj 对象的 print 方法实质上就是调 用 Object.prototype.print 方法。

Object 本身是一个函数,可以当作工具方法使用,将任意值转为对象。这个方法常用于保证某个值 一定是对象。 如果参数为空(或者为 undefined 和 null ), Object() 返回一个空对象。

1. var obj = Object();
2. // 等同于
3. var obj = Object(undefined);
4. var obj = Object(null);
5.
6. obj instanceof Object // true

instanceof 运算符用来验证,一个对象是否为指定的构造函数的实例。 obj instanceof Object 返回 true ,就表示 obj 对象是 Object 的实例。

1. var obj = Object(1);
2. obj instanceof Object // true
3. obj instanceof Number // true
4.
5. var obj = Object('foo');
6. obj instanceof Object // true
7. obj instanceof String // true
8.
9. var obj = Object(true);
10. obj instanceof Object // true
11. obj instanceof Boolean // true

如果 Object 方法的参数是一个对象,它总是返回该对象,即不用转换。

1. var arr = [];
2. var obj = Object(arr); // 返回原数组
3. obj === arr // true
4.
5. var value = {};
6. var obj = Object(value) // 返回原对象
7. obj === value // true
8.
9. var fn = function () {};
10. var obj = Object(fn); // 返回原函数
11. obj === fn // true

利用这一点,可以写一个判断变量是否为对象的函数。

1. function isObject(value) {
2.     return value === Object(value);
3. }
4.
5. isObject([]) // true
6. isObject(true) // false

Object 不仅可以当作工具函数使用,还可以当作构造函数使用,即前面可以使用 new 命令。 Object 构造函数的首要用途,是直接通过它来生成新对象。

1. var obj = new Object();

Object 构造函数的用法与工具方法很相似,几乎一模一样。使用时,可以接受一个参数,如果该参 数是一个对象,则直接返回这个对象;如果是一个原始类型的值,则返回该值对应的包装对象

1. var o1 = {a: 1};
2. var o2 = new Object(o1);
3. o1 === o2 // true
4.
5. var obj = new Object(123);
6. obj instanceof Number // true

虽然用法相似,但是 Object(value) 与 new Object(value) 两者的语义是不同的, Object(value) 表示将 value 转成一个对象, new Object(value) 则表示新生成一个对 象,它的值是 value 。

Object 的静态方法:所谓“静态方法”,是指部署在 Object 对象自身的方法。

Object.keys(),Object.getOwnPropertyNames()

Object.keys 方法和 Object.getOwnPropertyNames 方法都用来遍历对象的属性。 Object.keys 方法的参数是一个对象,返回一个数组。该数组的成员都是该对象自身的(而不是继 承的)所有属性名。

1. var obj = {
2.     p1: 123,
3.     p2: 456
4. };
5.
6. Object.keys(obj) // ["p1", "p2"]

Object.getOwnPropertyNames 方法与 Object.keys 类似,也是接受一个对象作为参数,返回一 个数组,包含了该对象自身的所有属性名。

1. var obj = {
2.     p1: 123,
3.     p2: 456
4. };
5.
6. Object.getOwnPropertyNames(obj) // ["p1", "p2"]

对于一般的对象来说, Object.keys() 和 Object.getOwnPropertyNames() 返回的结果是一样 的。只有涉及不可枚举属性时,才会有不一样的结果。

1. var a = ['Hello', 'World'];
2.
3. Object.keys(a) // ["0", "1"]
4. Object.getOwnPropertyNames(a) // ["0", "1", "length"]

在 Object.getOwnPropertyNames 方法的返回结果中。 由于 JavaScript 没有提供计算对象属性个数的方法,所以可以用这两个方法代替。

1. var obj = {
2.     p1: 123,
3.     p2: 456
4. };
5.
6. Object.keys(obj).length // 2
7. Object.getOwnPropertyNames(obj).length // 2

除了上面提到的两个方法, Object 还有不少其他静态方法:

(1)对象属性模型的相关方法

        Object.getOwnPropertyDescriptor() :获取某个属性的描述对象。

        Object.defineProperty() :通过描述对象,定义某个属性。

        Object.defineProperties() :通过描述对象,定义多个属性。

(2)控制对象状态的方法

        Object.preventExtensions() :防止对象扩展。

        Object.isExtensible() :判断对象是否可扩展。

        Object.seal() :禁止对象配置。

        Object.isSealed() :判断一个对象是否可配置。

        Object.freeze() :冻结一个对象。

        Object.isFrozen() :判断一个对象是否被冻结。

(3)原型链相关方法

        Object.create() :该方法可以指定原型对象和属性,返回一个新的对象。         Object.getPrototypeOf() :获取对象的 Prototype 对象。

Object 的实例方法:除了静态方法,还有不少方法定义在 Object.prototype 对象。它们称为实例方法,所 有 Object 的实例对象都继承了这些方法。

Object 实例对象的方法,主要有以下六个。

        Object.prototype.valueOf() :返回当前对象对应的值。

        Object.prototype.toString() :返回当前对象对应的字符串形式。         Object.prototype.toLocaleString() :返回当前对象对应的本地字符串形式。         Object.prototype.hasOwnProperty() :判断某个属性是否为当前对象自身的属性,还是继承 自原型对象的属性。

        Object.prototype.isPrototypeOf() :判断当前对象是否为另一个对象的原型。         Object.prototype.propertyIsEnumerable() :判断某个属性是否可枚举。

Object.prototype.valueOf()

valueOf 方法的作用是返回一个对象的“值”,默认情况下返回对象本身。

1. var obj = new Object();
2. obj.valueOf() === obj // true

上面代码比较 obj.valueOf() 与 obj 本身,两者是一样的。 valueOf 方法的主要用途是,JavaScript 自动类型转换时会默认调用这个方法。

1. var obj = new Object();
2. 1 + obj // "1[object Object]"

上面代码将对象 obj 与数字 1 相加,这时 JavaScript 就会默认调用 valueOf() 方法,求 出 obj 的值再与 1 相加。所以,如果自定义 valueOf 方法,就可以得到想要的结果。

1. var obj = new Object();
2. obj.valueOf = function () {
3.     return 2;
4. };
5.
6. 1 + obj // 3

上面代码自定义了 obj 对象的 valueOf 方法,于是 1 + obj 就得到了 3 。这种方法就相当 于用自定义的 obj.valueOf ,覆盖 Object.prototype.valueOf 。

Object.prototype.toString()

toString 方法的作用是返回一个对象的字符串形式,默认情况下返回类型字符串。

1. var o1 = new Object();
2. o1.toString() // "[object Object]"
3.
4. var o2 = {a:1};
5. o2.toString() // "[object Object]"

上面代码表示,对于一个对象调用 toString 方法,会返回字符串 [object Object] ,该字符串 说明对象的类型。 字符串 [object Object] 本身没有太大的用处,但是通过自定义 toString 方法,可以让对象在 自动类型转换时,得到想要的字符串形式。

1. var obj = new Object();
2.
3. obj.toString = function () {
4.     return 'hello';
5. };
6.
7. obj + ' ' + 'world' // "hello world"

上面代码表示,当对象用于字符串加法时,会自动调用 toString 方法。由于自定义 了 toString 方法,所以返回字符串 hello world 。 数组、字符串、函数、Date 对象都分别部署了自定义的 toString 方法,覆盖 了 Object.prototype.toString 方法。

1. [1, 2, 3].toString() // "1,2,3"
2.
3. '123'.toString() // "123"
4.
5. (function () {
6.     return 123;
7. }).toString()
8. // "function () {
9. // return 123;
10. // }"
11.
12. (new Date()).toString()
13. // "Tue May 10 2016 09:11:31 GMT+0800 (CST)"

上面代码中,数组、字符串、函数、Date 对象调用 toString 方法,并不会返回 [object Object] ,因为它们都自定义了 toString 方法,覆盖原始方法。

toString() 的应用:判断数据类型

Object.prototype.toString 方法返回对象的类型字符串,因此可以用来判断一个值的类型。

1. var obj = {};
2. obj.toString() // "[object Object]"

上面代码调用空对象的 toString 方法,结果返回一个字符串 object Object ,其中第二 个 Object 表示该值的构造函数。这是一个十分有用的判断数据类型的方法。 由于实例对象可能会自定义 toString 方法,覆盖掉 Object.prototype.toString 方法,所以为 了得到类型字符串,最好直接使用 Object.prototype.toString 方法。通过函数的 call 方法, 可以在任意值上调用这个方法,帮助我们判断这个值的类型。

1. Object.prototype.toString.call(value)

上面代码表示对 value 这个值调用 Object.prototype.toString 方法。 不同数据类型的 Object.prototype.toString 方法返回值如下。

        数值:返回 [object Number] 。

        字符串:返回 [object String] 。

        布尔值:返回 [object Boolean] 。

        undefined:返回 [object Undefined] 。

        null:返回 [object Null] 。

        数组:返回 [object Array] 。

        arguments 对象:返回 [object Arguments] 。

        函数:返回 [object Function] 。

        Error 对象:返回 [object Error] 。

        Date 对象:返回 [object Date] 。

        RegExp 对象:返回 [object RegExp] 。

        其他对象:返回 [object Object] 。

Object.prototype.toString 可以看出一个值到底是什么类型。

1. Object.prototype.toString.call(2) // "[object Number]"
2. Object.prototype.toString.call('') // "[object String]"
3. Object.prototype.toString.call(true) // "[object Boolean]"
4. Object.prototype.toString.call(undefined) // "[object Undefined]"
5. Object.prototype.toString.call(null) // "[object Null]"
6. Object.prototype.toString.call(Math) // "[object Math]"
7. Object.prototype.toString.call({}) // "[object Object]"
8. Object.prototype.toString.call([]) // "[object Array]"

利用这个特性,可以写出一个比 typeof 运算符更准确的类型判断函数。

1. var type = function (o){
2.     var s = Object.prototype.toString.call(o);
3.     return s.match(/\[object (.*?)\]/)[1].toLowerCase();
4. };
5.
6. type({}); // "object"
7. type([]); // "array"
8. type(5); // "number"
9. type(null); // "null"
10. type(); // "undefined"
11. type(/abcd/); // "regex"
12. type(new Date()); // "date"

在上面这个 type 函数的基础上,还可以加上专门判断某种类型数据的方法。

1. var type = function (o){
2.     var s = Object.prototype.toString.call(o);
3.     return s.match(/\[object (.*?)\]/)[1].toLowerCase();
4. };
5.
6. ['Null',
7.     'Undefined',
8.     'Object',
9.     'Array',
10.    'String',
11.    'Number',
12.    'Boolean',
13.    'Function',
14.    'RegExp'
15. ].forEach(function (t) {
16.     type['is' + t] = function (o) {
17.         return type(o) === t.toLowerCase();
18.     };
19. });
20.
21. type.isObject({}) // true
22. type.isNumber(NaN) // true
23. type.isRegExp(/abc/) // true

Object.prototype.toLocaleString()

Object.prototype.toLocaleString 方法与 toString 的返回结果相同,也是返回一个值的字符 串形式。

1. var obj = {};
2. obj.toString(obj) // "[object Object]"
3. obj.toLocaleString(obj) // "[object Object]"

这个方法的主要作用是留出一个接口,让各种不同的对象实现自己版本的 toLocaleString ,用来返 回针对某些地域的特定的值。

1. var person = {
2.     toString: function () {
3.         return 'Henry Norman Bethune';
4.     },
5.     toLocaleString: function () {
6.         return '小白';
7.     }
8. };
9.
10. person.toString() // Henry Norman Bethune
11. person.toLocaleString() // 小白

上面代码中, toString() 方法返回对象的一般字符串形式, toLocaleString() 方法返回本地的 字符串形式。 目前,主要有三个对象自定义了 toLocaleString 方法。

        Array.prototype.toLocaleString()

        Number.prototype.toLocaleString()

        Date.prototype.toLocaleString()

例如:日期的实例对象的 toString 和 toLocaleString 返回值就不一样,而 且 toLocaleString 的返回值跟用户设定的所在地域相关。

1. var date = new Date();
2. date.toString() // "Tue Jan 01 2018 12:01:33 GMT+0800 (CST)"
3. date.toLocaleString() // "1/01/2018, 12:01:33 PM"

Object.prototype.hasOwnProperty()

Object.prototype.hasOwnProperty 方法接受一个字符串作为参数,返回一个布尔值,表示该实例 对象自身是否具有该属性。

1. var obj = {
2.     p: 123
3. };
4.
5. obj.hasOwnProperty('p') // true
6. obj.hasOwnProperty('toString') // false

上面代码中,对象 obj 自身具有 p 属性,所以返回true 。toString属性是继承的,所以返回false 。

2,属性描述对象

JavaScript 提供了一个内部数据结构,用来描述对象的属性,控制它的行为,比如该属性是否可 写、可遍历等等。这个内部数据结构称为“属性描述对象”(attributes object)。每个属性都有自 己对应的属性描述对象,保存该属性的一些元信息。

下面是属性描述对象的一个例子。

1. {
2.     value: 123,
3.     writable: false,
4.     enumerable: true,
5.     configurable: false,
6.     get: undefined,
7.     set: undefined
8. }

属性描述对象提供6个元属性。

(1) value :value 是该属性的属性值,默认为 undefined 。

(2) writable :writable 是一个布尔值,表示属性值(value)是否可改变(即是否可写),默认为 true 。

(3) enumerable :enumerable 是一个布尔值,表示该属性是否可遍历,默认为 true 。如果设为 false ,会使得 某些操作(比如 for...in 循环、 Object.keys() )跳过该属性。

(4) configurable :configurable 是一个布尔值,表示可配置性,默认为 true 。如果设为 false ,将阻止某些操 作改写该属性,比如无法删除该属性,也不得改变该属性的属性描述对象( value 属性除外)。也就 是说, configurable 属性控制了属性描述对象的可写性。

(5) get:get 是一个函数,表示该属性的取值函数(getter),默认为 undefined 。

(6) set :set 是一个函数,表示该属性的存值函数(setter),默认为 undefined 。

Object.getOwnPropertyDescriptor()

Object.getOwnPropertyDescriptor() 方法可以获取属性描述对象。它的第一个参数是目标对象, 第二个参数是一个字符串,对应目标对象的某个属性名。

1. var obj = { p: 'a' };
2.
3. Object.getOwnPropertyDescriptor(obj, 'p')
4. // Object { value: "a",
5. // writable: true,
6. // enumerable: true,
7. // configurable: true
8. // }

上面代码中, Object.getOwnPropertyDescriptor() 方法获取 obj.p 的属性描述对象。 注意, Object.getOwnPropertyDescriptor() 方法只能用于对象自身的属性,不能用于继承的属 性。

1. var obj = { p: 'a' };
2.
3. Object.getOwnPropertyDescriptor(obj, 'toString')
4. // undefined

上面代码中, toString 是 obj 对象继承的属性, Object.getOwnPropertyDescriptor() 无法 获取。

Object.getOwnPropertyNames()

Object.getOwnPropertyNames 方法返回一个数组,成员是参数对象自身的全部属性的属性名,不管 该属性是否可遍历。

1. var obj = Object.defineProperties({}, {
2.     p1: { value: 1, enumerable: true },
3.     p2: { value: 2, enumerable: false }
4. });
5.
6. Object.getOwnPropertyNames(obj)
7. // ["p1", "p2"]

上面代码中, obj.p1 是可遍历的, obj.p2 是不可遍历的。 Object.getOwnPropertyNames 会 将它们都返回。 这跟 Object.keys 的行为不同, Object.keys 只返回对象自身的可遍历属性的全部属性名。

1. Object.keys([]) // []
2. Object.getOwnPropertyNames([]) // [ 'length' ]
3.
4. Object.keys(Object.prototype) // []
5. Object.getOwnPropertyNames(Object.prototype)
6. // ['hasOwnProperty',
7. // 'valueOf',
8. // 'constructor',
9. // 'toLocaleString',
10. // 'isPrototypeOf',
11. // 'propertyIsEnumerable',
12. // 'toString']

上面代码中,数组自身的 length 属性是不可遍历的, Object.keys 不会返回该属性。第二个例 子的 Object.prototype 也是一个对象,所有实例对象都会继承它,它自身的属性都是不可遍历的。

Object.defineProperty(), Object.defineProperties()

Object.defineProperty() 方法允许通过属性描述对象,定义或修改一个属性,然后返回修改后的 对象,它的用法如下。

1. Object.defineProperty(object, propertyName, attributesObject)

Object.defineProperty 方法接受三个参数,依次如下。

        object:属性所在的对象

        propertyName:字符串,表示属性名

        attributesObject:属性描述对象

举例来说,定义 obj.p 可以写成下面这样。

1. var obj = Object.defineProperty({}, 'p', {
2.     value: 123,
3.     writable: false,
4.     enumerable: true,
5.     configurable: false
6. });
7.
8. obj.p // 123
9.
10. obj.p = 246;
11. obj.p // 123

上面代码中, Object.defineProperty() 方法定义了 obj.p 属性。由于属性描述对象 的 writable 属性为 false ,所以 obj.p 属性不可写。注意,这里 的 Object.defineProperty 方法的第一个参数是 {} (一个新建的空对象), p 属性直接定义 在这个空对象上面,然后返回这个对象,这是 Object.defineProperty() 的常见用法。 如果属性已经存在, Object.defineProperty() 方法相当于更新该属性的属性描述对象。 如果一次性定义或修改多个属性,可以使用 Object.defineProperties() 方法。

1. var obj = Object.defineProperties({}, {
2.     p1: { value: 123, enumerable: true },
3.     p2: { value: 'abc', enumerable: true },
4.     p3: { get: function () { return this.p1 + this.p2 },
5.         enumerable:true,
6.         configurable:true
7.     }
8. });
9.
10. obj.p1 // 123
11. obj.p2 // "abc"
12. obj.p3 // "123abc"

上面代码中, Object.defineProperties() 同时定义了 obj 对象的三个属性。其中, p3 属性 定义了取值函数 get ,即每次读取该属性,都会调用这个取值函数。 注意,一旦定义了取值函数 get (或存值函数 set ),就不能将 writable 属性设 为 true ,或者同时定义 value 属性,否则会报错。

1. var obj = {};
2.
3. Object.defineProperty(obj, 'p', {
4.     value: 123,
5.     get: function() { return 456; }
6. });
7. // TypeError: Invalid property.
8. // A property cannot both have accessors and be writable or have a value
9.
10. Object.defineProperty(obj, 'p', {
11.     writable: true,
12.     get: function() { return 456; }
13. });
14. // TypeError: Invalid property descriptor.
15. // Cannot both specify accessors and a value or writable attribute

上面代码中,同时定义了 get 属性和 value 属性,以及将 writable 属性设为 true ,就会 报错。 Object.defineProperty() 和 Object.defineProperties() 参数里面的属性描述对象, writable 、 configurable 、 enumerable 这三个属性的默认值都为 false 。

1. var obj = {};
2.     Object.defineProperty(obj, 'foo', {});
3.     Object.getOwnPropertyDescriptor(obj, 'foo')
4. // {
5. // value: undefined,
6. // writable: false,
7. // enumerable: false,
8. // configurable: false
9. // }

上面代码中,定义 obj.foo 时用了一个空的属性描述对象,就可以看到各个元属性的默认值。

Object.prototype.propertyIsEnumerable()

实例对象的 propertyIsEnumerable() 方法返回一个布尔值,用来判断某个属性是否可遍历。注意, 这个方法只能用于判断对象自身的属性,对于继承的属性一律返回 false 。

1. var obj = {};
2. obj.p = 123;
3.
4. obj.propertyIsEnumerable('p') // true
5. obj.propertyIsEnumerable('toString') // false

上面代码中, obj.p 是可遍历的,而 obj.toString 是继承的属性。

元属性

属性描述对象的各个属性称为“元属性”,因为它们可以看作是控制属性的属性。

value:性是目标属性的值。

1. var obj = {};
2. obj.p = 123;
3.
4. Object.getOwnPropertyDescriptor(obj, 'p').value
5. // 123
6.
7. Object.defineProperty(obj, 'p', { value: 246 });
8. obj.p // 246

上面代码是通过 value 属性,读取或改写 obj.p 的例子。

writable:是一个布尔值,决定了目标属性的值(value)是否可以被改变。

1. var obj = {};
2.
3. Object.defineProperty(obj, 'a', {
4.     value: 37,
5.     writable: false
6. });
7.
8. obj.a // 37
9. obj.a = 25;
10. obj.a // 37

上面代码中, obj.a 的 writable 属性是 false 。然后,改变 obj.a 的值,不会有任何效 果。注意,正常模式下,对 writable 为 false 的属性赋值不会报错,只会默默失败。但是,严格模式 下会报错,即使对 a 属性重新赋予一个同样的值。

1. 'use strict';
2. var obj = {};
3.
4. Object.defineProperty(obj, 'a', {
5.     value: 37,
6.     writable: false
7. });
8.
9. obj.a = 37;
10. // Uncaught TypeError: Cannot assign to read only property 'a' of object

上面代码是严格模式,对 obj.a 任何赋值行为都会报错。 如果原型对象的某个属性的 writable 为 false ,那么子对象将无法自定义这个属性。

1. var proto = Object.defineProperty({}, 'foo', {
2.     value: 'a',
3.     writable: false
4. });
5.
6. var obj = Object.create(proto);
7.
8. obj.foo = 'b';
9. obj.foo // 'a'

上面代码中, proto 是原型对象,它的 foo 属性不可写。 obj 对象继承 proto ,也不可以再 自定义这个属性了。如果是严格模式,这样做还会抛出一个错误。 但是,有一个规避方法,就是通过覆盖属性描述对象,绕过这个限制。原因是这种情况下,原型链会被 完全忽视。

1. var proto = Object.defineProperty({}, 'foo', {
2.     value: 'a',
3.     writable: false
4. });
5.
6. var obj = Object.create(proto);
7. Object.defineProperty(obj, 'foo', {
8.     value: 'b'
9. });
10.
11. obj.foo // "b"

enumerable:(可遍历性)返回一个布尔值,表示目标属性是否可遍历。

JavaScript 的早期版本, for...in 循环是基于 in 运算符的。我们知道, in 运算符不管某 个属性是对象自身的还是继承的,都会返回 true 。

1. var obj = {};
2. '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 可以用来设置“秘密”属性。

1. var obj = {};
2.
3. Object.defineProperty(obj, 'x', {
4.     value: 123,
5.     enumerable: false
6. });
7.
8. obj.x // 123
9.
10. for (var key in obj) {
11.     console.log(key);
12. }
13. // undefined
14.
15. Object.keys(obj) // []
16. JSON.stringify(obj) // "{}"

上面代码中, obj.x 属性的 enumerable 为 false ,所以一般的遍历操作都无法获取该属性, 使得它有点像“秘密”属性,但不是真正的私有属性,还是可以直接获取它的值。 注意, for...in 循环包括继承的属性, Object.keys 方法不包括继承的属性。如果需要获取对 象自身的所有属性,不管是否可遍历,可以使用 Object.getOwnPropertyNames 方法。 另外, JSON.stringify 方法会排除 enumerable 为 false 的属性,有时可以利用这一点。如 果对象的 JSON 格式输出要排除某些属性,就可以把这些属性的 enumerable 设为 false 。

configurable:configurable (可配置性)返回一个布尔值,决定了是否可以修改属性描述对象。 configurable 为 false 时, value 、 writable 、 enumerable 和 configurable 都不能被修改了。

1. var obj = Object.defineProperty({}, 'p', {
2.     value: 1,
3.     writable: false,
4.     enumerable: false,
5.     configurable: false
6. });
7.
8. Object.defineProperty(obj, 'p', {value: 2})
9. // TypeError: Cannot redefine property: p
10.
11. Object.defineProperty(obj, 'p', {writable: true})
12. // TypeError: Cannot redefine property: p
13.
14. Object.defineProperty(obj, 'p', {enumerable: true})
15. // TypeError: Cannot redefine property: p
16.
17. Object.defineProperty(obj, 'p', {configurable: true})
18. // TypeError: Cannot redefine property: p

上面代码中, obj.p 的 configurable 为 false 。然后,改 动 value 、 writable 、 enumerable 、 configurable ,结果都报错。 注意, writable 只有在 false 改为 true 会报错, true 改为 false 是允许的。

1. var obj = Object.defineProperty({}, 'p', {
2.     writable: true,
3.     configurable: false
4. });
5.
6. Object.defineProperty(obj, 'p', {writable: false})
7. // 修改成功

至于 value ,只要 writable 和 configurable 有一个为 true ,就允许改动。

1. var o1 = Object.defineProperty({}, 'p', {
2.     value: 1,
3.     writable: true,
4.     configurable: false
5. });
6.
7. Object.defineProperty(o1, 'p', {value: 2})
8. // 修改成功
9.
10. var o2 = Object.defineProperty({}, 'p', {
11.     value: 1,
12.     writable: false,
13.     configurable: true
14. });
15.
16. Object.defineProperty(o2, 'p', {value: 2})
17. // 修改成功

另外, writable 为 false 时,直接目标属性赋值,不报错,但不会成功。

1. var obj = Object.defineProperty({}, 'p', {
2.     value: 1,
3.     writable: false,
4.     configurable: false
5. });
6.
7. obj.p = 2;
8. obj.p // 1

上面代码中, obj.p 的 writable 为 false ,对 obj.p 直接赋值不会生效。如果是严格模 式,还会报错。

可配置性决定了目标属性是否可以被删除(delete)。

1. var obj = Object.defineProperties({}, {
2.     p1: { value: 1, configurable: true },
3.     p2: { value: 2, configurable: false }
4. });
5.
6. delete obj.p1 // true
7. delete obj.p2 // false
8.
9. obj.p1 // undefined
10. obj.p2 // 2

上面代码中, obj.p1 的 configurable 是 true ,所以可以被删除, obj.p2 就无法删除。

存取器:除了直接定义以外,属性还可以用存取器(accessor)定义。其中,存值函数称为 setter ,使用 属性描述对象的 set 属性;取值函数称为 getter ,使用属性描述对象的 get 属性。

一旦对目标属性定义了存取器,那么存取的时候,都将执行对应的函数。利用这个功能,可以实现许多 高级特性,比如定制属性的读取和赋值行为。

1. var obj = Object.defineProperty({}, 'p', {
2.     get: function () {
3.         return 'getter';
4.     },
5.     set: function (value) {
6.         console.log('setter: ' + value);
7.     }
8. });
9.
10. obj.p // "getter"
11. obj.p = 123 // "setter: 123"

上面代码中, obj.p 定义了 get 和 set 属性。 obj.p 取值时,就会调用 get ;赋值时, 就会调用 set 。 JavaScript 还提供了存取器的另一种写法。

1. // 写法二
2. var obj = {
3.     get p() {
4.         return 'getter';
5.     },
6.     set p(value) {
7.         console.log('setter: ' + value);
8.     }
9. };

上面两种写法,虽然属性 p 的读取和赋值行为是一样的,但是有一些细微的区别。第一种写法,属 性 p 的 configurable 和 enumerable 都为 false ,从而导致属性 p 是不可遍历的;第二 种写法,属性 p 的 configurable 和 enumerable 都为 true ,因此属性 p 是可遍历的。 实际开发中,写法二更常用。 注意,取值函数 get 不能接受参数,存值函数 set 只能接受一个参数(即属性的值)。 存取器往往用于,属性的值依赖对象内部数据的场合。

1. var obj ={
2.     $n : 5,
3.     get next() { return this.$n++ },
4.     set next(n) {
5.         if (n >= this.$n) this.$n = n;
6.         else throw new Error('新的值必须大于当前值');
7.     }
8. };
9.
10. obj.next // 5
11.
12. obj.next = 10;
13. obj.next // 10
14.
15. obj.next = 5;
16. // Uncaught Error: 新的值必须大于当前值

上面代码中, next 属性的存值函数和取值函数,都依赖于内部属性 $n 。

对象的拷贝:将一个对象的所有属性,拷贝到另一个对象,可以用下面的方法实现。

1. var extend = function (to, from) {
2.     for (var property in from) {
3.         to[property] = from[property];
4.     }
5.
6.     return to;
7. }
8.
9. extend({}, {
10.     a: 1
11. })
12. // {a: 1}

上面这个方法的问题在于,如果遇到存取器定义的属性,会只拷贝值。

1. extend({}, {
2.     get a() { return 1 }
3. })
4. // {a: 1}

为了解决这个问题,我们可以通过 Object.defineProperty 方法来拷贝属性。

1. var extend = function (to, from) {
2.     for (var property in from) {
3.         if (!from.hasOwnProperty(property)) continue;
4.         Object.defineProperty(
5.             to,
6.             property,
7.             Object.getOwnPropertyDescriptor(from, property)
8.         );
9.     }
10.
11.     return to;
12. }
13.
14. extend({}, { get a(){ return 1 } })
15. // { get a(){ return 1 } })

上面代码中, hasOwnProperty 那一行用来过滤掉继承的属性,否则可能会报错,因 为 Object.getOwnPropertyDescriptor 读不到继承属性的属性描述对象。

控制对象状态:有时需要冻结对象的读写状态,防止对象被改变。JavaScript 提供了三种冻结方法,最弱的一种 是 Object.preventExtensions ,其次是 Object.seal ,最强的是 Object.freeze 。

Object.preventExtensions()

Object.preventExtensions 方法可以使得一个对象无法再添加新的属性。

1. var obj = new Object();
2. Object.preventExtensions(obj);
3.
4. Object.defineProperty(obj, 'p', {
5.     value: 'hello'
6. });
7. // TypeError: Cannot define property:p, object is not extensible.
8.
9. obj.p = 1;
10. obj.p // undefined

上面代码中, obj 对象经过 Object.preventExtensions 以后,就无法添加新属性了。

Object.isExtensible()

Object.isExtensible 方法用于检查一个对象是否使用了 Object.preventExtensions 方法。也 就是说,检查是否可以为一个对象添加属性。

1. var obj = new Object();
2.
3. Object.isExtensible(obj) // true
4. Object.preventExtensions(obj);
5. Object.isExtensible(obj) // false

上面代码中,对 obj 对象使用 Object.preventExtensions 方法以后,再使 用 Object.isExtensible 方法,返回 false ,表示已经不能添加新属性了。

Object.seal()

Object.seal 方法使得一个对象既无法添加新属性,也无法删除旧属性。

1. var obj = { p: 'hello' };
2. Object.seal(obj);
3.
4. delete obj.p;
5. obj.p // "hello"
6.
7. obj.x = 'world';
8. obj.x // undefined

上面代码中, obj 对象执行 Object.seal 方法以后,就无法添加新属性和删除旧属性了。 Object.seal 实质是把属性描述对象的 configurable 属性设为 false ,因此属性描述对象不 再能改变了。

1. var obj = {
2.     p: 'a'
3. };
4.
5. // seal方法之前
6. Object.getOwnPropertyDescriptor(obj, 'p')
7. // Object {
8. // value: "a",
9. // writable: true,
10. // enumerable: true,
11. // configurable: true
12. // }
13.
14. Object.seal(obj);
15.
16. // seal方法之后
17. Object.getOwnPropertyDescriptor(obj, 'p')
18. // Object {
19. // value: "a",
20. // writable: true,
21. // enumerable: true,
22. // configurable: false
23. // }
24.
25. Object.defineProperty(o, 'p', {
26.     enumerable: false
27. })
28. // TypeError: Cannot redefine property: p

上面代码中,使用 Object.seal 方法之后,属性描述对象的 configurable 属性就变成 了 false ,然后改变 enumerable 属性就会报错。

Object.seal 只是禁止新增或删除属性,并不影响修改某个属性的值。

1. var obj = { p: 'a' };
2. Object.seal(obj);
3. obj.p = 'b';
4. obj.p // 'b'

上面代码中, Object.seal 方法对 p 属性的 value 无效,是因为此时 p 属性的可写性 由 writable 决定。

Object.isSealed()

Object.isSealed 方法用于检查一个对象是否使用了 Object.seal 方法。

1. var obj = { p: 'a' };
2.
3. Object.seal(obj);
4. Object.isSealed(obj) // true

这时, Object.isExtensible 方法也返回 false 。

1. var obj = { p: 'a' };
2.
3. Object.seal(obj);
4. Object.isExtensible(obj) // false

Object.freeze()

Object.freeze 方法可以使得一个对象无法添加新属性、无法删除旧属性、也无法改变属性的值, 使得这个对象实际上变成了常量。

1. var obj = {
2.     p: 'hello'
3. };
4.
5. Object.freeze(obj);
6.
7. obj.p = 'world';
8. obj.p // "hello"
9.
10. obj.t = 'hello';
11. obj.t // undefined
12.
13. delete obj.p // false
14. obj.p // "hello"

上面代码中,对 obj 对象进行 Object.freeze() 以后,修改属性、新增属性、删除属性都无效 了。这些操作并不报错,只是默默地失败。如果在严格模式下,则会报错。

Object.isFrozen()

Object.isFrozen 方法用于检查一个对象是否使用了 Object.freeze 方法。

1. var obj = {
2.     p: 'hello'
3. };
4.
5. Object.freeze(obj);
6. Object.isFrozen(obj) // true

使用 Object.freeze 方法以后, Object.isSealed将会返回true , Object.isExtensible返回false 。

1. var obj = {
2.     p: 'hello'
3. };
4.
5. Object.freeze(obj);
6.
7. Object.isSealed(obj) // true
8. Object.isExtensible(obj) // false

Object.isFrozen 的一个用途是,确认某个对象没有被冻结后,再对它的属性赋值。

1. var obj = {
2.     p: 'hello'
3. };
4.
5. Object.freeze(obj);
6.
7. if (!Object.isFrozen(obj)) {
8.     obj.p = 'world';
9. }

上面代码中,确认 obj 没有被冻结后,再对它的属性赋值,就不会报错了。

局限性:上面的三个方法锁定对象的可写性有一个漏洞:可以通过改变原型对象,来为对象增加属性。

1. var obj = new Object();
2. Object.preventExtensions(obj);
3.
4. var proto = Object.getPrototypeOf(obj);
5. proto.t = 'hello';
6. obj.t
7. // hello

上面代码中,对象 obj 本身不能新增属性,但是可以在它的原型对象上新增属性,就依然能够 在 obj 上读到。 一种解决方案是,把 obj 的原型也冻结住。

1. var obj = new Object();
2. Object.preventExtensions(obj);
3.
4. var proto = Object.getPrototypeOf(obj);
5. Object.preventExtensions(proto);
6.
7. proto.t = 'hello';
8. obj.t // undefined

另外一个局限是,如果属性值是对象,上面这些方法只能冻结属性指向的对象,而不能冻结对象本身的 内容。

1. var obj = {
2.     foo: 1,
3.     bar: ['a', 'b']
4. };
5. Object.freeze(obj);
6.
7. obj.bar.push('c');
8. obj.bar // ["a", "b", "c"]

上面代码中, obj.bar 属性指向一个数组, obj 对象被冻结以后,这个指向无法改变,即无法指向其他值,但是所指向的数组是可以改变的。

3,Array 对象

Array 是 JavaScript 的原生对象,同时也是一个构造函数,可以用它生成新的数组。

1. var arr = new Array(2);
2. arr.length // 2
3. arr // [ empty x 2 ]

上面代码中, Array() 构造函数的参数 2 ,表示生成一个两个成员的数组,每个位置都是空值。 如果没有使用 new 关键字,运行结果也是一样的。

1. var arr = new Array(2);
2. // 等同于
3. var arr = Array(2);

考虑到语义性,以及与其他构造函数用户保持一致,建议总是加上 new 。 Array() 构造函数有一个很大的缺陷,不同的参数会导致行为不一致。

1. // 无参数时,返回一个空数组
2. new Array() // []
3.
4. // 单个正整数参数,表示返回的新数组的长度
5. new Array(1) // [ empty ]
6. new Array(2) // [ empty x 2 ]
7.
8. // 非正整数的数值作为参数,会报错
9. new Array(3.2) // RangeError: Invalid array length
10. new Array(-3) // RangeError: Invalid array length
11.
12. // 单个非数值(比如字符串、布尔值、对象等)作为参数,
13. // 则该参数是返回的新数组的成员
14. new Array('abc') // ['abc']
15. new Array([1]) // [Array[1]]
16.
17. // 多参数时,所有参数都是返回的新数组的成员
18. new Array(1, 2) // [1, 2]
19. new Array('a', 'b', 'c') // ['a', 'b', 'c']

可以看到, Array() 作为构造函数,行为很不一致。因此,不建议使用它生成新数组,直接使用数组 字面量是更好的做法。

1. // bad
2. var arr = new Array(1, 2);
3.
4. // good
5. var arr = [1, 2];

注意,如果参数是一个正整数,返回数组的成员都是空位。虽然读取的时候返回 undefined ,但实 际上该位置没有任何值。虽然这时可以读取到 length 属性,但是取不到键名。

1. var a = new Array(3);
2. var b = [undefined, undefined, undefined];
3.
4. a.length // 3
5. b.length // 3
6.
7. a[0] // undefined
8. b[0] // undefined
9.
10. 0 in a // false
11. 0 in b // true

上面代码中, a 是 Array() 生成的一个长度为3的空数组, b 是一个三个成员都 是 undefined 的数组,这两个数组是不一样的。读取键值的时候, a 和 b 都返 回 undefined ,但是 a 的键名(成员的序号)都是空的, b 的键名是有值的。

静态方法:

Array.isArray():方法返回一个布尔值,表示参数是否为数组。它可以弥补 typeof 运算符的不足。

1. var arr = [1, 2, 3];
2.
3. typeof arr // "object"
4. Array.isArray(arr) // true

上面代码中, typeof 运算符只能显示数组的类型是 Object ,而 Array.isArray 方法可以识别 数组。

实例方法:

valueOf ():方法是一个所有对象都拥有的方法,表示对该对象求值。不同对象的 valueOf 方法不尽 一致,数组的 valueOf 方法返回数组本身。

1. var arr = [1, 2, 3];
2. arr.valueOf() // [1, 2, 3]

toString ():方法也是对象的通用方法,数组的 toString 方法返回数组的字符串形式。

1. var arr = [1, 2, 3];
2. arr.toString() // "1,2,3"
3.
4. var arr = [1, 2, 3, [4, 5, 6]];
5. arr.toString() // "1,2,3,4,5,6"

push() :方法用于在数组的末端添加一个或多个元素,并返回添加新元素后的数组长度。注意,该方法 会改变原数组。

1. var arr = [];
2.
3. arr.push(1) // 1
4. arr.push('a') // 2
5. arr.push(true, {}) // 4
6. arr // [1, 'a', true, {}]

上面代码使用 push 方法,往数组中添加了四个成员。 pop 方法用于删除数组的最后一个元素,并返回该元素。注意,该方法会改变原数组。

1. var arr = ['a', 'b', 'c'];
2.
3. arr.pop() // 'c'
4. arr // ['a', 'b']

对空数组使用 pop 方法,不会报错,而是返回 undefined 。

1. [].pop() // undefined

push 和 pop 结合使用,就构成了“后进先出”的栈结构(stack)。

1. var arr = [];
2. arr.push(1, 2);
3. arr.push(3);
4. arr.pop();
5. arr // [1, 2]

上面代码中, 3 是最后进入数组的,但是最早离开数组。

shift() :方法用于删除数组的第一个元素,并返回该元素。注意,该方法会改变原数组。

1. var a = ['a', 'b', 'c'];
2.
3. a.shift() // 'a'
4. a // ['b', 'c']

上面代码中,使用 shift() 方法以后,原数组就变了。 shift() 方法可以遍历并清空一个数组。

1. var list = [1, 2, 3, 4];
2. var item;
3.
4. while (item = list.shift()) {
5.     console.log(item);
6. }
7.
8. list // []

上面代码通过 list.shift() 方法每次取出一个元素,从而遍历数组。它的前提是数组元素不能 是 0 或任何布尔值等于 false 的元素,因此这样的遍历不是很可靠。

push() 和 shift() 结合使用,就构成了“先进先出”的队列结构(queue)。unshift() 方法用于在数组的第一个位置添加元素,并返回添加新元素后的数组长度。注意,该方 法会改变原数组。

1. var a = ['a', 'b', 'c'];
2.
3. a.unshift('x'); // 4
4. a // ['x', 'a', 'b', 'c']

unshift() 方法可以接受多个参数,这些参数都会添加到目标数组头部。

1. var arr = [ 'c', 'd' ];
2. arr.unshift('a', 'b') // 4
3. arr // [ 'a', 'b', 'c', 'd' ]

join() :方法以指定参数作为分隔符,将所有数组成员连接为一个字符串返回。如果不提供参数,默 认用逗号分隔。

1. var a = [1, 2, 3, 4];
2.
3. a.join(' ') // '1 2 3 4'
4. a.join(' | ') // "1 | 2 | 3 | 4"
5. a.join() // "1,2,3,4"

如果数组成员是 undefined 或 null 或空位,会被转成空字符串。

1. [undefined, null].join('#')
2. // '#'
3.
4. ['a',, 'b'].join('-')
5. // 'a--b'

通过 call 方法,这个方法也可以用于字符串或类似数组的对象。

1. Array.prototype.join.call('hello', '-')
2. // "h-e-l-l-o"
3.
4. var obj = { 0: 'a', 1: 'b', length: 2 };
5. Array.prototype.join.call(obj, '-')
6. // 'a-b'

concat ():方法用于多个数组的合并。它将新数组的成员,添加到原数组成员的后部,然后返回一个新 数组,原数组不变。

1. ['hello'].concat(['world'])
2. // ["hello", "world"]
3.
4. ['hello'].concat(['world'], ['!'])
5. // ["hello", "world", "!"]
6.
7. [].concat({a: 1}, {b: 2})
8. // [{ a: 1 }, { b: 2 }]
9.
10. [2].concat({a: 1})
11. // [2, {a: 1}]

除了数组作为参数, concat 也接受其他类型的值作为参数,添加到目标数组尾部。

1. [1, 2, 3].concat(4, 5, 6)
2. // [1, 2, 3, 4, 5, 6]

如果数组成员包括对象, concat 方法返回当前数组的一个浅拷贝。所谓“浅拷贝”,指的是新数组拷 贝的是对象的引用。

1. var obj = { a: 1 };
2. var oldArray = [obj];
3.
4. var newArray = oldArray.concat();
5.
6. obj.a = 2;
7. newArray[0].a // 2

上面代码中,原数组包含一个对象, concat 方法生成的新数组包含这个对象的引用。所以,改变原 对象以后,新数组跟着改变。

reverse():方法用于颠倒排列数组元素,返回改变后的数组。注意,该方法将改变原数组。

1. var a = ['a', 'b', 'c'];
2.
3. a.reverse() // ["c", "b", "a"]
4. a // ["c", "b", "a"]

slice():方法用于提取目标数组的一部分,返回一个新数组,原数组不变。

1. arr.slice(start, end);

它的第一个参数为起始位置(从0开始,会包括在返回的新数组之中),第二个参数为终止位置(但该 位置的元素本身不包括在内)。如果省略第二个参数,则一直返回到原数组的最后一个成员。

1. var a = ['a', 'b', 'c'];
2.
3. a.slice(0) // ["a", "b", "c"]
4. a.slice(1) // ["b", "c"]
5. a.slice(1, 2) // ["b"]
6. a.slice(2, 6) // ["c"]
7. a.slice() // ["a", "b", "c"]

上面代码中,最后一个例子 slice() 没有参数,实际上等于返回一个原数组的拷贝。 如果 slice() 方法的参数是负数,则表示倒数计算的位置。

1. var a = ['a', 'b', 'c'];
2. a.slice(-2) // ["b", "c"]
3. a.slice(-2, -1) // ["b"]

上面代码中, -2 表示倒数计算的第二个位置, -1 表示倒数计算的第一个位置。 如果第一个参数大于等于数组长度,或者第二个参数小于第一个参数,则返回空数组。

1. var a = ['a', 'b', 'c'];
2. a.slice(4) // []
3. a.slice(2, 1) // []

slice() 方法的一个重要应用,是将类似数组的对象转为真正的数组。

1. Array.prototype.slice.call({ 0: 'a', 1: 'b', length: 2 })
2. // ['a', 'b']
3.
4. Array.prototype.slice.call(document.querySelectorAll("div"));
5. Array.prototype.slice.call(arguments);

上面代码的参数都不是数组,但是通过 call 方法,在它们上面调用 slice() 方法,就可以把它们 转为真正的数组。

splice() :方法用于删除原数组的一部分成员,并可以在删除的位置添加新的数组成员,返回值是被 删除的元素。注意,该方法会改变原数组。

1. arr.splice(start, count, addElement1, addElement2, ...);

splice 的第一个参数是删除的起始位置(从0开始),第二个参数是被删除的元素个数。如果后面 还有更多的参数,则表示这些就是要被插入数组的新元素。

1. var a = ['a', 'b', 'c', 'd', 'e', 'f'];
2. a.splice(4, 2) // ["e", "f"]
3. a // ["a", "b", "c", "d"]

上面代码从原数组4号位置,删除了两个数组成员。

1. var a = ['a', 'b', 'c', 'd', 'e', 'f'];
2. a.splice(4, 2, 1, 2) // ["e", "f"]
3. a // ["a", "b", "c", "d", 1, 2]

上面代码除了删除成员,还插入了两个新成员。 起始位置如果是负数,就表示从倒数位置开始删除。

1. var a = ['a', 'b', 'c', 'd', 'e', 'f'];
2. a.splice(-4, 2) // ["c", "d"]

上面代码表示,从倒数第四个位置 c 开始删除两个成员。 如果只是单纯地插入元素, splice 方法的第二个参数可以设为 0 。

1. var a = [1, 1, 1];
2.
3. a.splice(1, 0, 2) // []
4. a // [1, 2, 1, 1]

如果只提供第一个参数,等同于将原数组在指定位置拆分成两个数组。

1. var a = [1, 2, 3, 4];
2. a.splice(2) // [3, 4]
3. a // [1, 2]

sort():方法对数组成员进行排序,默认是按照字典顺序排序。排序后,原数组将被改变。

1. ['d', 'c', 'b', 'a'].sort()
2. // ['a', 'b', 'c', 'd']
3.
4. [4, 3, 2, 1].sort()
5. // [1, 2, 3, 4]
6.
7. [11, 101].sort()
8. // [101, 11]
9.
10. [10111, 1101, 111].sort()
11. // [10111, 1101, 111]

上面代码的最后两个例子,需要特殊注意。 sort() 方法不是按照大小排序,而是按照字典顺序。也 就是说,数值会被先转成字符串,再按照字典顺序进行比较,所以 101 排在 11 的前面。 如果想让 sort 方法按照自定义方式排序,可以传入一个函数作为参数。

1. [10111, 1101, 111].sort(function (a, b) {
2.     return a - b;
3. })
4. // [111, 1101, 10111]

上面代码中, sort 的参数函数本身接受两个参数,表示进行比较的两个数组成员。如果该函数的返 回值大于 0 ,表示第一个成员排在第二个成员后面;其他情况下,都是第一个元素排在第二个元素前 面。

1. [
2. { name: "张三", age: 30 },
3. { name: "李四", age: 24 },
4. { name: "王五", age: 28 }
5. ].sort(function (o1, o2) {
6. return o1.age - o2.age;
7. })
8. // [
9. // { name: "李四", age: 24 },
10. // { name: "王五", age: 28 },
11. // { name: "张三", age: 30 }
12. // ]

注意,自定义的排序函数应该返回数值,否则不同的浏览器可能有不同的实现,不能保证结果都一致。

1. // bad
2. [1, 4, 2, 6, 0, 6, 2, 6].sort((a, b) => a > b)
3.
4. // good
5. [1, 4, 2, 6, 0, 6, 2, 6].sort((a, b) => a - b)

上面代码中,前一种排序算法返回的是布尔值,这是不推荐使用的。后一种是数值,才是更好的写法。

map():方法将数组的所有成员依次传入参数函数,然后把每一次的执行结果组成一个新数组返回。

1. var numbers = [1, 2, 3];
2.
3. numbers.map(function (n) {
4.     return n + 1;
5. });
6. // [2, 3, 4]
7.
8. numbers
9. // [1, 2, 3]

上面代码中, numbers 数组的所有成员依次执行参数函数,运行结果组成一个新数组返回,原数组没 有变化。 map 方法接受一个函数作为参数。该函数调用时, map 方法向它传入三个参数:当前成员、当前 位置和数组本身。

1. [1, 2, 3].map(function(elem, index, arr) {
2.     return elem * index;
3. });
4. // [0, 2, 6]

上面代码中, map 方法的回调函数有三个参数, elem 为当前成员的值, index 为当前成员的 位置, arr 为原数组( [1, 2, 3] )。map 方法还可以接受第二个参数,用来绑定回调函数内部的 this 变量。

1. var arr = ['a', 'b', 'c'];
2.
3. [1, 2].map(function (e) {
4.     return this[e];
5. }, arr)
6. // ['b', 'c']

上面代码通过 map 方法的第二个参数,将回调函数内部的 this 对象,指向 arr 数组。 如果数组有空位, map 方法的回调函数在这个位置不会执行,会跳过数组的空位。

1. var f = function (n) { return 'a' };
2.
3. [1, undefined, 2].map(f) // ["a", "a", "a"]
4. [1, null, 2].map(f) // ["a", "a", "a"]
5. [1, , 2].map(f) // ["a", , "a"]

上面代码中, map 方法不会跳过 undefined 和 null ,但是会跳过空位。

forEach():方法与 map 方法很相似,也是对数组的所有成员依次执行参数函数。但 是, forEach 方法不返回值,只用来操作数据。这就是说,如果数组遍历的目的是为了得到返回值, 那么使用 map 方法,否则使用 forEach 方法。forEach 的用法与 map 方法一致,参数是一个函数,该函数同样接受三个参数:当前值、当前位 置、整个数组。

1. function log(element, index, array) {
2.     console.log('[' + index + '] = ' + element);
3. }
4.
5. [2, 5, 9].forEach(log);
6. // [0] = 2
7. // [1] = 5
8. // [2] = 9

上面代码中, forEach 遍历数组不是为了得到返回值,而是为了在屏幕输出内容,所以不必使 用 map 方法。 forEach 方法也可以接受第二个参数,绑定参数函数的 this 变量。

1. var out = [];
2.
3. [1, 2, 3].forEach(function(elem) {
4.     this.push(elem * elem);
5. }, out);
6.
7. out // [1, 4, 9]

上面代码中,空数组 out 是 forEach 方法的第二个参数,结果,回调函数内部的 this 关键字 就指向 out 。 注意, forEach 方法无法中断执行,总是会将所有成员遍历完。如果希望符合某种条件时,就中断遍 历,要使用 for 循环。

1. var arr = [1, 2, 3];
2.
3. for (var i = 0; i < arr.length; i++) {
4.     if (arr[i] === 2) break;
5.     console.log(arr[i]);
6. }
7. // 1

上面代码中,执行到数组的第二个成员时,就会中断执行。 forEach 方法做不到这一点。 forEach 方法也会跳过数组的空位。

1. var log = function (n) {
2.     console.log(n + 1);
3. };
4.
5. [1, undefined, 2].forEach(log)
6. // 2
7. // NaN
8. // 3
9.
10. [1, null, 2].forEach(log)
11. // 2
12. // 1
13. // 3
14.
15. [1, , 2].forEach(log)
16. // 2
17. // 3

上面代码中, forEach 方法不会跳过 undefined 和 null ,但会跳过空位。

filter():方法用于过滤数组成员,满足条件的成员组成一个新数组返回。

它的参数是一个函数,所有数组成员依次执行该函数,返回结果为 true 的成员组成一个新数组返 回。该方法不会改变原数组。

1. [1, 2, 3, 4, 5].filter(function (elem) {
2.     return (elem > 3);
3. })
4. // [4, 5]

上面代码将大于 3 的数组成员,作为一个新数组返回。

1. var arr = [0, 1, 'a', false];
2.
3. arr.filter(Boolean)
4. // [1, "a"]

上面代码中, filter 方法返回数组 arr 里面所有布尔值为 true 的成员。 filter 方法的参数函数可以接受三个参数:当前成员,当前位置和整个数组。

1. [1, 2, 3, 4, 5].filter(function (elem, index, arr) {
2.     return index % 2 === 0;
3. });
4. // [1, 3, 5]

上面代码返回偶数位置的成员组成的新数组。filter 方法还可以接受第二个参数,用来绑定参数函数内部的 this 变量。

1. var obj = { MAX: 3 };
2. var myFilter = function (item) {
3.     if (item > this.MAX) return true;
4. };
5.
6. var arr = [2, 8, 3, 4, 1, 3, 2, 9];
7. arr.filter(myFilter, obj) // [8, 4, 9]

上面代码中,过滤器 myFilter 内部有 this 变量,它可以被 filter 方法的第二个参 数 obj 绑定,返回大于 3 的成员。

some(),every():这两个方法类似“断言”(assert),返回一个布尔值,表示判断数组成员是否符合某种条件。 它们接受一个函数作为参数,所有数组成员依次执行该函数。该函数接受三个参数:当前成员、当前位 置和整个数组,然后返回一个布尔值。

some 方法是只要一个成员的返回值是 true ,则整个 some 方法的返回值就是 true ,否则返 回 false 。

1. var arr = [1, 2, 3, 4, 5];
2. arr.some(function (elem, index, arr) {
3.     return elem >= 3;
4. });
5. // true

上面代码中,如果数组 arr 有一个成员大于等于3, some 方法就返回 true 。

every 方法是所有成员的返回值都是 true ,整个 every 方法才返回 true ,否则返 回 false 。

1. var arr = [1, 2, 3, 4, 5];
2. arr.every(function (elem, index, arr) {
3.     return elem >= 3;
4. });
5. // false

上面代码中,数组 arr 并非所有成员大于等于 3 ,所以返回 false 。

注意,对于空数组, some 方法返回 false , every 方法返回 true ,回调函数都不会执 行。

1. function isEven(x) { return x % 2 === 0 }
2.
3. [].some(isEven) // false
4. [].every(isEven) // true

some 和 every 方法还可以接受第二个参数,用来绑定参数函数内部的 this 变量。

reduce(),reduceRight():reduce 方法和 reduceRight 方法依次处理数组的每个成员,最终累计为一个值。它们的差别 是, reduce 是从左到右处理(从第一个成员到最后一个成员), reduceRight 则是从右到左 (从最后一个成员到第一个成员),其他完全一样。

1. [1, 2, 3, 4, 5].reduce(function (a, b) {
2.     console.log(a, b);
3.     return a + b;
4. })
5. // 1 2
6. // 3 3
7. // 6 4
8. // 10 5
9. //最后结果:15

上面代码中, reduce 方法求出数组所有成员的和。第一次执行, a 是数组的第一个成 员 1 , b 是数组的第二个成员 2 。第二次执行, a 为上一轮的返回值 3 , b 为第三个 成员 3 。第三次执行, a 为上一轮的返回值 6 , b 为第四个成员 4 。第四次执 行, a 为上一轮返回值 10 , b 为第五个成员 5 。至此所有成员遍历完成,整个方法的返回 值就是最后一轮的返回值 15 。

reduce 方法和 reduceRight 方法的第一个参数都是一个函数。该函数接受以下四个参数。

        1. 累积变量,默认为数组的第一个成员

        2. 当前变量,默认为数组的第二个成员

        3. 当前位置(从0开始)

        4. 原数组

这四个参数之中,只有前两个是必须的,后两个则是可选的。 如果要对累积变量指定初值,可以把它放在 reduce 方法和 reduceRight 方法的第二个参数。

1. [1, 2, 3, 4, 5].reduce(function (a, b) {
2.     return a + b;
3. }, 10);
4. // 25

上面代码指定参数 a 的初值为10,所以数组从10开始累加,最终结果为25。注意,这时 b 是从数 组的第一个成员开始遍历。 上面的第二个参数相当于设定了默认值,处理空数组时尤其有用。

1. function add(prev, cur) {
2.     return prev + cur;
3. }
4.
5. [].reduce(add)
6. // TypeError: Reduce of empty array with no initial value
7. [].reduce(add, 1)
8. // 1

上面代码中,由于空数组取不到初始值, reduce 方法会报错。这时,加上第二个参数,就能保证总 是会返回一个值。 下面是一个 reduceRight 方法的例子。

1. function subtract(prev, cur) {
2.     return prev - cur;
3. }
4.
5. [3, 2, 1].reduce(subtract) // 0
6. [3, 2, 1].reduceRight(subtract) // -4

上面代码中, reduce 方法相当于 3 减去 2 再减去 1 , reduceRight 方法相当于 1 减 去 2 再减去 3 。 由于这两个方法会遍历数组,所以实际上还可以用来做一些遍历相关的操作。比如,找出字符长度最长 的数组成员。

1. function findLongest(entries) {
2.     return entries.reduce(function (longest, entry) {
3.         return entry.length > longest.length ? entry : longest;
4.     }, '');
5. }
6.
7. findLongest(['aaa', 'bb', 'c']) // "aaa"

上面代码中, reduce 的参数函数会将字符长度较长的那个数组成员,作为累积值。这导致遍历所有 成员之后,累积值就是字符长度最长的那个成员。

indexOf():方法返回给定元素在数组中第一次出现的位置,如果没有出现则返回 -1 。

1. var a = ['a', 'b', 'c'];
2.
3. a.indexOf('b') // 1
4. a.indexOf('y') // -1

indexOf 方法还可以接受第二个参数,表示搜索的开始位置。

1. ['a', 'b', 'c'].indexOf('a', 1) // -1

上面代码从1号位置开始搜索字符 a ,结果为 -1 ,表示没有搜索到。

lastIndexOf():方法返回给定元素在数组中最后一次出现的位置,如果没有出现则返回 -1 。

1. var a = [2, 5, 9, 2];
2. a.lastIndexOf(2) // 3
3. a.lastIndexOf(7) // -1

注意,这两个方法不能用来搜索 NaN 的位置,即它们无法确定数组成员是否包含 NaN 。

1. [NaN].indexOf(NaN) // -1
2. [NaN].lastIndexOf(NaN) // -1

这是因为这两个方法内部,使用严格相等运算符( === )进行比较,而 NaN 是唯一一个不等于自 身的值。

链式使用

上面这些数组方法之中,有不少返回的还是数组,所以可以链式使用。

1. var users = [
2.     {name: 'tom', email: 'tom@example.com'},
3.     {name: 'peter', email: 'peter@example.com'}
4. ];
5.
6. users
7. .map(function (user) {
8.     return user.email;
9. })
10. .filter(function (email) {
11. return /^t/.test(email);
12. })
13. .forEach(function (email) {
14.     console.log(email);
15. });
16. // "tom@example.com"

上面代码中,先产生一个所有 Email 地址组成的数组,然后再过滤出以 t 开头的 Email 地址, 最后将它打印出来。

4,包装对象

对象是 JavaScript 语言最主要的数据类型,三种原始类型的值——数值、字符串、布尔值——在一定 条件下,也会自动转为对象,也就是原始类型的“包装对象”(wrapper)。 所谓“包装对象”,指的是与数值、字符串、布尔值分别相对应 的 Number 、 String 、 Boolean 三个原生对象。这三个原生对象可以把原始类型的值变成(包 装成)对象。

1. var v1 = new Number(123);
2. var v2 = new String('abc');
3. var v3 = new Boolean(true);
4.
5. typeof v1 // "object"
6. typeof v2 // "object"
7. typeof v3 // "object"
8.
9. v1 === 123 // false
10. v2 === 'abc' // false
11. v3 === true // false

上面代码中,基于原始类型的值,生成了三个对应的包装对象。可以看到, v1 、 v2 、 v3 都是 对象,且与对应的简单类型值不相等。 包装对象的设计目的,首先是使得“对象”这种类型可以覆盖 JavaScript 所有的值,整门语言有一个 通用的数据模型,其次是使得原始类型的值也有办法调用自己的方法。

Number 、 String 和 Boolean 这三个原生对象,如果不作为构造函数调用(即调用时不 加 new ),而是作为普通函数调用,常常用于将任意类型的值转为数值、字符串和布尔值。

1. // 字符串转为数值
2. Number('123') // 123
3.
4. // 数值转为字符串
5. String(123) // "123"
6.
7. // 数值转为布尔值
8. Boolean(123) // true

这三个对象作为构造函数使用(带有 new )时,可以将原始类型的值转为对象;作为普通 函数使用时(不带有 new ),可以将任意类型的值,转为原始类型的值。

实例方法:三种包装对象各自提供了许多实例方法,详见后文。这里介绍两种它们共同具有、从 Object 对象继 承的方法: valueOf() 和 toString() 。

valueOf():方法返回包装对象实例对应的原始类型的值。

1. new Number(123).valueOf() // 123
2. new String('abc').valueOf() // "abc"
3. new Boolean(true).valueOf() // true

toString():方法返回对应的字符串形式。

1. new Number(123).toString() // "123"
2. new String('abc').toString() // "abc"
3. new Boolean(true).toString() // "true"

原始类型与实例对象的自动转换:某些场合,原始类型的值会自动当作包装对象调用,即调用包装对象的属性和方法。这时, JavaScript 引擎会自动将原始类型的值转为包装对象实例,并在使用后立刻销毁实例。 比如,字符串可以调用 length 属性,返回字符串的长度。

1. 'abc'.length // 3

上面代码中, abc 是一个字符串,本身不是对象,不能调用 length 属性。JavaScript 引擎自 动将其转为包装对象,在这个对象上调用 length 属性。调用结束后,这个临时对象就会被销毁。这 就叫原始类型与实例对象的自动转换。

1. var str = 'abc';
2. str.length // 3
3.
4. // 等同于
5. var strObj = new String(str)
6. // String {
7. // 0: "a", 1: "b", 2: "c", length: 3, [[PrimitiveValue]]: "abc"
8. // }
9. strObj.length // 3

上面代码中,字符串 abc 的包装对象提供了多个属性, length 只是其中之一。 自动转换生成的包装对象是只读的,无法修改。所以,字符串无法添加新属性。

1. var s = 'Hello World';
2. s.x = 123;
3. s.x // undefined

上面代码为字符串 s 添加了一个 x 属性,结果无效,总是返回 undefined 。 另一方面,调用结束后,包装对象实例会自动销毁。这意味着,下一次调用字符串的属性时,实际是调 用一个新生成的对象,而不是上一次调用时生成的那个对象,所以取不到赋值在上一个对象的属性。如 果要为字符串添加属性,只有在它的原型对象 String.prototype 上定义。

自定义方法:除了原生的实例方法,包装对象还可以自定义方法和属性,供原始类型的值直接调用。 比如,我们可以新增一个 double 方法,使得字符串和数字翻倍。

1. String.prototype.double = function () {
2.     return this.valueOf() + this.valueOf();
3. };
4.
5. 'abc'.double()
6. // abcabc
7.
8. Number.prototype.double = function () {
9.     return this.valueOf() + this.valueOf();
10. };
11.
12. (123).double() // 246

上面代码在 String 和 Number 这两个对象的原型上面,分别自定义了一个方法,从而可以在所有 实例对象上调用。注意,最后一行的 123 外面必须要加上圆括号,否则后面的点运算符( . )会 被解释成小数点。

5,Boolean 对象

Boolean 对象是 JavaScript 的三个包装对象之一。作为构造函数,它主要用于生成布尔值的包 装对象实例。

1. var b = new Boolean(true);
2.
3. typeof b // "object"
4. b.valueOf() // true

上面代码的变量 b 是一个 Boolean 对象的实例,它的类型是对象,值为布尔值 true 。 注意, false 对应的包装对象实例,布尔运算结果也是 true 。

1. if (new Boolean(false)) {
2.     console.log('true');
3. } // true
4.
5. if (new Boolean(false).valueOf()) {
6.     console.log('true');
7. } // 无输出

上面代码的第一个例子之所以得到 true ,是因为 false 对应的包装对象实例是一个对象,进行逻 辑运算时,被自动转化成布尔值 true (因为所有对象对应的布尔值都是 true )。而实例 的 valueOf 方法,则返回实例对应的原始值,本例为 false 。

Boolean 函数的类型转换作用:Boolean 对象除了可以作为构造函数,还可以单独使用,将任意值转为布尔值。这时 Boolean 就 是一个单纯的工具方法。

1. Boolean(undefined) // false
2. Boolean(null) // false
3. Boolean(0) // false
4. Boolean('') // false
5. Boolean(NaN) // false
6.
7. Boolean(1) // true
8. Boolean('false') // true
9. Boolean([]) // true
10. Boolean({}) // true
11. Boolean(function () {}) // true
12. Boolean(/foo/) // true

上面代码中几种得到 true 的情况,都值得认真记住。 顺便提一下,使用双重的否运算符( ! )也可以将任意值转为对应的布尔值。

1. !!undefined // false
2. !!null // false
3. !!0 // false
4. !!'' // false
5. !!NaN // false
6.
7. !!1 // true
8. !!'false' // true
9. !![] // true
10. !!{} // true
11. !!function(){} // true
12. !!/foo/ // true

最后,对于一些特殊值, Boolean 对象前面加不加 new ,会得到完全相反的结果,必须小心。

1. if (Boolean(false)) {
2.     console.log('true');
3. } // 无输出
4.
5. if (new Boolean(false)) {
6.     console.log('true');
7. } // true
8.
9. if (Boolean(null)) {
10.     console.log('true');
11. } // 无输出
12.
13. if (new Boolean(null)) {
14.     console.log('true');
15. } // true

6,Number 对象

Number 对象是数值对应的包装对象,可以作为构造函数使用,也可以作为工具函数使用。 作为构造函数时,它用于生成值为数值的对象。

1. var n = new Number(1);
2. typeof n // "object"

上面代码中, Number 对象作为构造函数使用,返回一个值为 1 的对象。 作为工具函数时,它可以将任何类型的值转为数值。

1. Number(true) // 1

Number 对象拥有以下一些静态属性(即直接定义在 Number 对象上的属性,而不是定义在实例上 的属性)。

         Number.POSITIVE_INFINITY :正的无限,指向 Infinity 。

        Number.NEGATIVE_INFINITY :负的无限,指向 -Infinity 。

        Number.NaN :表示非数值,指向 NaN 。

        Number.MIN_VALUE :表示最小的正数(即最接近0的正数,在64位浮点数体系中为 5e324 ),相应的,最接近0的负数为 -Number.MIN_VALUE 。

        Number.MAX_SAFE_INTEGER :表示能够精确表示的最大整数,即 9007199254740991 。         Number.MIN_SAFE_INTEGER :表示能够精确表示的最小整数,即 -9007199254740991 。

1. Number.POSITIVE_INFINITY // Infinity
2. Number.NEGATIVE_INFINITY // -Infinity
3. Number.NaN // NaN
4.
5. Number.MAX_VALUE
6. // 1.7976931348623157e+308
7. Number.MAX_VALUE < Infinity
8. // true
9.
10. Number.MIN_VALUE
11. // 5e-324
12. Number.MIN_VALUE > 0
13. // true
14.
15. Number.MAX_SAFE_INTEGER // 9007199254740991
16. Number.MIN_SAFE_INTEGER // -9007199254740991

Number 对象有4个实例方法,都跟将数值转换成指定格式有关。

Number.prototype.toString():

Number 对象部署了自己的 toString 方法,用来将一个数值转为字符串形式。

1. (10).toString() // "10"

toString 方法可以接受一个参数,表示输出的进制。如果省略这个参数,默认将数值先转为十进 制,再输出字符串;否则,就根据参数指定的进制,将一个数字转化成某个进制的字符串。

1. (10).toString(2) // "1010"
2. (10).toString(8) // "12"
3. (10).toString(16) // "a"

上面代码中, 10 一定要放在括号里,这样表明后面的点表示调用对象属性。如果不加括号,这个点 会被 JavaScript 引擎解释成小数点,从而报错。

1. 10.toString(2)
2. // SyntaxError: Unexpected token ILLEGAL

只要能够让 JavaScript 引擎不混淆小数点和对象的点运算符,各种写法都能用。除了为 10 加上 括号,还可以在 10 后面加两个点,JavaScript 会把第一个点理解成小数点(即 10.0 ),把第 二个点理解成调用对象属性,从而得到正确结果。

1. 10..toString(2)
2. // "1010"
3.
4. // 其他方法还包括
5. 10 .toString(2) // "1010"
6. 10.0.toString(2) // "1010"

这实际上意味着,可以直接对一个小数使用 toString 方法。

1. 10.5.toString() // "10.5"
2. 10.5.toString(2) // "1010.1"
3. 10.5.toString(8) // "12.4"
4. 10.5.toString(16) // "a.8"

通过方括号运算符也可以调用 toString 方法。

1. 10['toString'](2) // "1010"

toString 方法只能将十进制的数,转为其他进制的字符串。如果要将其他进制的数,转回十进制, 需要使用 parseInt 方法。

Number.prototype.toFixed():

toFixed() 方法先将一个数转为指定位数的小数,然后返回这个小数对应的字符串。

1. (10).toFixed(2) // "10.00"
2. 10.005.toFixed(2) // "10.01"

上面代码中, 10 和 10.005 先转成2位小数,然后转成字符串。其中 10 必须放在括号里,否则 后面的点会被处理成小数点。 toFixed() 方法的参数为小数位数,有效范围为0到100,超出这个范围将抛出 RangeError 错 误。 由于浮点数的原因,小数 5 的四舍五入是不确定的,使用的时候必须小心。

1. (10.055).toFixed(2) // 10.05
2. (10.005).toFixed(2) // 10.01

Number.prototype.toExponential():

toExponential 方法用于将一个数转为科学计数法形式。

1. (10).toExponential() // "1e+1"
2. (10).toExponential(1) // "1.0e+1"
3. (10).toExponential(2) // "1.00e+1"
4.
5. (1234).toExponential() // "1.234e+3"
6. (1234).toExponential(1) // "1.2e+3"
7. (1234).toExponential(2) // "1.23e+3"

toExponential 方法的参数是小数点后有效数字的位数,范围为0到100,超出这个范围,会抛出一 个 RangeError 错误。

Number.prototype.toPrecision()

Number.prototype.toPrecision() 方法用于将一个数转为指定位数的有效数字。

1. (12.34).toPrecision(1) // "1e+1"
2. (12.34).toPrecision(2) // "12"
3. (12.34).toPrecision(3) // "12.3"
4. (12.34).toPrecision(4) // "12.34"
5. (12.34).toPrecision(5) // "12.340"

该方法的参数为有效数字的位数,范围是1到100,超出这个范围会抛出 RangeError 错误。 该方法用于四舍五入时不太可靠,跟浮点数不是精确储存有关。

1. (12.35).toPrecision(3) // "12.3"
2. (12.25).toPrecision(3) // "12.3"
3. (12.15).toPrecision(3) // "12.2"
4. (12.45).toPrecision(3) // "12.4"

Number.prototype.toLocaleString():

Number.prototype.toLocaleString() 方法接受一个地区码作为参数,返回一个字符串,表示当前 数字在该地区的当地书写形式。

1. (123).toLocaleString('zh-Hans-CN-u-nu-hanidec')
2. // "一二三"

该方法还可以接受第二个参数配置对象,用来定制指定用途的返回字符串。该对象的 style 属性指定 输出样式,默认值是 decimal ,表示输出十进制形式。如果值为 percent ,表示输出百分数。

1. (123).toLocaleString('zh-Hans-CN', { style: 'percent' })
2. // "12,300%"

如果 style 属性的值为 currency ,则可以搭配 currency 属性,输出指定格式的货币字符串形 式。

1. (123).toLocaleString('zh-Hans-CN', { style: 'currency', currency: 'CNY' })
2. // "¥123.00"
3.
4. (123).toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })
5. // "123,00 €"
6.
7. (123).toLocaleString('en-US', { style: 'currency', currency: 'USD' })
8. // "$123.00"

如果 Number.prototype.toLocaleString() 省略了参数,则由浏览器自行决定如何处理,通常会使 用操作系统的地区设定。注意,该方法如果使用浏览器不认识的地区码,会抛出一个错误。

1. (123).toLocaleString('123') // 出错

自定义方法:与其他对象一样, Number.prototype 对象上面可以自定义方法,被 Number 的实例继承。

1. Number.prototype.add = function (x) {
2.     return this + x;
3. };
4.
5. 8['add'](2) // 10

上面代码为 Number 对象实例定义了一个 add 方法。在数值上调用某个方法,数值会自动转 为 Number 的实例对象,所以就可以调用 add 方法了。由于 add 方法返回的还是数值,所以可 以链式运算。

1. Number.prototype.subtract = function (x) {
2.     return this - x;
3. };
4.
5. (8).add(2).subtract(4)
6. // 6

上面代码在 Number 对象的实例上部署了 subtract 方法,它可以与 add 方法链式调用。 还可以部署更复杂的方法。

1. Number.prototype.iterate = function () {
2.     var result = [];
3.     for (var i = 0; i <= this; i++) {
4.         result.push(i);
5.     }
6.     return result;
7. };
8.
9. (8).iterate()
10. // [0, 1, 2, 3, 4, 5, 6, 7, 8]

上面代码在 Number 对象的原型上部署了 iterate 方法,将一个数值自动遍历为一个数组。 注意,数值的自定义方法,只能定义在它的原型对象 Number.prototype 上面,数值本身是无法自定 义属性的。

1. var n = 1;
2. n.x = 1;
3. n.x // undefined

上面代码中, n 是一个原始类型的数值。直接在它上面新增一个属性 x ,不会报错,但毫无作 用,总是返回 undefined 。这是因为一旦被调用属性, n 就自动转为 Number 的实例对象,调 用结束后,该对象自动销毁。所以,下一次调用 n 的属性时,实际取到的是另一个对象,属 性 x 当然就读不出来。

7,String 对象

String 对象是 JavaScript 原生提供的三个包装对象之一,用来生成字符串对象。

1. var s1 = 'abc';
2. var s2 = new String('abc');
3.
4. typeof s1 // "string"
5. typeof s2 // "object"
6.
7. s2.valueOf() // "abc"

上面代码中,变量 s1 是字符串, s2 是对象。由于 s2 是字符串对象, s2.valueOf 方法返回 的就是它所对应的原始字符串。 字符串对象是一个类似数组的对象(很像数组,但不是数组)。

1. new String('abc')
2. // String {0: "a", 1: "b", 2: "c", length: 3}
3.
4. (new String('abc'))[1] // "b"

上面代码中,字符串 abc 对应的字符串对象,有数值键( 0 、 1 、 2 )和 length 属性, 所以可以像数组那样取值。 除了用作构造函数, String 对象还可以当作工具方法使用,将任意类型的值转为字符串。

1. String(true) // "true"
2. String(5) // "5"

静态方法String.fromCharCode():

String 对象提供的静态方法(即定义在对象本身,而不是定义在对象实例的方法),主要是 String.fromCharCode() 。该方法的参数是一个或多个数值,代表 Unicode 码点,返回值是这 些码点组成的字符串。

1. String.fromCharCode() // ""
2. String.fromCharCode(97) // "a"
3. String.fromCharCode(104, 101, 108, 108, 111)
4. // "hello"

上面代码中, String.fromCharCode 方法的参数为空,就返回空字符串;否则,返回参数对应的 Unicode 字符串。 注意,该方法不支持 Unicode 码点大于 0xFFFF 的字符,即传入的参数不能大于 0xFFFF (即十 进制的 65535)。

1. String.fromCharCode(0x20BB7)
2. // ""
3. String.fromCharCode(0x20BB7) === String.fromCharCode(0x0BB7)
4. // true

上面代码中, String.fromCharCode 参数 0x20BB7 大于 0xFFFF ,导致返回结果出 错。 0x20BB7 对应的字符是汉字 ,但是返回结果却是另一个字符(码点 0x0BB7 )。这是因 为 String.fromCharCode 发现参数值大于 0xFFFF ,就会忽略多出的位(即忽略 0x20BB7 里面 的 2 )。 这种现象的根本原因在于,码点大于 0xFFFF 的字符占用四个字节,而 JavaScript 默认支持两个 字节的字符。这种情况下,必须把 0x20BB7 拆成两个字符表示。

1. String.fromCharCode(0xD842, 0xDFB7)
2. // ""

上面代码中,0x20BB7拆成两个字符 0xD842 和 0xDFB7 (即两个两字节字符,合成一个四字节 字符),就能得到正确的结果。码点大于0xFFFF的字符的四字节表示法,由UTF-16编码方法决定。

实例属性String.prototype.length:

字符串实例的 length 属性返回字符串的长度。


1. 'abc'.length // 3

实例方法String.prototype.charAt()

charAt 方法返回指定位置的字符,参数是从 0 开始编号的位置。

1. var s = new String('abc');
2.
3. s.charAt(1) // "b"
4. s.charAt(s.length - 1) // "c"

这个方法完全可以用数组下标替代。

1. 'abc'.charAt(1) // "b"
2. 'abc'[1] // "b"

如果参数为负数,或大于等于字符串的长度, charAt 返回空字符串。

1. 'abc'.charAt(-1) // ""
2. 'abc'.charAt(3) // ""

String.prototype.charCodeAt()

charCodeAt() 方法返回字符串指定位置的 Unicode 码点(十进制表示),相当 于 String.fromCharCode() 的逆操作。

1. 'abc'.charCodeAt(1) // 98

上面代码中, abc 的 1 号位置的字符是 b ,它的 Unicode 码点是 98 。 如果没有任何参数, charCodeAt 返回首字符的 Unicode 码点。

1. 'abc'.charCodeAt() // 97

如果参数为负数,或大于等于字符串的长度, charCodeAt 返回 NaN 。

1. 'abc'.charCodeAt(-1) // NaN
2. 'abc'.charCodeAt(4) // NaN

注意, charCodeAt 方法返回的 Unicode 码点不会大于65536(0xFFFF),也就是说,只返回两 个字节的字符的码点。如果遇到码点大于 65536 的字符(四个字节的字符),必须连续使用两 次 charCodeAt ,不仅读入 charCodeAt(i) ,还要读入 charCodeAt(i+1) ,将两个值放在一 起,才能得到准确的字符。

String.prototype.concat()

concat 方法用于连接两个字符串,返回一个新字符串,不改变原字符串。

1. var s1 = 'abc';
2. var s2 = 'def';
3.
4. s1.concat(s2) // "abcdef"
5. s1 // "abc"

该方法可以接受多个参数。

1. 'a'.concat('b', 'c') // "abc"

如果参数不是字符串, concat 方法会将其先转为字符串,然后再连接。

1. var one = 1;
2. var two = 2;
3. var three = '3';
4.
5. ''.concat(one, two, three) // "123"
6. one + two + three // "33"

上面代码中, concat 方法将参数先转成字符串再连接,所以返回的是一个三个字符的字符串。作为 对比,加号运算符在两个运算数都是数值时,不会转换类型,所以返回的是一个两个字符的字符串。

String.prototype.slice()

slice() 方法用于从原字符串取出子字符串并返回,不改变原字符串。它的第一个参数是子字符串的 开始位置,第二个参数是子字符串的结束位置(不含该位置)。

1. 'JavaScript'.slice(0, 4) // "Java"

如果省略第二个参数,则表示子字符串一直到原字符串结束。

1. 'JavaScript'.slice(4) // "Script"

如果参数是负值,表示从结尾开始倒数计算的位置,即该负值加上字符串长度。

1. 'JavaScript'.slice(-6) // "Script"
2. 'JavaScript'.slice(0, -6) // "Java"
3. 'JavaScript'.slice(-2, -1) // "p"

如果第一个参数大于第二个参数(正数情况下), slice() 方法返回一个空字符串。

1. 'JavaScript'.slice(2, 1) // ""

String.prototype.substring()

substring 方法用于从原字符串取出子字符串并返回,不改变原字符串,跟 slice 方法很相像。 它的第一个参数表示子字符串的开始位置,第二个位置表示结束位置(返回结果不含该位置)。

1. 'JavaScript'.substring(0, 4) // "Java"

如果省略第二个参数,则表示子字符串一直到原字符串的结束。

1. 'JavaScript'.substring(4) // "Script"

如果第一个参数大于第二个参数, substring 方法会自动更换两个参数的位置。

1. 'JavaScript'.substring(10, 4) // "Script"
2. // 等同于
3. 'JavaScript'.substring(4, 10) // "Script"

上面代码中,调换 substring 方法的两个参数,都得到同样的结果。 如果参数是负数, substring 方法会自动将负数转为0。

1. 'JavaScript'.substring(-3) // "JavaScript"
2. 'JavaScript'.substring(4, -3) // "Java"

上面代码中,第二个例子的参数 -3 会自动变成 0 ,等同于 'JavaScript'.substring(4, 0) 。由于第二个参数小于第一个参数,会自动互换位置,所以返回 Java 。 由于这些规则违反直觉,因此不建议使用 substring 方法,应该优先使用 slice 。

String.prototype.substr()

substr 方法用于从原字符串取出子字符串并返回,不改变原字符串, 跟 slice 和 substring 方法的作用相同。 substr 方法的第一个参数是子字符串的开始位置(从0开始计算),第二个参数是子字符串的长度。

1. 'JavaScript'.substr(4, 6) // "Script"

如果省略第二个参数,则表示子字符串一直到原字符串的结束。

1. 'JavaScript'.substr(4) // "Script"

如果第一个参数是负数,表示倒数计算的字符位置。如果第二个参数是负数,将被自动转为0,因此会 返回空字符串。

1. 'JavaScript'.substr(-6) // "Script"
2. 'JavaScript'.substr(4, -1) // ""

上面代码中,第二个例子的参数 -1 自动转为 0 ,表示子字符串长度为 0 ,所以返回空字符串。

String.prototype.indexOf(), String.prototype.lastIndexOf()

indexOf 方法用于确定一个字符串在另一个字符串中第一次出现的位置,返回结果是匹配开始的位 置。如果返回 -1 ,就表示不匹配。

1. 'hello world'.indexOf('o') // 4
2. 'JavaScript'.indexOf('script') // -1

indexOf 方法还可以接受第二个参数,表示从该位置开始向后匹配。

1. 'hello world'.indexOf('o', 6) // 7

lastIndexOf 方法的用法跟 indexOf 方法一致,主要的区别是 lastIndexOf 从尾部开始匹 配, indexOf 则是从头部开始匹配。

1. 'hello world'.lastIndexOf('o') // 7

另外, lastIndexOf 的第二个参数表示从该位置起向前匹配。

1. 'hello world'.lastIndexOf('o', 6) // 4

String.prototype.trim():

trim 方法用于去除字符串两端的空格,返回一个新字符串,不改变原字符串。

1. ' hello world '.trim()
2. // "hello world"

该方法去除的不仅是空格,还包括制表符( \t 、 \v )、换行符( \n )和回车符( \r )。

1. '\r\nabc \t'.trim() // 'abc'

String.prototype.toLowerCase(), String.prototype.toUpperCase():

toLowerCase 方法用于将一个字符串全部转为小写, toUpperCase 则是全部转为大写。它们都返 回一个新字符串,不改变原字符串。

1. 'Hello World'.toLowerCase()
2. // "hello world"
3.
4. 'Hello World'.toUpperCase()
5. // "HELLO WORLD"

String.prototype.match():

match 方法用于确定原字符串是否匹配某个子字符串,返回一个数组,成员为匹配的第一个字符串。 如果没有找到匹配,则返回 null 。

1. 'cat, bat, sat, fat'.match('at') // ["at"]
2. 'cat, bat, sat, fat'.match('xt') // null

返回的数组还有 index 属性和 input 属性,分别表示匹配字符串开始的位置和原始字符串。

1. var matches = 'cat, bat, sat, fat'.match('at');
2. matches.index // 1
3. matches.input // "cat, bat, sat, fat"

String.prototype.search(),String.prototype.replace():

search 方法的用法基本等同于 match ,但是返回值为匹配的第一个位置。如果没有找到匹配,则 返回 -1 。

1. 'cat, bat, sat, fat'.search('at') // 1

search 方法还可以使用正则表达式作为参数,详见《正则表达式》一节。 replace 方法用于替换匹配的子字符串,一般情况下只替换第一个匹配(除非使用带有 g 修饰符 的正则表达式)。

1. 'aaa'.replace('a', 'b') // "baa"

String.prototype.split():

split 方法按照给定规则分割字符串,返回一个由分割出来的子字符串组成的数组。

1. 'a|b|c'.split('|') // ["a", "b", "c"]

如果分割规则为空字符串,则返回数组的成员是原字符串的每一个字符。

1. 'a|b|c'.split('') // ["a", "|", "b", "|", "c"]

如果省略参数,则返回数组的唯一成员就是原字符串。

1. 'a|b|c'.split() // ["a|b|c"]

如果满足分割规则的两个部分紧邻着(即两个分割符中间没有其他字符),则返回数组之中会有一个空字符串。

1. 'a||c'.split('|') // ['a', '', 'c']

如果满足分割规则的部分处于字符串的开头或结尾(即它的前面或后面没有其他字符),则返回数组的 第一个或最后一个成员是一个空字符串。

1. '|b|c'.split('|') // ["", "b", "c"]
2. 'a|b|'.split('|') // ["a", "b", ""]

split 方法还可以接受第二个参数,限定返回数组的最大成员数。

1. 'a|b|c'.split('|', 0) // []
2. 'a|b|c'.split('|', 1) // ["a"]
3. 'a|b|c'.split('|', 2) // ["a", "b"]
4. 'a|b|c'.split('|', 3) // ["a", "b", "c"]
5. 'a|b|c'.split('|', 4) // ["a", "b", "c"]

上面代码中, split 方法的第二个参数,决定了返回数组的成员数。

String.prototype.localeCompare():

localeCompare 方法用于比较两个字符串。它返回一个整数,如果小于0,表示第一个字符串小于第二个字符串;如果等于0,表示两者相等;如果大于0,表示第一个字符串大于第二个字符串。

1. 'apple'.localeCompare('banana') // -1
2. 'apple'.localeCompare('apple') // 0

该方法的最大特点,就是会考虑自然语言的顺序。举例来说,正常情况下,大写的英文字母小于小写字母。

1. 'B' > 'a' // false

上面代码中,字母 B 小于字母 a 。因为 JavaScript 采用的是 Unicode 码点比较, B 的 码点是66,而 a 的码点是97。

但是, localeCompare 方法会考虑自然语言的排序情况,将 B 排在 a 的前面。

1. 'B'.localeCompare('a') // 1

上面代码中, localeCompare 方法返回整数1,表示 B 较大。 localeCompare 还可以有第二个参数,指定所使用的语言(默认是英语),然后根据该语言的规则 进行比较。

1. 'ä'.localeCompare('z', 'de') // -1
2. 'ä'.localeCompare('z', 'sv') // 1

上面代码中, de 表示德语, sv 表示瑞典语。德语中, ä 小于 z ,所以返回 -1 ;瑞典语 中, ä 大于 z ,所以返回 1 。

8,Math 对象

Math 是 JavaScript 的原生对象,提供各种数学功能。该对象不是构造函数,不能生成实例,所 有的属性和方法都必须在 Math 对象上调用。

静态属性:

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 方法返回参数值的绝对值。

1. Math.abs(1) // 1
2. Math.abs(-1) // 1

Math.max(),Math.min()

Math.max 方法返回参数之中最大的那个值, Math.min 返回最小的那个值。如果参数为空, Math.min 返回 Infinity , Math.max 返回 -Infinity 。

1. Math.max(2, -1, 5) // 5
2. Math.min(2, -1, 5) // -1
3. Math.min() // Infinity
4. Math.max() // -Infinity

Math.floor(),Math.ceil():

Math.floor 方法返回小于参数值的最大整数(地板值)。

1. Math.floor(3.2) // 3
2. Math.floor(-3.2) // -4

Math.ceil 方法返回大于参数值的最小整数(天花板值)。

1. Math.ceil(3.2) // 4
2. Math.ceil(-3.2) // -3

这两个方法可以结合起来,实现一个总是返回数值的整数部分的函数。

1. function ToInteger(x) {
2. x = Number(x);
3. return x < 0 ? Math.ceil(x) : Math.floor(x);
4. }
5.
6. ToInteger(3.2) // 3
7. ToInteger(3.5) // 3
8. ToInteger(3.8) // 3
9. ToInteger(-3.2) // -3
10. ToInteger(-3.5) // -3
11. ToInteger(-3.8) // -3

上面代码中,不管正数或负数, ToInteger 函数总是返回一个数值的整数部分。

Math.round():

Math.round 方法用于四舍五入。

1. Math.round(0.1) // 0
2. Math.round(0.5) // 1
3. Math.round(0.6) // 1
4.
5. // 等同于
6. Math.floor(x + 0.5)

注意,它对负数的处理(主要是对 0.5 的处理)。

1. Math.round(-1.1) // -1
2. Math.round(-1.5) // -1
3. Math.round(-1.6) // -2

Math.pow():

Math.pow 方法返回以第一个参数为底数、第二个参数为指数的幂运算值。

1. // 等同于 2 ** 2
2. Math.pow(2, 2) // 4
3. // 等同于 2 ** 3
4. Math.pow(2, 3) // 8

下面是计算圆面积的方法。

1. var radius = 20;
2. var area = Math.PI * Math.pow(radius, 2);

Math.sqrt():

Math.sqrt 方法返回参数值的平方根。如果参数是一个负值,则返回 NaN 。

1. Math.sqrt(4) // 2
2. Math.sqrt(-4) // NaN

Math.log():

Math.log 方法返回以 e 为底的自然对数值。

1. Math.log(Math.E) // 1
2. Math.log(10) // 2.302585092994046

如果要计算以10为底的对数,可以先用 Math.log 求出自然对数,然后除以 Math.LN10 ;求以2为 底的对数,可以除以 Math.LN2 。

1. Math.log(100)/Math.LN10 // 2
2. Math.log(8)/Math.LN2 // 3

Math.exp():

Math.exp 方法返回常数 e 的参数次方。

1. Math.exp(1) // 2.718281828459045
2. Math.exp(3) // 20.085536923187668

Math.random():

Math.random() 返回0到1之间的一个伪随机数,可能等于0,但是一定小于1。

1. Math.random() // 0.7151307314634323

任意范围的随机数生成函数如下。

1. function getRandomArbitrary(min, max) {
2. return Math.random() * (max - min) + min;
3. }
4.
5. getRandomArbitrary(1.5, 6.5)
6. // 2.4942810038223864

任意范围的随机整数生成函数如下。

1. function getRandomInt(min, max) {
2. return Math.floor(Math.random() * (max - min + 1)) + min;
3. }
4.
5. getRandomInt(1, 6) // 5

返回随机字符的例子如下。

1. function random_str(length) {
2. var ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
3. ALPHABET += 'abcdefghijklmnopqrstuvwxyz';
4. ALPHABET += '0123456789-_';
5. var str = '';
6. for (var i = 0; i < length; ++i) {
7. var rand = Math.floor(Math.random() * ALPHABET.length);
8. str += ALPHABET.substring(rand, rand + 1);
9. }
10. return str;
11. }
12.
13. random_str(6) // "NdQKOr"

上面代码中, random_str 函数接受一个整数作为参数,返回变量 ALPHABET 内的随机字符所组成 的指定长度的字符串。

三角函数方法

Math 对象还提供一系列三角函数方法。

        Math.sin() :返回参数的正弦(参数为弧度值)

        Math.cos() :返回参数的余弦(参数为弧度值)

        Math.tan() :返回参数的正切(参数为弧度值)

        Math.asin() :返回参数的反正弦(返回值为弧度值)

        Math.acos() :返回参数的反余弦(返回值为弧度值)

        Math.atan() :返回参数的反正切(返回值为弧度值)

1. Math.sin(0) // 0
2. Math.cos(0) // 1
3. Math.tan(0) // 0
4.
5. Math.sin(Math.PI / 2) // 1
6.
7. Math.asin(1) // 1.5707963267948966
8. Math.acos(1) // 0
9. Math.atan(1) // 0.7853981633974483

9,Date 对象

Date 对象是 JavaScript 原生的时间库。它以国际标准时间(UTC)1970年1月1日00:00:00作 为时间的零点,可以表示的时间范围是前后各1亿天(单位为毫秒)。

普通函数的用法:

Date 对象可以作为普通函数直接调用,返回一个代表当前时间的字符串。

1. Date()
2. // "Tue Dec 01 2015 09:34:43 GMT+0800 (CST)"

注意,即使带有参数, Date 作为普通函数使用时,返回的还是当前时间。

1. Date(2000, 1, 1)
2. // "Tue Dec 01 2015 09:34:43 GMT+0800 (CST)"

上面代码说明,无论有没有参数,直接调用 Date 总是返回当前时间。

构造函数的用法:

Date 还可以当作构造函数使用。对它使用 new 命令,会返回一个 Date 对象的实例。如果不加 参数,实例代表的就是当前时间。

1. var today = new Date();

Date 实例有一个独特的地方。其他对象求值的时候,都是默认调用 .valueOf() 方法,但 是 Date 实例求值的时候,默认调用的是 toString() 方法。这导致对 Date 实例求值,返回的 是一个字符串,代表该实例对应的时间。

1. var today = new Date();
2.
3. today
4. // "Tue Dec 01 2015 09:34:43 GMT+0800 (CST)"
5.
6. // 等同于
7. today.toString()
8. // "Tue Dec 01 2015 09:34:43 GMT+0800 (CST)"

上面代码中, today 是 Date 的实例,直接求值等同于调用 toString 方法。 作为构造函数时, Date 对象可以接受多种格式的参数,返回一个该参数对应的时间实例。

1. // 参数为时间零点开始计算的毫秒数
2. new Date(1378218728000)
3. // Tue Sep 03 2013 22:32:08 GMT+0800 (CST)
4.
5. // 参数为日期字符串
6. new Date('January 6, 2013');
7. // Sun Jan 06 2013 00:00:00 GMT+0800 (CST)
8.
9. // 参数为多个整数,
10. // 代表年、月、日、小时、分钟、秒、毫秒
11. new Date(2013, 0, 1, 0, 0, 0, 0)
12. // Tue Jan 01 2013 00:00:00 GMT+0800 (CST)

关于 Date 构造函数的参数的说明:

第一点,参数可以是负整数,代表1970年元旦之前的时间。

1. new Date(-1378218728000)
2. // Fri Apr 30 1926 17:27:52 GMT+0800 (CST)

第二点,只要是能被 Date.parse() 方法解析的字符串,都可以当作参数。

1. new Date('2013-2-15')
2. new Date('2013/2/15')
3. new Date('02/15/2013')
4. new Date('2013-FEB-15')
5. new Date('FEB, 15, 2013')
6. new Date('FEB 15, 2013')
7. new Date('February, 15, 2013')
8. new Date('February 15, 2013')
9. new Date('15 Feb 2013')
10. new Date('15, February, 2013')
11. // Fri Feb 15 2013 00:00:00 GMT+0800 (CST)

上面多种日期字符串的写法,返回的都是同一个时间。

第三,参数为年、月、日等多个整数时,年和月是不能省略的,其他参数都可以省略的。也就是说,这 时至少需要两个参数,因为如果只使用“年”这一个参数, Date 会将其解释为毫秒数。

1. new Date(2013)
2. // Thu Jan 01 1970 08:00:02 GMT+0800 (CST)

上面代码中,2013被解释为毫秒数,而不是年份。

1. new Date(2013, 0)
2. // Tue Jan 01 2013 00:00:00 GMT+0800 (CST)
3. new Date(2013, 0, 1)
4. // Tue Jan 01 2013 00:00:00 GMT+0800 (CST)
5. new Date(2013, 0, 1, 0)
6. // Tue Jan 01 2013 00:00:00 GMT+0800 (CST)
7. new Date(2013, 0, 1, 0, 0, 0, 0)
8. // 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月。

1. new Date(2013, 15)
2. // Tue Apr 01 2014 00:00:00 GMT+0800 (CST)
3. new Date(2013, 0, 0)
4. // Mon Dec 31 2012 00:00:00 GMT+0800 (CST)

上面代码的第二个例子,日期设为 0 ,就代表上个月的最后一天。 参数还可以使用负数,表示扣去的时间。

1. new Date(2013, -1)
2. // Sat Dec 01 2012 00:00:00 GMT+0800 (CST)
3. new Date(2013, 0, -1)
4. // Sun Dec 30 2012 00:00:00 GMT+0800 (CST)

上面代码中,分别对月和日使用了负数,表示从基准日扣去相应的时间。

日期的运算:

类型自动转换时, Date 实例如果转为数值,则等于对应的毫秒数;如果转为字符串,则等于对应的 日期字符串。所以,两个日期实例对象进行减法运算时,返回的是它们间隔的毫秒数;进行加法运算时,返回的是两个字符串连接而成的新字符串。

1. var d1 = new Date(2000, 2, 1);
2. var d2 = new Date(2000, 3, 1);
3.
4. d2 - d1
5. // 2678400000
6. d2 + d1
7.
// "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。

1. Date.now() // 1364026285194

Date.parse()

Date.parse 方法用来解析日期字符串,返回该时间距离时间零点(1970年1月1日 00:00:00)的 毫秒数。 日期字符串应该符合 RFC 2822 和 ISO 8061 这两个标准,即 YYYY-MM-DDTHH:mm:ss.sssZ 格 式,其中最后的 Z 表示时区。但是,其他格式也可以被解析,请看下面的例子。

1. Date.parse('Aug 9, 1995')
2. Date.parse('January 26, 2011 13:51:50')
3. Date.parse('Mon, 25 Dec 1995 13:30:00 GMT')
4. Date.parse('Mon, 25 Dec 1995 13:30:00 +0430')
5. Date.parse('2011-10-10')
6. Date.parse('2011-10-10T14:48:00')

上面的日期字符串都可以解析。 如果解析失败,返回 NaN 。

1. Date.parse('xxx') // NaN

Date.UTC()

Date.UTC 方法接受年、月、日等变量作为参数,返回该时间距离时间零点(1970年1月1日 00:00:00 UTC)的毫秒数。

1. // 格式
2. Date.UTC(year, month[, date[, hrs[, min[, sec[, ms]]]]])
3.
4. // 用法
5. Date.UTC(2011, 0, 1, 2, 3, 4, 567)
6. // 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 方法。

1. var d = new Date();
2.
3. d.valueOf() // 1362790014817
4. d.getTime() // 1362790014817

预期为数值的场合, Date 实例会自动调用该方法,所以可以用下面的方法计算时间的间隔。

1. var start = new Date();
2. // ...
3. var end = new Date();
4. var elapsed = end - start;

to 类方法:

(1)Date.prototype.toString()

toString 方法返回一个完整的日期字符串。

1. var d = new Date(2013, 0, 1);
2.
3. d.toString()
4. // "Tue Jan 01 2013 00:00:00 GMT+0800 (CST)"
5. d
6. // "Tue Jan 01 2013 00:00:00 GMT+0800 (CST)"

因为 toString 是默认的调用方法,所以如果直接读取 Date 实例,就相当于调用这个方法。

(2)Date.prototype.toUTCString()

toUTCString 方法返回对应的 UTC 时间,也就是比北京时间晚8个小时。

1. var d = new Date(2013, 0, 1);
2.
3. d.toUTCString()
4. // "Mon, 31 Dec 2012 16:00:00 GMT"

(3)Date.prototype.toISOString()

toISOString 方法返回对应时间的 ISO8601 写法。

1. var d = new Date(2013, 0, 1);
2.
3. d.toISOString()
4. // "2012-12-31T16:00:00.000Z"

注意, toISOString 方法返回的总是 UTC 时区的时间。

(4)Date.prototype.toJSON()

toJSON 方法返回一个符合 JSON 格式的 ISO 日期字符串,与 toISOString 方法的返回结果完 全相同。

1. var d = new Date(2013, 0, 1);
2.
3. d.toJSON()
4. // "2012-12-31T16:00:00.000Z"

(5)Date.prototype.toDateString()

toDateString 方法返回日期字符串(不含小时、分和秒)。

1. var d = new Date(2013, 0, 1);
2. d.toDateString() // "Tue Jan 01 2013"

(6)Date.prototype.toTimeString()

toTimeString 方法返回时间字符串(不含年月日)。

1. var d = new Date(2013, 0, 1);
2. d.toTimeString() // "00:00:00 GMT+0800 (CST)"

(7)本地时间

以下三种方法,可以将 Date 实例转为表示本地时间的字符串。

        Date.prototype.toLocaleString() :完整的本地时间。

        Date.prototype.toLocaleDateString() :本地日期(不含小时、分和秒)。

        Date.prototype.toLocaleTimeString() :本地时间(不含年月日)。

下面是用法实例。

1. var d = new Date(2013, 0, 1);
2.
3. d.toLocaleString()
4. // 中文版浏览器为"2013年1月1日 上午12:00:00"
5. // 英文版浏览器为"1/1/2013 12:00:00 AM"
6.
7. d.toLocaleDateString()
8. // 中文版浏览器为"2013年1月1日"
9. // 英文版浏览器为"1/1/2013"
10.
11. d.toLocaleTimeString()
12. // 中文版浏览器为"上午12:00:00"
13. // 英文版浏览器为"12:00:00 AM"

这三个方法都有两个可选的参数。

1. dateObj.toLocaleString([locales[, options]])
2. dateObj.toLocaleDateString([locales[, options]])
3. dateObj.toLocaleTimeString([locales[, options]])

这两个参数中, locales 是一个指定所用语言的字符串, options 是一个配置对象。下面 是 locales 的例子,分别采用 en-US 和 zh-CN 语言设定。

1. var d = new Date(2013, 0, 1);
2.
3. d.toLocaleString('en-US') // "1/1/2013, 12:00:00 AM"
4. d.toLocaleString('zh-CN') // "2013/1/1 上午12:00:00"
5.
6. d.toLocaleDateString('en-US') // "1/1/2013"
7. d.toLocaleDateString('zh-CN') // "2013/1/1"
8.
9. d.toLocaleTimeString('en-US') // "12:00:00 AM"
10. 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 的时区数据库。

        timeZooneName :可能的值为 long 、 short 。

        hour12 :24小时周期还是12小时周期,可能的值为 true 、 false 。

下面是用法实例。

1. var d = new Date(2013, 0, 1);
2.
3. d.toLocaleDateString('en-US', {
4.     weekday: 'long',
5.     year: 'numeric',
6.     month: 'long',
7.     day: 'numeric'
8. })
9. // "Tuesday, January 1, 2013"
10.
11. d.toLocaleDateString('en-US', {
12.     day: "2-digit",
13.     month: "long",
14.     year: "2-digit"
15. });
16. // "January 01, 13"
17.
18. d.toLocaleTimeString('en-US', {
19.     timeZone: 'UTC',
20.     timeZoneName: 'short'
21. })
22. // "4:00:00 PM UTC"
23.
24. d.toLocaleTimeString('en-US', {
25.     timeZone: 'Asia/Shanghai',
26.     timeZoneName: 'long'
27. })
28. // "12:00:00 AM China Standard Time"
29.
30. d.toLocaleTimeString('en-US', {
31.     hour12: false
32. })
33. // "00:00:00"
34.
35. d.toLocaleTimeString('en-US', {
36.     hour12: true
37. })
38. // "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(十二月)

1. var d = new Date('January 6, 2013');
2.
3. d.getDate() // 6
4. d.getMonth() // 0
5. d.getFullYear() // 2013
6. d.getTimezoneOffset() // -480

上面代码中,最后一行返回 -480 ,即 UTC 时间减去当前时间,单位是分钟。 -480 表示 UTC 比当前时间少480分钟,即当前时区比 UTC 早8个小时。 下面是一个例子,计算本年度还剩下多少天。

1. function leftDays() {
2.     var today = new Date();
3.     var endYear = new Date(today.getFullYear(), 11, 31, 23, 59, 59, 999);
4.     var msPerDay = 24 * 60 * 60 * 1000;
5.     return Math.round((endYear.getTime() - today.getTime()) / msPerDay);
6. }

上面这些 get* 方法返回的都是当前时区的时间, Date 对象还提供了这些方法对应的 UTC 版 本,用来返回 UTC 时间。

        getUTCDate()

        getUTCFullYear()

        getUTCMonth()

        getUTCDay()

        getUTCHours()

        getUTCMinutes()

        getUTCSeconds()

        getUTCMilliseconds()

1. var d = new Date('January 6, 2013');
2.
3. d.getDate() // 6
4. 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() :设置毫秒(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月。

1. var d = new Date ('January 6, 2013');
2.
3. d // Sun Jan 06 2013 00:00:00 GMT+0800 (CST)
4. d.setDate(9) // 1357660800000
5. d // Wed Jan 09 2013 00:00:00 GMT+0800 (CST)

set* 方法的参数都会自动折算。以 setDate() 为例,如果参数超过当月的最大天数,则向下一个 月顺延,如果参数是负数,表示从上个月的最后一天开始减去的天数。

1. var d1 = new Date('January 6, 2013');
2.
3. d1.setDate(32) // 1359648000000
4. d1 // Fri Feb 01 2013 00:00:00 GMT+0800 (CST)
5.
6. var d2 = new Date ('January 6, 2013');
7.
8. d2.setDate(-1) // 1356796800000
9. 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 类方法,可以结合使用,得到相对时间。

1. var d = new Date();
2.
3. // 将日期向后推1000天
4. d.setDate(d.getDate() + 1000);
5. // 将时间设为6小时后
6. d.setHours(d.getHours() + 6);
7. // 将年份设为去年
8. d.setFullYear(d.getFullYear() - 1);

set* 系列方法除了 setTime() ,都有对应的 UTC 版本,即设置 UTC 时区的时间。

        setUTCDate()

        setUTCFullYear()

        setUTCHours()

        setUTCMilliseconds()

        setUTCMinutes()

        setUTCMonth()

        setUTCSeconds()

1. var d = new Date('January 6, 2013');
2. d.getUTCHours() // 16
3. d.setUTCHours(22) // 1357423200000
4. d // Sun Jan 06 2013 06:00:00 GMT+0800 (CST)

上面代码中,本地时区(东八时区)的1月6日0点0分,是 UTC 时区的前一天下午16点。设为 UTC 时区的22点以后,就变为本地时区的上午6点。

10,RegExp 对象

RegExp 对象提供正则表达式的功能。

正则表达式(regular expression)是一种表达文本模式(即字符串结构)的方法,有点像字符串 的模板,常常用来按照“给定模式”匹配文本。比如,正则表达式给出一个 Email 地址的模式,然后用 它来确定一个字符串是否为 Email 地址。JavaScript 的正则表达式体系是参照 Perl 5 建立 的。 新建正则表达式有两种方法。一种是使用字面量,以斜杠表示开始和结束。

1. var regex = /xyz/;

另一种是使用 RegExp 构造函数。

1. var regex = new RegExp('xyz');

上面两种写法是等价的,都新建了一个内容为 xyz 的正则表达式对象。它们的主要区别是,第一种方 法在引擎编译代码时,就会新建正则表达式,第二种方法在运行时新建正则表达式,所以前者的效率较 高。而且,前者比较便利和直观,所以实际应用中,基本上都采用字面量定义正则表达式。 RegExp 构造函数还可以接受第二个参数,表示修饰符。

1. var regex = new RegExp('xyz', 'i');
2. // 等价于
3. var regex = /xyz/i;

上面代码中,正则表达式 /xyz/ 有一个修饰符 i 。

实例属性:

正则对象的实例属性分成两类。 一类是修饰符相关,用于了解设置了什么修饰符。

        RegExp.prototype.ignoreCase :返回一个布尔值,表示是否设置了 i 修饰符。

        RegExp.prototype.global :返回一个布尔值,表示是否设置了 g 修饰符。

        RegExp.prototype.multiline :返回一个布尔值,表示是否设置了 m 修饰符。

        RegExp.prototype.flags :返回一个字符串,包含了已经设置的所有修饰符,按字母排序。上面四个属性都是只读的。

1. var r = /abc/igm;
2.
3. r.ignoreCase // true
4. r.global // true
5. r.multiline // true
6. r.flags // 'gim'

另一类是与修饰符无关的属性,主要是下面两个。

        RegExp.prototype.lastIndex :返回一个整数,表示下一次开始搜索的位置。该属性可读写, 但是只在进行连续搜索时有意义,详细介绍请看后文。

        RegExp.prototype.source :返回正则表达式的字符串形式(不包括反斜杠),该属性只读。

1. var r = /abc/igm;
2.
3. r.lastIndex // 0
4. r.source // "abc"

实例方法:RegExp.prototype.test()

正则实例对象的 test 方法返回一个布尔值,表示当前模式是否能匹配参数字符串。

1. /cat/.test('cats and dogs') // true

上面代码验证参数字符串之中是否包含 cat ,结果返回 true 。 如果正则表达式带有 g 修饰符,则每一次 test 方法都从上一次结束的位置开始向后匹配。

1. var r = /x/g;
2. var s = '_x_x';
3.
4. r.lastIndex // 0
5. r.test(s) // true
6.
7. r.lastIndex // 2
8. r.test(s) // true
9.
10. r.lastIndex // 4
11. r.test(s) // false

上面代码的正则表达式使用了 g 修饰符,表示是全局搜索,会有多个结果。接着,三次使 用 test 方法,每一次开始搜索的位置都是上一次匹配的后一个位置。 带有 g 修饰符时,可以通过正则对象的 lastIndex 属性指定开始搜索的位置。

1. var r = /x/g;
2. var s = '_x_x';
3.
4. r.lastIndex = 4;
5. r.test(s) // false
6.
7. r.lastIndex // 0
8. r.test(s)

上面代码指定从字符串的第五个位置开始搜索,这个位置为空,所以返回 false 。同 时, lastIndex 属性重置为 0 ,所以第二次执行 r.test(s) 会返回 true 。 注意,带有 g 修饰符时,正则表达式内部会记住上一次的 lastIndex 属性,这时不应该更换所要 匹配的字符串,否则会有一些难以察觉的错误。

1. var r = /bb/g;
2. r.test('bb') // true
3. r.test('-bb-') // false

上面代码中,由于正则表达式 r 是从上一次的 lastIndex 位置开始匹配,导致第二次执 行 test 方法时出现预期以外的结果。 lastIndex 属性只对同一个正则表达式有效,所以下面这样写是错误的。

1. var count = 0;
2. while (/a/g.test('babaa')) count++;

上面代码会导致无限循环,因为 while 循环的每次匹配条件都是一个新的正则表达式,导 致 lastIndex 属性总是等于0。 如果正则模式是一个空字符串,则匹配所有字符串。

1. new RegExp('').test('abc')
2. // true

RegExp.prototype.exec():

正则实例对象的 exec() 方法,用来返回匹配结果。如果发现匹配,就返回一个数组,成员是匹配成 功的子字符串,否则返回 null 。

1. var s = '_x_x';
2. var r1 = /x/;
3. var r2 = /y/;
4.
5. r1.exec(s) // ["x"]
6. r2.exec(s) // null

上面代码中,正则对象 r1 匹配成功,返回一个数组,成员是匹配结果;正则对象 r2 匹配失败, 返回 null 。 如果正则表示式包含圆括号(即含有“组匹配”),则返回的数组会包括多个成员。第一个成员是整个匹 配成功的结果,后面的成员就是圆括号对应的匹配成功的组。也就是说,第二个成员对应第一个括号, 第三个成员对应第二个括号,以此类推。整个数组的 length 属性等于组匹配的数量再加1。

1. var s = '_x_x';
2. var r = /_(x)/;
3.
4. r.exec(s) // ["_x", "x"]

上面代码的 exec() 方法,返回一个数组。第一个成员是整个匹配的结果,第二个成员是圆括号匹配 的结果。

exec() 方法的返回数组还包含以下两个属性:

        input :整个原字符串。

        index :模式匹配成功的开始位置(从0开始计数)。

1. var r = /a(b+)a/;
2. var arr = r.exec('_abbba_aba_');
3.
4. arr // ["abbba", "bbb"]
5.
6. arr.index // 1
7. arr.input // "_abbba_aba_"

上面代码中的 index 属性等于1,是因为从原字符串的第二个位置开始匹配成功。 如果正则表达式加上 g 修饰符,则可以使用多次 exec() 方法,下一次搜索的位置从上一次匹配成 功结束的位置开始。

1. var reg = /a/g;
2. var str = 'abc_abc_abc'
3.
4. var r1 = reg.exec(str);
5. r1 // ["a"]
6. r1.index // 0
7. reg.lastIndex // 1
8.
9. var r2 = reg.exec(str);
10. r2 // ["a"]
11. r2.index // 4
12. reg.lastIndex // 5
13.
14. var r3 = reg.exec(str);
15. r3 // ["a"]
16. r3.index // 8
17. reg.lastIndex // 9
18.
19. var r4 = reg.exec(str);
20. r4 // null
21. reg.lastIndex // 0

上面代码连续用了四次 exec() 方法,前三次都是从上一次匹配结束的位置向后匹配。当第三次匹配 结束以后,整个字符串已经到达尾部,匹配结果返回 null ,正则实例对象的 lastIndex 属性也重 置为 0 ,意味着第四次匹配将从头开始。 利用 g 修饰符允许多次匹配的特点,可以用一个循环完成全部匹配。

1. var reg = /a/g;
2. var str = 'abc_abc_abc'
3.
4. while(true) {
5.     var match = reg.exec(str);
6.     if (!match) break;
7.     console.log('#' + match.index + ':' + match[0]);
8. }
9. // #0:a
10. // #4:a
11. // #8:a

上面代码中,只要 exec() 方法不返回 null ,就会一直循环下去,每次输出匹配的位置和匹配的 文本。 正则实例对象的 lastIndex 属性不仅可读,还可写。设置了 g 修饰符的时候,只要手动设置 了 lastIndex 的值,就会从指定位置开始匹配。

字符串的实例方法:

字符串的实例方法之中,有4种与正则表达式有关。

        String.prototype.match() :返回一个数组,成员是所有匹配的子字符串。

        String.prototype.search() :按照给定的正则表达式进行搜索,返回一个整数,表示匹配开 始的位置。

        String.prototype.replace() :按照给定的正则表达式进行替换,返回替换后的字符串。

        String.prototype.split() :按照给定规则进行字符串分割,返回一个数组,包含分割后的各 个成员。

String.prototype.match():

字符串实例对象的 match 方法对字符串进行正则匹配,返回匹配结果。

1. var s = '_x_x';
2. var r1 = /x/;
3. var r2 = /y/;
4.
5. s.match(r1) // ["x"]
6. s.match(r2) // null

从上面代码可以看到,字符串的 match 方法与正则对象的 exec 方法非常类似:匹配成功返回一个 数组,匹配失败返回 null 。 如果正则表达式带有 g 修饰符,则该方法与正则对象的 exec 方法行为不同,会一次性返回所有匹 配成功的结果。

1. var s = 'abba';
2. var r = /a/g;
3.
4. s.match(r) // ["a", "a"]
5. r.exec(s) // ["a"]

设置正则表达式的 lastIndex 属性,对 match 方法无效,匹配总是从字符串的第一个字符开始。

1. var r = /a|b/g;
2. r.lastIndex = 7;
3. 'xaxb'.match(r) // ['a', 'b']
4. r.lastIndex // 0

上面代码表示,设置正则对象的 lastIndex 属性是无效的。

String.prototype.search():

字符串对象的 search 方法,返回第一个满足条件的匹配结果在整个字符串中的位置。如果没有任何 匹配,则返回 -1 。

1. '_x_x'.search(/x/)
2. // 1

上面代码中,第一个匹配结果出现在字符串的 1 号位置。

String.prototype.replace():

字符串对象的 replace 方法可以替换匹配的值。它接受两个参数,第一个是正则表达式,表示搜索模 式,第二个是替换的内容。

1. str.replace(search, replacement)

正则表达式如果不加 g 修饰符,就替换第一个匹配成功的值,否则替换所有匹配成功的值。

1. 'aaa'.replace('a', 'b') // "baa"
2. 'aaa'.replace(/a/, 'b') // "baa"
3. 'aaa'.replace(/a/g, 'b') // "bbb"

上面代码中,最后一个正则表达式使用了 g 修饰符,导致所有的 a 都被替换掉了。 replace 方法的一个应用,就是消除字符串首尾两端的空格。

1. var str = ' #id div.class ';
2.
3. str.replace(/^\s+|\s+$/g, '')
4. // "#id div.class"

replace 方法的第二个参数可以使用美元符号 $ ,用来指代所替换的内容。

        $& :匹配的子字符串。

        $` :匹配结果前面的文本。

        $' :匹配结果后面的文本。

        $n :匹配成功的第 n 组内容, n 是从1开始的自然数。

        $$ :指代美元符号 $ 。

1. 'hello world'.replace(/(\w+)\s(\w+)/, '$2 $1')
2. // "world hello"
3.
4. 'abc'.replace('b', '[$`-$&-$\']')
5. // "a[a-b-c]c"

上面代码中,第一个例子是将匹配的组互换位置,第二个例子是改写匹配的值。 replace 方法的第二个参数还可以是一个函数,将每一个匹配内容替换为函数返回值。

1. '3 and 5'.replace(/[0-9]+/g, function (match) {
2.     return 2 * match;
3. })
4. // "6 and 10"
5.
6. var a = 'The quick brown fox jumped over the lazy dog.';
7. var pattern = /quick|brown|lazy/ig;
8.
9. a.replace(pattern, function replacer(match) {
10.     return match.toUpperCase();
11. });
12. // The QUICK BROWN fox jumped over the LAZY dog.

作为 replace 方法第二个参数的替换函数,可以接受多个参数。其中,第一个参数是捕捉到的内容, 第二个参数是捕捉到的组匹配(有多少个组匹配,就有多少个对应的参数)。此外,最后还可以添加两 个参数,倒数第二个参数是捕捉到的内容在整个字符串中的位置(比如从第五个位置开始),最后一个 参数是原字符串。下面是一个网页模板替换的例子。

1. var prices = {
2.     'p1': '$1.99',
3.     'p2': '$9.99',
4.     'p3': '$5.00'
5. };
6.
7. var template = '<span id="p1"></span>'
8. + '<span id="p2"></span>'
9. + '<span id="p3"></span>';
10.
11. template.replace(
12.     /(<span id=")(.*?)(">)(<\/span>)/g,
13.         function(match, $1, $2, $3, $4){
14.             return $1 + $2 + $3 + prices[$2] + $4;
15.     }
16. );
17.
// "<span id="p1">$1.99</span><span id="p2">$9.99</span><span
id="p3">$5.00</span>"

上面代码的捕捉模式中,有四个括号,所以会产生四个组匹配,在匹配函数中用 $1 到 $4 表示。 匹配函数的作用是将价格插入模板中。

String.prototype.split():

字符串对象的 split 方法按照正则规则分割字符串,返回一个由分割后的各个部分组成的数组。

1. str.split(separator, [limit])

该方法接受两个参数,第一个参数是正则表达式,表示分隔规则,第二个参数是返回数组的最大成员数。

1. // 非正则分隔
2. 'a, b,c, d'.split(',')
3. // [ 'a', ' b', 'c', ' d' ]
4.
5. // 正则分隔,去除多余的空格
6. 'a, b,c, d'.split(/, */)
7. // [ 'a', 'b', 'c', 'd' ]
8.
9. // 指定返回数组的最大成员
10. 'a, b,c, d'.split(/, */, 2)
11. [ 'a', 'b' ]

上面代码使用正则表达式,去除了子字符串的逗号后面的空格。

1. // 例一
2. 'aaa*a*'.split(/a*/)
3. // [ '', '*', '*' ]
4.
5. // 例二
6. 'aaa**a*'.split(/a*/)
7. // ["", "*", "*", "*"]

上面代码的分割规则是0次或多次的 a ,由于正则默认是贪婪匹配,所以例一的第一个分隔符 是 aaa ,第二个分割符是 a ,将字符串分成三个部分,包含开始处的空字符串。例二的第一个分 隔符是 aaa ,第二个分隔符是0个 a (即空字符),第三个分隔符是 a ,所以将字符串分成四 个部分。 如果正则表达式带有括号,则括号匹配的部分也会作为数组成员返回。

1. 'aaa*a*'.split(/(a*)/)
2. // [ '', 'aaa', '*', 'a', '*' ]

上面代码的正则表达式使用了括号,第一个组匹配是 aaa ,第二个组匹配是 a ,它们都作为数组 成员返回。

匹配规则

字面量字符和元字符:

大部分字符在正则表达式中,就是字面的含义,比如 /a/ 匹配 a , /b/ 匹配 b 。如果在正则 表达式之中,某个字符只表示它字面的含义(就像前面的 a 和 b ),那么它们就叫做“字面量字 符”(literal characters)。

1. /dog/.test('old dog') // true

上面代码中正则表达式的 dog ,就是字面量字符,所以 /dog/ 匹配 old dog ,因为它就表 示 d 、 o 、 g 三个字母连在一起。 除了字面量字符以外,还有一部分字符有特殊含义,不代表字面的意思。它们叫做“元字 符”(metacharacters),主要有以下几个。

(1)点字符(.)

点字符( . )匹配除回车( \r )、换行( \n ) 、行分隔符( \u2028 )和段分隔符 ( \u2029 )以外的所有字符。注意,对于码点大于 0xFFFF 字符,点字符不能正确匹配,会认为 这是两个字符。

1. /c.t/

上面代码中, c.t 匹配 c 和 t 之间包含任意一个字符的情况,只要这三个字符在同一行,比 如 cat 、 c2t 、 c-t 等等,但是不匹配 coot 。

(2)位置字符

位置字符用来提示字符所处的位置,主要有两个字符。

        ^ 表示字符串的开始位置

        $ 表示字符串的结束位置

1. // test必须出现在开始位置
2. /^test/.test('test123') // true
3.
4. // test必须出现在结束位置
5. /test$/.test('new test') // true
6.
7. // 从开始位置到结束位置只有test
8. /^test$/.test('test') // true
9. /^test$/.test('test test') // false

(3)选择符( | )

竖线符号( | )在正则表达式中表示“或关系”(OR),即 cat|dog 表示匹配 cat 或 dog 。

1. /11|22/.test('911') // true

上面代码中,正则表达式指定必须匹配 11 或 22 。 多个选择符可以联合使用。

1. // 匹配fred、barney、betty之中的一个
2. /fred|barney|betty/

选择符会包括它前后的多个字符,比如 /ab|cd/ 指的是匹配 ab 或者 cd ,而不是指匹配b或者c 。如果想修改这个行为,可以使用圆括号。

1. /a( |\t)b/.test('a\tb') // true

上面代码指的是, a 和 b 之间有一个空格或者一个制表符。 其他的元字符还包括 \ 、 \* 、 + 、 ? 、 () 、 [] 、 {} 等。

转义符:

正则表达式中那些有特殊含义的元字符,如果要匹配它们本身,就需要在它们前面要加上反斜杠。比如 要匹配 + ,就要写成 \+ 。

1. /1+1/.test('1+1')
2. // false
3.
4. /1\+1/.test('1+1')
5. // true

上面代码中,第一个正则表达式之所以不匹配,因为加号是元字符,不代表自身。第二个正则表达式使 用反斜杠对加号转义,就能匹配成功。 正则表达式中,需要反斜杠转义的,一共有12个字 符: ^ 、 . 、 [ 、 $ 、 ( 、 ) 、 | 、 * 、 + 、 ? 、 { 和 \ 。需要特别 注意的是,如果使用 RegExp 方法生成正则对象,转义需要使用两个斜杠,因为字符串内部会先转义 一次。

1. (new RegExp('1\+1')).test('1+1')
2. // false
3.
4. (new RegExp('1\\+1')).test('1+1')
5. // 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 之中任选一个匹配。

1. /[abc]/.test('hello world') // false
2. /[abc]/.test('apple') // true

上面代码中,字符串 hello world 不包含 a 、 b 、 c 这三个字母中的任一个,所以返 回 false ;字符串 apple 包含字母 a ,所以返回 true 。 有两个字符在字符类中有特殊含义。

(1)脱字符(^)

如果方括号内的第一个字符是 [^] ,则表示除了字符类之中的字符,其他字符都可以匹配。比 如, [^xyz] 表示除了 x 、 y 、 z 之外都可以匹配。

1. /[^abc]/.test('bbc news') // true
2. /[^abc]/.test('bbc') // false

上面代码中,字符串 bbc news 包含 a 、 b 、 c 以外的其他字符,所以返回 true ;字符 串 bbc 不包含 a 、 b 、 c 以外的其他字符,所以返回 false 。 如果方括号内没有其他字符,即只有 [^] ,就表示匹配一切字符,其中包括换行符。相比之下,点号 作为元字符( . )是不包括换行符的。

1. var s = 'Please yes\nmake my day!';
2.
3. s.match(/yes.*day/) // null
4. s.match(/yes[^]*day/) // [ 'yes\nmake my day']

上面代码中,字符串 s 含有一个换行符,点号不包括换行符,所以第一个正则表达式匹配失败;第二 个正则表达式 [^] 包含一切字符,所以匹配成功。

(2)连字符(-)

某些情况下,对于连续序列的字符,连字符( - )用来提供简写形式,表示字符的连续范围。比 如, [abc] 可以写成 [a-c] , [0123456789] 可以写成 [0-9] ,同理 [A-Z] 表示26个大 写字母。

1. /a-z/.test('b') // false
2. /[a-z]/.test('b') // true

上面代码中,当连字号(dash)不出现在方括号之中,就不具备简写的作用,只代表字面的含义,所以 不匹配字符 b 。只有当连字号用在方括号之中,才表示连续的字符序列。 以下都是合法的字符类简写形式。

1. [0-9.,]
2. [0-9a-fA-F]
3. [a-zA-Z0-9-]
4. [1-31]

上面代码中最后一个字符类 [1-31] ,不代表 1 到 31 ,只代表 1 到 3 。 连字符还可以用来指定 Unicode 字符的范围。

1. var str = "\u0130\u0131\u0132";
2. /[\u0128-\uFFFF]/.test(str)
3. // true

上面代码中, \u0128-\uFFFF 表示匹配码点在 0128 到 FFFF 之间的所有字符。 另外,不要过分使用连字符,设定一个很大的范围,否则很可能选中意料之外的字符。最典型的例子就 是 [A-z] ,表面上它是选中从大写的 A 到小写的 z 之间52个字母,但是由于在 ASCII 编码之 中,大写字母与小写字母之间还有其他字符,结果就会出现意料之外的结果。

1. /[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 匹配非词边界,即在词的内部。

下面是一些例子。

1. // \s 的例子
2. /\s\w*/.exec('hello world') // [" world"]
3.
4. // \b 的例子
5. /\bworld/.test('hello world') // true
6. /\bworld/.test('hello-world') // true
7. /\bworld/.test('helloworld') // false
8.
9. // \B 的例子
10. /\Bworld/.test('hello-world') // false
11. /\Bworld/.test('helloworld') // true

上面代码中, \s 表示空格,所以匹配结果会包括空格。 \b 表示词的边界,所以 world 的词首 必须独立(词尾是否独立未指定),才会匹配。同理, \B 表示非词的边界,只有 world 的词首不 独立,才会匹配。 通常,正则表达式遇到换行符( \n )就会停止匹配。

1. var html = "<b>Hello</b>\n<i>world!</i>";
2.
3. /.*/.exec(html)[0]
4. // "<b>Hello</b>"

上面代码中,字符串 html 包含一个换行符,结果点字符( . )不匹配换行符,导致匹配结果可能 不符合原意。这时使用 \s 字符类,就能包括换行符。

1. var html = "<b>Hello</b>\n<i>world!</i>";
2.
3. /[\S\s]*/.exec(html)[0]
4. // "<b>Hello</b>\n<i>world!</i>"

上面代码中, [\S\s] 指代一切字符。

重复类:

模式的精确匹配次数,使用大括号( {} )表示。 {n} 表示恰好重复 n 次, {n,} 表示至少重 复 n 次, {n,m} 表示重复不少于 n 次,不多于 m 次。

1. /lo{2}k/.test('look') // true
2. /lo{2,5}k/.test('looook') // true

上面代码中,第一个模式指定 o 连续出现2次,第二个模式指定 o 连续出现2次到5次之间。

量词符:

量词符用来设定某个模式出现的次数。

        ? 问号表示某个模式出现0次或1次,等同于 {0, 1} 。

        * 星号表示某个模式出现0次或多次,等同于 {0,} 。

        + 加号表示某个模式出现1次或多次,等同于 {1,} 。

1. // t 出现0次或1次
2. /t?est/.test('test') // true
3. /t?est/.test('est') // true
4.
5. // t 出现1次或多次
6. /t+est/.test('test') // true
7. /t+est/.test('ttest') // true
8. /t+est/.test('est') // false
9.
10. // t 出现0次或多次
11. /t*est/.test('test') // true
12. /t*est/.test('ttest') // true
13. /t*est/.test('tttest') // true
14. /t*est/.test('est') // true

贪婪模式:

上一小节的三个量词符,默认情况下都是最大可能匹配,即匹配到下一个字符不满足匹配规则为止。这 被称为贪婪模式。

1. var s = 'aaa';
2. s.match(/a+/) // ["aaa"]

上面代码中,模式是 /a+/ ,表示匹配1个 a 或多个 a ,那么到底会匹配几个 a 呢?因为默认 是贪婪模式,会一直匹配到字符 a 不出现为止,所以匹配结果是3个 a 。 除了贪婪模式,还有非贪婪模式,即最小可能匹配。只要一发现匹配,就返回结果,不要往下检查。如 果想将贪婪模式改为非贪婪模式,可以在量词符后面加一个问号。

1. var s = 'aaa';
2. s.match(/a+?/) // ["a"]

上面例子中,模式结尾添加了一个问号 /a+?/ ,这时就改为非贪婪模式,一旦条件满足,就不再往下 匹配, +? 表示只要发现一个 a ,就不再往下匹配了。 除了非贪婪模式的加号( +? ),还有非贪婪模式的星号( *? )和非贪婪模式的问号( ?? )。

        +? :表示某个模式出现1次或多次,匹配时采用非贪婪模式。

        *? :表示某个模式出现0次或多次,匹配时采用非贪婪模式。

        ?? :表格某个模式出现0次或1次,匹配时采用非贪婪模式。

1. 'abb'.match(/ab*/) // ["abb"]
2. 'abb'.match(/ab*?/) // ["a"]
3.
4. 'abb'.match(/ab?/) // ["ab"]
5. 'abb'.match(/ab??/) // ["a"]

上面例子中, /ab*/ 表示如果 a 后面有多个 b ,那么匹配尽可能多的 b ; /ab*?/ 表示匹 配尽可能少的 b ,也就是0个 b 。

修饰符:

修饰符(modifier)表示模式的附加规则,放在正则模式的最尾部。 修饰符可以单个使用,也可以多个一起使用。

1. // 单个修饰符
2. var regex = /test/i;
3.
4. // 多个修饰符
5. var regex = /test/ig;

(1)g 修饰符

默认情况下,第一次匹配成功后,正则对象就停止向下匹配了。 g 修饰符表示全局匹配(global),加上它以后,正则对象将匹配全部符合条件的结果,主要用于搜索和替换。

1. var regex = /b/;
2. var str = 'abba';
3.
4. regex.test(str); // true
5. regex.test(str); // true
6. regex.test(str); // true

上面代码中,正则模式不含 g 修饰符,每次都是从字符串头部开始匹配。所以,连续做了三次匹配, 都返回 true 。

1. var regex = /b/g;
2. var str = 'abba';
3.
4. regex.test(str); // true
5. regex.test(str); // true
6. regex.test(str); // false

上面代码中,正则模式含有 g 修饰符,每次都是从上一次匹配成功处,开始向后匹配。因为字符 串 abba 只有两个 b ,所以前两次匹配结果为 true ,第三次匹配结果为 false 。

(2)i 修饰符

默认情况下,正则对象区分字母的大小写,加上 i 修饰符以后表示忽略大小写(ignoreCase)。

1. /abc/.test('ABC') // false
2. /abc/i.test('ABC') // true

上面代码表示,加了 i 修饰符以后,不考虑大小写,所以模式 abc 匹配字符串 ABC 。

(3)m 修饰符

m 修饰符表示多行模式(multiline),会修改 ^ 和 $ 的行为。默认情况下(即不加 m 修 饰符时), ^ 和 $ 匹配字符串的开始处和结尾处,加上 m 修饰符以后, ^ 和 $ 还会匹配行 首和行尾,即 ^ 和 $ 会识别换行符( \n )。

1. /world$/.test('hello world\n') // false
2. /world$/m.test('hello world\n') // true

上面的代码中,字符串结尾处有一个换行符。如果不加 m 修饰符,匹配不成功,因为字符串的结尾不 是 world ;加上以后, $ 可以匹配行尾。

1. /^b/m.test('a\nb') // true

上面代码要求匹配行首的 b ,如果不加 m 修饰符,就相当于 b 只能处在字符串的开始处。加 上 m 修饰符以后,换行符 \n 也会被认为是一行的开始。

组匹配:

(1)概述

正则表达式的括号表示分组匹配,括号中的模式可以用来匹配分组的内容。

1. /fred+/.test('fredd') // true
2. /(fred)+/.test('fredfred') // true

上面代码中,第一个模式没有括号,结果 + 只表示重复字母 d ,第二个模式有括号,结果 + 就 表示匹配 fred 这个词。 下面是另外一个分组捕获的例子。

1. var m = 'abcabc'.match(/(.)b(.)/);
2. m
3. // ['abc', 'a', 'c']

上面代码中,正则表达式 /(.)b(.)/ 一共使用两个括号,第一个括号捕获 a ,第二个括号捕 获 c 。 注意,使用组匹配时,不宜同时使用 g 修饰符,否则 match 方法不会捕获分组的内容。

1. var m = 'abcabc'.match(/(.)b(.)/g);
2. m // ['abc', 'abc']

上面代码使用带 g 修饰符的正则表达式,结果 match 方法只捕获了匹配整个表达式的部分。这时 必须使用正则表达式的 exec 方法,配合循环,才能读到每一轮匹配的组捕获。

1. var str = 'abcabc';
2. var reg = /(.)b(.)/g;
3. while (true) {
4. var result = reg.exec(str);
5. if (!result) break;
6. console.log(result);
7. }
8. // ["abc", "a", "c"]
9. // ["abc", "a", "c"]

正则表达式内部,还可以用 \n 引用括号匹配的内容, n 是从1开始的自然数,表示对应顺序的括 号。

1. /(.)b(.)\1b\2/.test("abcabc")
2. // true

上面的代码中, \1 表示第一个括号匹配的内容(即 a ), \2 表示第二个括号匹配的内容 (即 c )。 下面是另外一个例子。

1. /y(..)(.)\2\1/.test('yabccab') // true

括号还可以嵌套。

1. /y((..)\2)\1/.test('yabababab') // true

上面代码中, \1 指向外层括号, \2 指向内层括号。 组匹配非常有用,下面是一个匹配网页标签的例子。

1. var tagName = /<([^>]+)>[^<]*<\/\1>/;
2.
3. tagName.exec("<b>bold</b>")[1]
4. // 'b'

上面代码中,圆括号匹配尖括号之中的标签,而 \1 就表示对应的闭合标签。 上面代码略加修改,就能捕获带有属性的标签。

1. var html = '<b class="hello">Hello</b><i>world</i>';
2. var tag = /<(\w+)([^>]*)>(.*?)<\/\1>/g;
3.
4. var match = tag.exec(html);
5.
6. match[1] // "b"
7. match[2] // " class="hello""
8. match[3] // "Hello"
9.
10. match = tag.exec(html);
11.
12. match[1] // "i"
13. match[2] // ""
14. match[3] // "world"

(2)非捕获组

(?:x) 称为非捕获组(Non-capturing group),表示不返回该组匹配的内容,即匹配的结果中 不计入这个括号。 非捕获组的作用请考虑这样一个场景,假定需要匹配 foo 或者 foofoo ,正则表达式就应该写 成 /(foo){1, 2}/ ,但是这样会占用一个组匹配。这时,就可以使用非捕获组,将正则表达式改 为 /(?:foo){1, 2}/ ,它的作用与前一个正则是一样的,但是不会单独输出括号内部的内容。

请看下面的例子。

1. var m = 'abc'.match(/(?:.)b(.)/);
2. m // ["abc", "c"]

上面代码中的模式,一共使用了两个括号。其中第一个括号是非捕获组,所以最后返回的结果中没有第 一个括号,只有第二个括号匹配的内容。 下面是用来分解网址的正则表达式。

1. // 正常匹配
2. var url = /(http|ftp):\/\/([^/\r\n]+)(\/[^\r\n]*)?/;
3.
4. url.exec('http://google.com/');
5. // ["http://google.com/", "http", "google.com", "/"]
6.
7. // 非捕获组匹配
8. var url = /(?:http|ftp):\/\/([^/\r\n]+)(\/[^\r\n]*)?/;
9.
10. url.exec('http://google.com/');
11. // ["http://google.com/", "google.com", "/"]

上面的代码中,前一个正则表达式是正常匹配,第一个括号返回网络协议;后一个正则表达式是非捕获 匹配,返回结果中不包括网络协议。

(3)先行断言

x(?=y) 称为先行断言(Positive look-ahead), x 只有在 y 前面才匹配, y 不会被计 入返回结果。比如,要匹配后面跟着百分号的数字,可以写成 /\d+(?=%)/ 。“先行断言”中,括号里的部分是不会返回的。

1. var m = 'abc'.match(/b(?=c)/);
2. m // ["b"]

上面的代码使用了先行断言, b 在 c 前面所以被匹配,但是括号对应的 c 不会被返回。

(4)先行否定断言

x(?!y) 称为先行否定断言(Negative look-ahead), x 只有不在 y 前面才匹配, y 不 会被计入返回结果。比如,要匹配后面跟的不是百分号的数字,就要写成 /\d+(?!%)/ 。

1. /\d+(?!\.)/.exec('3.14')
2. // ["14"]

上面代码中,正则表达式指定,只有不在小数点前面的数字才会被匹配,因此返回的结果就是 14 。 “先行否定断言”中,括号里的部分是不会返回的。

1. var m = 'abd'.match(/b(?!c)/);
2. m // ['b']

上面的代码使用了先行否定断言, b 不在 c 前面所以被匹配,而且括号对应的 d 不会被返回。

11,JSON 对象

JSON 格式:

JSON 格式(JavaScript Object Notation 的缩写)是一种用于数据交换的文本格式,2001年 由 Douglas Crockford 提出,目的是取代繁琐笨重的 XML 格式。 相比 XML 格式,JSON 格式有两个显著的优点:书写简单,一目了然;符合 JavaScript 原生语 法,可以由解释引擎直接处理,不用另外添加解析代码。所以,JSON 迅速被接受,已经成为各大网站 交换数据的标准格式,并被写入标准。 每个 JSON 对象就是一个值,可能是一个数组或对象,也可能是一个原始类型的值。只能是一 个值,不能是两个或更多的值。 JSON 对值的类型和格式有严格的规定。

以下都是合法的 JSON。

1. ["one", "two", "three"]
2.
3. { "one": 1, "two": 2, "three": 3 }
4.
5. {"names": ["张三", "李四"] }
6.
7. [ { "name": "张三"}, {"name": "李四"} ]

以下都是不合法的 JSON。

1. { name: "张三", 'age': 32 } // 属性名必须使用双引号
2.
3. [32, 64, 128, 0xFFF] // 不能使用十六进制值
4.
5. { "name": "张三", "age": undefined } // 不能使用 undefined
6.
7. { "name": "张三",
8. "birthday": new Date('Fri, 26 Aug 2011 07:13:10 GMT'),
9. "getName": function () {
10. return this.name;
11. }
12. } // 属性值不能使用函数和日期对象

注意, null 、空数组和空对象都是合法的 JSON 值。

JSON 对象:

JSON 对象是 JavaScript 的原生对象,用来处理 JSON 格式数据。它有两个静态方 法: JSON.stringify() 和 JSON.parse() 。

JSON.stringify():

基本用法:JSON.stringify 方法用于将一个值转为 JSON 字符串。该字符串符合 JSON 格式,并且可以 被 JSON.parse 方法还原。

1. JSON.stringify('abc') // ""abc""
2. JSON.stringify(1) // "1"
3. JSON.stringify(false) // "false"
4. JSON.stringify([]) // "[]"
5. JSON.stringify({}) // "{}"
6.
7. JSON.stringify([1, "false", false])
8. // '[1,"false",false]'
9.
10. JSON.stringify({ name: "张三" })
11. // '{"name":"张三"}'

上面代码将各种类型的值,转成 JSON 字符串。 注意,对于原始类型的字符串,转换结果会带双引号。

1. JSON.stringify('foo') === "foo" // false
2. JSON.stringify('foo') === "\"foo\"" // true

上面代码中,字符串 foo ,被转成了 "\"foo\"" 。这是因为将来还原的时候,内层双引号可以让 JavaScript 引擎知道,这是一个字符串,而不是其他类型的值。

1. JSON.stringify(false) // "false"
2. JSON.stringify('false') // "\"false\""

上面代码中,如果不是内层的双引号,将来还原的时候,引擎就无法知道原始值是布尔值还是字符串。 如果对象的属性是 undefined 、函数或 XML 对象,该属性会被 JSON.stringify 过滤。

1. var obj = {
2.     a: undefined,
3.     b: function () {}
4. };
5.
6. JSON.stringify(obj) // "{}"

上面代码中,对象 obj 的 a 属性是 undefined ,而 b 属性是一个函数,结果都 被 JSON.stringify 过滤。 如果数组的成员是 undefined 、函数或 XML 对象,则这些值被转成 null 。

1. var arr = [undefined, function () {}];
2. JSON.stringify(arr) // "[null,null]"

上面代码中,数组 arr 的成员是 undefined 和函数,它们都被转成了 null 。 正则对象会被转成空对象。

1. JSON.stringify(/foo/) // "{}"

JSON.stringify 方法会忽略对象的不可遍历的属性。

1. var obj = {};
2. Object.defineProperties(obj, {
3.     'foo': {
4.         value: 1,
5.         enumerable: true
6.     },
7.     'bar': {
8.         value: 2,
9.         enumerable: false
10.     }
11. });
12.
13. JSON.stringify(obj); // "{"foo":1}"

上面代码中, bar 是 obj 对象的不可遍历属性, JSON.stringify 方法会忽略这个属性。

第二个参数:

JSON.stringify 方法还可以接受一个数组,作为第二个参数,指定需要转成字符串的属性。

1. var obj = {
2.     'prop1': 'value1',
3.     'prop2': 'value2',
4.     'prop3': 'value3'
5. };
6.
7. var selectedProperties = ['prop1', 'prop2'];
8.
9. JSON.stringify(obj, selectedProperties)
10. // "{"prop1":"value1","prop2":"value2"}"

上面代码中,JSON.stringify 方法的第二个参数指定,只转 prop1 和 prop2 两个属性。 这个类似白名单的数组,只对对象的属性有效,对数组无效。

1. JSON.stringify(['a', 'b'], ['0'])
2. // "["a","b"]"
3.
4. JSON.stringify({0: 'a', 1: 'b'}, ['0'])
5. // "{"0":"a"}"

上面代码中,第二个参数指定 JSON 格式只转 0 号属性,实际上对数组是无效的,只对对象有效。 第二个参数还可以是一个函数,用来更改 JSON.stringify 的返回值。

1. function f(key, value) {
2.     if (typeof value === "number") {
3.         value = 2 * value;
4.     }
5.     return value;
6. }
7.
8. JSON.stringify({ a: 1, b: 2 }, f)
9. // '{"a": 2,"b": 4}'

上面代码中的 f 函数,接受两个参数,分别是被转换的对象的键名和键值。如果键值是数值,就将它 乘以 2 ,否则就原样返回。 注意,这个处理函数是递归处理所有的键。

1. var o = {a: {b: 1}};
2.
3. function f(key, value) {
4.     console.log("["+ key +"]:" + value);
5.     return value;
6. }
7.
8. JSON.stringify(o, f)
9. // []:[object Object]
10. // [a]:[object Object]
11. // [b]:1
12. // '{"a":{"b":1}}'

上面代码中,对象 o 一共会被 f 函数处理三次,最后那行是 JSON.stringify 的输出。第一次 键名为空,键值是整个对象 o ;第二次键名为 a ,键值是 {b: 1} ;第三次键名为 b ,键值 为1。 递归处理中,每一次处理的对象,都是前一次返回的值。

1. var o = {a: 1};
2.
3. function f(key, value) {
4.     if (typeof value === 'object') {
5.         return {b: 2};
6.     }
7.     return value * 2;
8. }
9.
10. JSON.stringify(o, f)
11. // "{"b": 4}"

上面代码中, f 函数修改了对象 o ,接着 JSON.stringify 方法就递归处理修改后的对 象 o 。 如果处理函数返回 undefined 或没有返回值,则该属性会被忽略。

1. function f(key, value) {
2.     if (typeof(value) === "string") {
3.         return undefined;
4.     }
5.     return value;
6. }
7.
8. JSON.stringify({ a: "abc", b: 123 }, f)
9. // '{"b": 123}'

上面代码中, a 属性经过处理后,返回 undefined ,于是该属性被忽略了。

第三个参数:

JSON.stringify 还可以接受第三个参数,用于增加返回的 JSON 字符串的可读性。如果是数字, 表示每个属性前面添加的空格(最多不超过10个);如果是字符串(不超过10个字符),则该字符串会 添加在每行前面。

1. JSON.stringify({ p1: 1, p2: 2 }, null, 2);
2. /*
3. "{
4. "p1": 1,
5. "p2": 2
6. }"
7. */
8.
9. JSON.stringify({ p1:1, p2:2 }, null, '|-');
10. /*
11. "{
12. |-"p1": 1,
13. |-"p2": 2
14. }"
15. */

参数对象的 toJSON 方法:

如果参数对象有自定义的 toJSON 方法,那么 JSON.stringify 会使用这个方法的返回值作为参 数,而忽略原对象的其他属性。 下面是一个普通的对象。

1. var user = {
2.     firstName: '三',
3.     lastName: '张',
4.
5.     get fullName(){
6.         return this.lastName + this.firstName;
7.     }
8. };
9.
10. JSON.stringify(user)
11. // "{"firstName":"三","lastName":"张","fullName":"张三"}"

现在,为这个对象加上 toJSON 方法。

1. var user = {
2.     firstName: '三',
3.     lastName: '张',
4.
5.     get fullName(){
6.         return this.lastName + this.firstName;
7.     },
8.
9.     toJSON: function () {
10.         return {
11.             name: this.lastName + this.firstName
12.         };
13.     }
14. };
15.
16. JSON.stringify(user)
17. // "{"name":"张三"}"

上面代码中, JSON.stringify 发现参数对象有 toJSON 方法,就直接使用这个方法的返回值作为 参数,而忽略原对象的其他参数。

Date 对象就有一个自己的 toJSON 方法。

1. var date = new Date('2015-01-01');
2. date.toJSON() // "2015-01-01T00:00:00.000Z"
3. JSON.stringify(date) // ""2015-01-01T00:00:00.000Z""

上面代码中, JSON.stringify 发现处理的是 Date 对象实例,就会调用这个实例对象 的 toJSON 方法,将该方法的返回值作为参数。 toJSON 方法的一个应用是,将正则对象自动转为字符串。因为 JSON.stringify 默认不能转换正 则对象,但是设置了 toJSON 方法以后,就可以转换正则对象了。

1. var obj = {
2.     reg: /foo/
3. };
4.
5. // 不设置 toJSON 方法时
6. JSON.stringify(obj) // "{"reg":{}}"
7.
8. // 设置 toJSON 方法时
9. RegExp.prototype.toJSON = RegExp.prototype.toString;
10. JSON.stringify(/foo/) // ""/foo/""

上面代码在正则对象的原型上面部署了 toJSON() 方法,将其指向 toString() 方法,因此转换成 JSON 格式时,正则对象就先调用 toJSON() 方法转为字符串,然后再被 JSON.stringify() 方法 处理。

JSON.parse()

JSON.parse 方法用于将 JSON 字符串转换成对应的值。

1. JSON.parse('{}') // {}
2. JSON.parse('true') // true
3. JSON.parse('"foo"') // "foo"
4. JSON.parse('[1, 5, "false"]') // [1, 5, "false"]
5. JSON.parse('null') // null
6.
7. var o = JSON.parse('{"name": "张三"}');
8. o.name // 张三

如果传入的字符串不是有效的 JSON 格式, JSON.parse 方法将报错。

1. JSON.parse("'String'") // illegal single quotes
2. // SyntaxError: Unexpected token ILLEGAL

上面代码中,双引号字符串中是一个单引号字符串,因为单引号字符串不符合 JSON 格式,所以报 错。 为了处理解析错误,可以将 JSON.parse 方法放在 try...catch 代码块中。

1. try {
2.     JSON.parse("'String'");
3. } catch(e) {
4.     console.log('parsing error');
5. }

JSON.parse 方法可以接受一个处理函数,作为第二个参数,用法与 JSON.stringify 方法类似。

1. function f(key, value) {
2.     if (key === 'a') {
3.         return value + 10;
4.     }
5.     return value;
6. }
7.
8. JSON.parse('{"a": 1, "b": 2}', f)
9. // {a: 11, b: 2}

上面代码中, JSON.parse 的第二个参数是一个函数,如果键名是 a ,该函数会将键值加上10。

06-29 08:00