• JavaScript-修炼之路第五层


    五、标准库

    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. 1. Object.prototype.print = function () {
    2. 2. console.log(this);
    3. 3. };
    4. 4.
    5. 5. var obj = new Object();
    6. 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. 1. var obj = Object();
    2. 2. // 等同于
    3. 3. var obj = Object(undefined);
    4. 4. var obj = Object(null);
    5. 5.
    6. 6. obj instanceof Object // true

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

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

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

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

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

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

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

    1. var obj = new Object();

    注意,通过 var obj = new Object() 的写法生成新对象,与字面量的写法 var obj = {} 是等价 的。或者说,后者只是前者的一种简便写法。

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

    1. 1. var o1 = {a: 1};
    2. 2. var o2 = new Object(o1);
    3. 3. o1 === o2 // true
    4. 4.
    5. 5. var obj = new Object(123);
    6. 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. 1. var obj = {
    2. 2. p1: 123,
    3. 3. p2: 456
    4. 4. };
    5. 5.
    6. 6. Object.keys(obj) // ["p1", "p2"]

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

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

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

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

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

    1. 1. var obj = {
    2. 2. p1: 123,
    3. 3. p2: 456
    4. 4. };
    5. 5.
    6. 6. Object.keys(obj).length // 2
    7. 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. 1. var obj = new Object();
    2. 2. obj.valueOf() === obj // true

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

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

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

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

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

    Object.prototype.toString()

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

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

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

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

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

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

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

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

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

    1. 1. var obj = {};
    2. 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. 1. Object.prototype.toString.call(2) // "[object Number]"
    2. 2. Object.prototype.toString.call('') // "[object String]"
    3. 3. Object.prototype.toString.call(true) // "[object Boolean]"
    4. 4. Object.prototype.toString.call(undefined) // "[object Undefined]"
    5. 5. Object.prototype.toString.call(null) // "[object Null]"
    6. 6. Object.prototype.toString.call(Math) // "[object Math]"
    7. 7. Object.prototype.toString.call({}) // "[object Object]"
    8. 8. Object.prototype.toString.call([]) // "[object Array]"

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

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

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

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

    Object.prototype.toLocaleString()

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

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

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

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

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

            Array.prototype.toLocaleString()

            Number.prototype.toLocaleString()

            Date.prototype.toLocaleString()

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

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

    Object.prototype.hasOwnProperty()

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

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

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

    2,属性描述对象

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

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

    1. 1. {
    2. 2. value: 123,
    3. 3. writable: false,
    4. 4. enumerable: true,
    5. 5. configurable: false,
    6. 6. get: undefined,
    7. 7. set: undefined
    8. 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. 1. var obj = { p: 'a' };
    2. 2.
    3. 3. Object.getOwnPropertyDescriptor(obj, 'p')
    4. 4. // Object { value: "a",
    5. 5. // writable: true,
    6. 6. // enumerable: true,
    7. 7. // configurable: true
    8. 8. // }

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

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

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

    Object.getOwnPropertyNames()

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

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

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

    1. 1. Object.keys([]) // []
    2. 2. Object.getOwnPropertyNames([]) // [ 'length' ]
    3. 3.
    4. 4. Object.keys(Object.prototype) // []
    5. 5. Object.getOwnPropertyNames(Object.prototype)
    6. 6. // ['hasOwnProperty',
    7. 7. // 'valueOf',
    8. 8. // 'constructor',
    9. 9. // 'toLocaleString',
    10. 10. // 'isPrototypeOf',
    11. 11. // 'propertyIsEnumerable',
    12. 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. 1. var obj = Object.defineProperty({}, 'p', {
    2. 2. value: 123,
    3. 3. writable: false,
    4. 4. enumerable: true,
    5. 5. configurable: false
    6. 6. });
    7. 7.
    8. 8. obj.p // 123
    9. 9.
    10. 10. obj.p = 246;
    11. 11. obj.p // 123

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

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

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

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

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

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

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

    Object.prototype.propertyIsEnumerable()

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

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

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

    元属性

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

    value:性是目标属性的值。

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

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

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

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

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

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

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

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

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

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

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

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

    1. 1. var obj = {};
    2. 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. 1. var obj = {};
    2. 2.
    3. 3. Object.defineProperty(obj, 'x', {
    4. 4. value: 123,
    5. 5. enumerable: false
    6. 6. });
    7. 7.
    8. 8. obj.x // 123
    9. 9.
    10. 10. for (var key in obj) {
    11. 11. console.log(key);
    12. 12. }
    13. 13. // undefined
    14. 14.
    15. 15. Object.keys(obj) // []
    16. 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. 1. var obj = Object.defineProperty({}, 'p', {
    2. 2. value: 1,
    3. 3. writable: false,
    4. 4. enumerable: false,
    5. 5. configurable: false
    6. 6. });
    7. 7.
    8. 8. Object.defineProperty(obj, 'p', {value: 2})
    9. 9. // TypeError: Cannot redefine property: p
    10. 10.
    11. 11. Object.defineProperty(obj, 'p', {writable: true})
    12. 12. // TypeError: Cannot redefine property: p
    13. 13.
    14. 14. Object.defineProperty(obj, 'p', {enumerable: true})
    15. 15. // TypeError: Cannot redefine property: p
    16. 16.
    17. 17. Object.defineProperty(obj, 'p', {configurable: true})
    18. 18. // TypeError: Cannot redefine property: p

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    Object.preventExtensions()

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

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

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

    Object.isExtensible()

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

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

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

    Object.seal()

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

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

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

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

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

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

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

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

    Object.isSealed()

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

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

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

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

    Object.freeze()

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

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

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

    Object.isFrozen()

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    3,Array 对象

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

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

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

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

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

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

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

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

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

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

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

    静态方法:

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

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

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

    实例方法:

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

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

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

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

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

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

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

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

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

    1. [].pop() // undefined

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    1. arr.slice(start, end);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    1. 1. [1, 2, 3, 4, 5].reduce(function (a, b) {
    2. 2. console.log(a, b);
    3. 3. return a + b;
    4. 4. })
    5. 5. // 1 2
    6. 6. // 3 3
    7. 7. // 6 4
    8. 8. // 10 5
    9. 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. [1, 2, 3, 4, 5].reduce(function (a, b) {
    2. 2. return a + b;
    3. 3. }, 10);
    4. 4. // 25

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    链式使用

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

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

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

    4,包装对象

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

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

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

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

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

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

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

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

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

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

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

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

    1. 'abc'.length // 3

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

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

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

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

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

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

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

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

    5,Boolean 对象

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

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

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

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

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

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

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

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

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

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

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

    6,Number 对象

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

    1. 1. var n = new Number(1);
    2. 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. 1. Number.POSITIVE_INFINITY // Infinity
    2. 2. Number.NEGATIVE_INFINITY // -Infinity
    3. 3. Number.NaN // NaN
    4. 4.
    5. 5. Number.MAX_VALUE
    6. 6. // 1.7976931348623157e+308
    7. 7. Number.MAX_VALUE < Infinity
    8. 8. // true
    9. 9.
    10. 10. Number.MIN_VALUE
    11. 11. // 5e-324
    12. 12. Number.MIN_VALUE > 0
    13. 13. // true
    14. 14.
    15. 15. Number.MAX_SAFE_INTEGER // 9007199254740991
    16. 16. Number.MIN_SAFE_INTEGER // -9007199254740991

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

    Number.prototype.toString():

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

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

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

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

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

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

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

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

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

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

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

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

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

    Number.prototype.toFixed():

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

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

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

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

    Number.prototype.toExponential():

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

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

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

    Number.prototype.toPrecision()

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

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

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

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

    Number.prototype.toLocaleString():

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    7,String 对象

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

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

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

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

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

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

    静态方法String.fromCharCode():

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

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

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

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

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

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

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

    实例属性String.prototype.length:

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

    1. 1. 'abc'.length // 3

    实例方法String.prototype.charAt()

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

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

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

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

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

    1. 1. 'abc'.charAt(-1) // ""
    2. 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. 1. 'abc'.charCodeAt(-1) // NaN
    2. 2. 'abc'.charCodeAt(4) // NaN

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

    String.prototype.concat()

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

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

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

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

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

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

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

    String.prototype.slice()

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

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

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

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

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

    1. 1. 'JavaScript'.slice(-6) // "Script"
    2. 2. 'JavaScript'.slice(0, -6) // "Java"
    3. 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. 1. 'JavaScript'.substring(10, 4) // "Script"
    2. 2. // 等同于
    3. 3. 'JavaScript'.substring(4, 10) // "Script"

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

    1. 1. 'JavaScript'.substring(-3) // "JavaScript"
    2. 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. 1. 'JavaScript'.substr(-6) // "Script"
    2. 2. 'JavaScript'.substr(4, -1) // ""

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

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

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

    1. 1. 'hello world'.indexOf('o') // 4
    2. 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. 1. ' hello world '.trim()
    2. 2. // "hello world"

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

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

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

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

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

    String.prototype.match():

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

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

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

    1. 1. var matches = 'cat, bat, sat, fat'.match('at');
    2. 2. matches.index // 1
    3. 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. 1. '|b|c'.split('|') // ["", "b", "c"]
    2. 2. 'a|b|'.split('|') // ["a", "b", ""]

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

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

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

    String.prototype.localeCompare():

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

    1. 1. 'apple'.localeCompare('banana') // -1
    2. 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. 1. 'ä'.localeCompare('z', 'de') // -1
    2. 2. 'ä'.localeCompare('z', 'sv') // 1

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

    8,Math 对象

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

    静态属性:

    Math 对象的静态属性,提供以下一些数学常数。

    Math.E :常数 e 。

    Math.LN2 :2 的自然对数。

    Math.LN10 :10 的自然对数。

    Math.LOG2E :以 2 为底的 e 的对数。

    Math.LOG10E :以 10 为底的 e 的对数。

    Math.PI :常数 π 。

    Math.SQRT1_2 :0.5 的平方根。

    Math.SQRT2 :2 的平方根。

    1. Math.E // 2.718281828459045

    2. Math.LN2 // 0.6931471805599453

    3. Math.LN10 // 2.302585092994046

    4. Math.LOG2E // 1.4426950408889634

    5. Math.LOG10E // 0.4342944819032518

    6. Math.PI // 3.141592653589793

    7. Math.SQRT1_2 // 0.7071067811865476

    8. Math.SQRT2 // 1.4142135623730951

    这些属性都是只读的,不能修改。

    静态方法:

            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. 1. Math.abs(1) // 1
    2. 2. Math.abs(-1) // 1

    Math.max(),Math.min()

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

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

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

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

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

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

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

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

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

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

    Math.round():

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

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

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

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

    Math.pow():

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

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

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

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

    Math.sqrt():

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

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

    Math.log():

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

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

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

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

    Math.exp():

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

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

    Math.random():

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

    1. Math.random() // 0.7151307314634323

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

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

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

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

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

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

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

    三角函数方法

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

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

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

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

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

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

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

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

    9,Date 对象

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

    普通函数的用法:

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

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

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

    1. 1. Date(2000, 1, 1)
    2. 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. 1. var today = new Date();
    2. 2.
    3. 3. today
    4. 4. // "Tue Dec 01 2015 09:34:43 GMT+0800 (CST)"
    5. 5.
    6. 6. // 等同于
    7. 7. today.toString()
    8. 8. // "Tue Dec 01 2015 09:34:43 GMT+0800 (CST)"

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

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

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

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

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

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

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

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

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

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

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

    1. 1. new Date(2013, 0)
    2. 2. // Tue Jan 01 2013 00:00:00 GMT+0800 (CST)
    3. 3. new Date(2013, 0, 1)
    4. 4. // Tue Jan 01 2013 00:00:00 GMT+0800 (CST)
    5. 5. new Date(2013, 0, 1, 0)
    6. 6. // Tue Jan 01 2013 00:00:00 GMT+0800 (CST)
    7. 7. new Date(2013, 0, 1, 0, 0, 0, 0)
    8. 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. 1. new Date(2013, 15)
    2. 2. // Tue Apr 01 2014 00:00:00 GMT+0800 (CST)
    3. 3. new Date(2013, 0, 0)
    4. 4. // Mon Dec 31 2012 00:00:00 GMT+0800 (CST)

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

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

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

    日期的运算:

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

    1. 1. var d1 = new Date(2000, 2, 1);
    2. 2. var d2 = new Date(2000, 3, 1);
    3. 3.
    4. 4. d2 - d1
    5. 5. // 2678400000
    6. 6. d2 + d1
    7. 7.
    8. // "Sat Apr 01 2000 00:00:00 GMT+0800 (CST)Wed Mar 01 2000 00:00:00 GMT+0800
    9. (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. 1. Date.parse('Aug 9, 1995')
    2. 2. Date.parse('January 26, 2011 13:51:50')
    3. 3. Date.parse('Mon, 25 Dec 1995 13:30:00 GMT')
    4. 4. Date.parse('Mon, 25 Dec 1995 13:30:00 +0430')
    5. 5. Date.parse('2011-10-10')
    6. 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. 1. // 格式
    2. 2. Date.UTC(year, month[, date[, hrs[, min[, sec[, ms]]]]])
    3. 3.
    4. 4. // 用法
    5. 5. Date.UTC(2011, 0, 1, 2, 3, 4, 567)
    6. 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. 1. var d = new Date();
    2. 2.
    3. 3. d.valueOf() // 1362790014817
    4. 4. d.getTime() // 1362790014817

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

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

    to 类方法:

    (1)Date.prototype.toString()

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

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

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

    (2)Date.prototype.toUTCString()

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

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

    (3)Date.prototype.toISOString()

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

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

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

    (4)Date.prototype.toJSON()

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

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

    (5)Date.prototype.toDateString()

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

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

    (6)Date.prototype.toTimeString()

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

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

    (7)本地时间

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

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

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

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

    下面是用法实例。

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

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

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

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

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

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

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

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

            getUTCDate()

            getUTCFullYear()

            getUTCMonth()

            getUTCDay()

            getUTCHours()

            getUTCMinutes()

            getUTCSeconds()

            getUTCMilliseconds()

    1. 1. var d = new Date('January 6, 2013');
    2. 2.
    3. 3. d.getDate() // 6
    4. 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. 1. var d = new Date ('January 6, 2013');
    2. 2.
    3. 3. d // Sun Jan 06 2013 00:00:00 GMT+0800 (CST)
    4. 4. d.setDate(9) // 1357660800000
    5. 5. d // Wed Jan 09 2013 00:00:00 GMT+0800 (CST)

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

    1. 1. var d1 = new Date('January 6, 2013');
    2. 2.
    3. 3. d1.setDate(32) // 1359648000000
    4. 4. d1 // Fri Feb 01 2013 00:00:00 GMT+0800 (CST)
    5. 5.
    6. 6. var d2 = new Date ('January 6, 2013');
    7. 7.
    8. 8. d2.setDate(-1) // 1356796800000
    9. 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. 1. var d = new Date();
    2. 2.
    3. 3. // 将日期向后推1000天
    4. 4. d.setDate(d.getDate() + 1000);
    5. 5. // 将时间设为6小时后
    6. 6. d.setHours(d.getHours() + 6);
    7. 7. // 将年份设为去年
    8. 8. d.setFullYear(d.getFullYear() - 1);

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

            setUTCDate()

            setUTCFullYear()

            setUTCHours()

            setUTCMilliseconds()

            setUTCMinutes()

            setUTCMonth()

            setUTCSeconds()

    1. 1. var d = new Date('January 6, 2013');
    2. 2. d.getUTCHours() // 16
    3. 3. d.setUTCHours(22) // 1357423200000
    4. 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. 1. var regex = new RegExp('xyz', 'i');
    2. 2. // 等价于
    3. 3. var regex = /xyz/i;

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

    实例属性:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    RegExp.prototype.exec():

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

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

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

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

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

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

            input :整个原字符串。

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

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

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

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

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

    1. 1. var reg = /a/g;
    2. 2. var str = 'abc_abc_abc'
    3. 3.
    4. 4. while(true) {
    5. 5. var match = reg.exec(str);
    6. 6. if (!match) break;
    7. 7. console.log('#' + match.index + ':' + match[0]);
    8. 8. }
    9. 9. // #0:a
    10. 10. // #4:a
    11. 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. 1. var s = '_x_x';
    2. 2. var r1 = /x/;
    3. 3. var r2 = /y/;
    4. 4.
    5. 5. s.match(r1) // ["x"]
    6. 6. s.match(r2) // null

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

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

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

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

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

    String.prototype.search():

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

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

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

    String.prototype.replace():

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

    1. str.replace(search, replacement)

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

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

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

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

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

            $& :匹配的子字符串。

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

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

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

            $$ :指代美元符号 $ 。

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

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

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

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

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

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

    String.prototype.split():

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

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

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

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

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

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

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

    1. 1. 'aaa*a*'.split(/(a*)/)
    2. 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. 1. // test必须出现在开始位置
    2. 2. /^test/.test('test123') // true
    3. 3.
    4. 4. // test必须出现在结束位置
    5. 5. /test$/.test('new test') // true
    6. 6.
    7. 7. // 从开始位置到结束位置只有test
    8. 8. /^test$/.test('test') // true
    9. 9. /^test$/.test('test test') // false

    (3)选择符( | )

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

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

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

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

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

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

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

    转义符:

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

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

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

    1. 1. (new RegExp('1\+1')).test('1+1')
    2. 2. // false
    3. 3.
    4. 4. (new RegExp('1\\+1')).test('1+1')
    5. 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. 1. /[abc]/.test('hello world') // false
    2. 2. /[abc]/.test('apple') // true

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

    (1)脱字符(^)

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

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

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

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

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

    注意,脱字符只有在字符类的第一个位置才有特殊含义,否则就是字面含义。

    (2)连字符(-)

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

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

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

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

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

    1. 1. var str = "\u0130\u0131\u0132";
    2. 2. /[\u0128-\uFFFF]/.test(str)
    3. 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. 1. // \s 的例子
    2. 2. /\s\w*/.exec('hello world') // [" world"]
    3. 3.
    4. 4. // \b 的例子
    5. 5. /\bworld/.test('hello world') // true
    6. 6. /\bworld/.test('hello-world') // true
    7. 7. /\bworld/.test('helloworld') // false
    8. 8.
    9. 9. // \B 的例子
    10. 10. /\Bworld/.test('hello-world') // false
    11. 11. /\Bworld/.test('helloworld') // true

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

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

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

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

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

    重复类:

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

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

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

    量词符:

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

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

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

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

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

    贪婪模式:

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

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

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

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

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

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

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

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

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

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

    修饰符:

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

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

    (1)g 修饰符

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

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

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

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

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

    (2)i 修饰符

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

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

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

    (3)m 修饰符

    m 修饰符表示多行模式(multiline),会修改 ^ 和 $ 的行为。默认情况下(即不加 m 修 饰符时), ^ 和 $ 匹配字符串的开始处和结尾处,加上 m 修饰符以后, ^ 和 $ 还会匹配行 首和行尾,即 ^ 和 $ 会识别换行符( \n )。

    1. 1. /world$/.test('hello world\n') // false
    2. 2. /world$/m.test('hello world\n') // true

    上面的代码中,字符串结尾处有一个换行符。如果不加 m 修饰符,匹配不成功,因为字符串的结尾不 是 world ;加上以后, $ 可以匹配行尾。

    1. /^b/m.test('a\nb') // true

    上面代码要求匹配行首的 b ,如果不加 m 修饰符,就相当于 b 只能处在字符串的开始处。加 上 m 修饰符以后,换行符 \n 也会被认为是一行的开始。

    组匹配:

    (1)概述

    正则表达式的括号表示分组匹配,括号中的模式可以用来匹配分组的内容。

    1. 1. /fred+/.test('fredd') // true
    2. 2. /(fred)+/.test('fredfred') // true

    上面代码中,第一个模式没有括号,结果 + 只表示重复字母 d ,第二个模式有括号,结果 + 就 表示匹配 fred 这个词。 下面是另外一个分组捕获的例子。

    1. 1. var m = 'abcabc'.match(/(.)b(.)/);
    2. 2. m
    3. 3. // ['abc', 'a', 'c']

    上面代码中,正则表达式 /(.)b(.)/ 一共使用两个括号,第一个括号捕获 a ,第二个括号捕 获 c 。 注意,使用组匹配时,不宜同时使用 g 修饰符,否则 match 方法不会捕获分组的内容。

    1. 1. var m = 'abcabc'.match(/(.)b(.)/g);
    2. 2. m // ['abc', 'abc']

    上面代码使用带 g 修饰符的正则表达式,结果 match 方法只捕获了匹配整个表达式的部分。这时 必须使用正则表达式的 exec 方法,配合循环,才能读到每一轮匹配的组捕获。

    1. 1. var str = 'abcabc';
    2. 2. var reg = /(.)b(.)/g;
    3. 3. while (true) {
    4. 4. var result = reg.exec(str);
    5. 5. if (!result) break;
    6. 6. console.log(result);
    7. 7. }
    8. 8. // ["abc", "a", "c"]
    9. 9. // ["abc", "a", "c"]

    正则表达式内部,还可以用 \n 引用括号匹配的内容, n 是从1开始的自然数,表示对应顺序的括 号。

    1. 1. /(.)b(.)\1b\2/.test("abcabc")
    2. 2. // true

    上面的代码中, \1 表示第一个括号匹配的内容(即 a ), \2 表示第二个括号匹配的内容 (即 c )。 下面是另外一个例子。

    1. /y(..)(.)\2\1/.test('yabccab') // true

    括号还可以嵌套。

    1. /y((..)\2)\1/.test('yabababab') // true

    上面代码中, \1 指向外层括号, \2 指向内层括号。 组匹配非常有用,下面是一个匹配网页标签的例子。

    1. 1. var tagName = /<([^>]+)>[^<]*<\/\1>/;
    2. 2.
    3. 3. tagName.exec("<b>bold</b>")[1]
    4. 4. // 'b'

    上面代码中,圆括号匹配尖括号之中的标签,而 \1 就表示对应的闭合标签。 上面代码略加修改,就能捕获带有属性的标签。

    1. 1. var html = '<b class="hello">Hello</b><i>world</i>';
    2. 2. var tag = /<(\w+)([^>]*)>(.*?)<\/\1>/g;
    3. 3.
    4. 4. var match = tag.exec(html);
    5. 5.
    6. 6. match[1] // "b"
    7. 7. match[2] // " class="hello""
    8. 8. match[3] // "Hello"
    9. 9.
    10. 10. match = tag.exec(html);
    11. 11.
    12. 12. match[1] // "i"
    13. 13. match[2] // ""
    14. 14. match[3] // "world"

    (2)非捕获组

    (?:x) 称为非捕获组(Non-capturing group),表示不返回该组匹配的内容,即匹配的结果中 不计入这个括号。 非捕获组的作用请考虑这样一个场景,假定需要匹配 foo 或者 foofoo ,正则表达式就应该写 成 /(foo){1, 2}/ ,但是这样会占用一个组匹配。这时,就可以使用非捕获组,将正则表达式改 为 /(?:foo){1, 2}/ ,它的作用与前一个正则是一样的,但是不会单独输出括号内部的内容。

    请看下面的例子。

    1. 1. var m = 'abc'.match(/(?:.)b(.)/);
    2. 2. m // ["abc", "c"]

    上面代码中的模式,一共使用了两个括号。其中第一个括号是非捕获组,所以最后返回的结果中没有第 一个括号,只有第二个括号匹配的内容。 下面是用来分解网址的正则表达式。

    1. 1. // 正常匹配
    2. 2. var url = /(http|ftp):\/\/([^/\r\n]+)(\/[^\r\n]*)?/;
    3. 3.
    4. 4. url.exec('http://google.com/');
    5. 5. // ["http://google.com/", "http", "google.com", "/"]
    6. 6.
    7. 7. // 非捕获组匹配
    8. 8. var url = /(?:http|ftp):\/\/([^/\r\n]+)(\/[^\r\n]*)?/;
    9. 9.
    10. 10. url.exec('http://google.com/');
    11. 11. // ["http://google.com/", "google.com", "/"]

    上面的代码中,前一个正则表达式是正常匹配,第一个括号返回网络协议;后一个正则表达式是非捕获 匹配,返回结果中不包括网络协议。

    (3)先行断言

    x(?=y) 称为先行断言(Positive look-ahead), x 只有在 y 前面才匹配, y 不会被计 入返回结果。比如,要匹配后面跟着百分号的数字,可以写成 /\d+(?=%)/ 。“先行断言”中,括号里的部分是不会返回的。

    1. 1. var m = 'abc'.match(/b(?=c)/);
    2. 2. m // ["b"]

    上面的代码使用了先行断言, b 在 c 前面所以被匹配,但是括号对应的 c 不会被返回。

    (4)先行否定断言

    x(?!y) 称为先行否定断言(Negative look-ahead), x 只有不在 y 前面才匹配, y 不 会被计入返回结果。比如,要匹配后面跟的不是百分号的数字,就要写成 /\d+(?!%)/ 。

    1. 1. /\d+(?!\.)/.exec('3.14')
    2. 2. // ["14"]

    上面代码中,正则表达式指定,只有不在小数点前面的数字才会被匹配,因此返回的结果就是 14 。 “先行否定断言”中,括号里的部分是不会返回的。

    1. 1. var m = 'abd'.match(/b(?!c)/);
    2. 2. m // ['b']

    上面的代码使用了先行否定断言, b 不在 c 前面所以被匹配,而且括号对应的 d 不会被返回。

    11,JSON 对象

    JSON 格式:

    JSON 格式(JavaScript Object Notation 的缩写)是一种用于数据交换的文本格式,2001年 由 Douglas Crockford 提出,目的是取代繁琐笨重的 XML 格式。 相比 XML 格式,JSON 格式有两个显著的优点:书写简单,一目了然;符合 JavaScript 原生语 法,可以由解释引擎直接处理,不用另外添加解析代码。所以,JSON 迅速被接受,已经成为各大网站 交换数据的标准格式,并被写入标准。 每个 JSON 对象就是一个值,可能是一个数组或对象,也可能是一个原始类型的值。只能是一 个值,不能是两个或更多的值。 JSON 对值的类型和格式有严格的规定。

    1. 复合类型的值只能是数组或对象,不能是函数、正则表达式对象、日期对象。

    2. 原始类型的值只有四种:字符串、数值(必须以十进制表示)、布尔值和 null (不能使用 NaN , Infinity , -Infinity 和 undefined )。

    3. 字符串必须使用双引号表示,不能使用单引号。

    4. 对象的键名必须放在双引号里面。

    5. 数组或对象最后一个成员的后面,不能加逗号。

    以下都是合法的 JSON。

    1. 1. ["one", "two", "three"]
    2. 2.
    3. 3. { "one": 1, "two": 2, "three": 3 }
    4. 4.
    5. 5. {"names": ["张三", "李四"] }
    6. 6.
    7. 7. [ { "name": "张三"}, {"name": "李四"} ]

    以下都是不合法的 JSON。

    1. 1. { name: "张三", 'age': 32 } // 属性名必须使用双引号
    2. 2.
    3. 3. [32, 64, 128, 0xFFF] // 不能使用十六进制值
    4. 4.
    5. 5. { "name": "张三", "age": undefined } // 不能使用 undefined
    6. 6.
    7. 7. { "name": "张三",
    8. 8. "birthday": new Date('Fri, 26 Aug 2011 07:13:10 GMT'),
    9. 9. "getName": function () {
    10. 10. return this.name;
    11. 11. }
    12. 12. } // 属性值不能使用函数和日期对象

    注意, null 、空数组和空对象都是合法的 JSON 值。

    JSON 对象:

    JSON 对象是 JavaScript 的原生对象,用来处理 JSON 格式数据。它有两个静态方 法: JSON.stringify() 和 JSON.parse() 。

    JSON.stringify():

    基本用法:JSON.stringify 方法用于将一个值转为 JSON 字符串。该字符串符合 JSON 格式,并且可以 被 JSON.parse 方法还原。

    1. 1. JSON.stringify('abc') // ""abc""
    2. 2. JSON.stringify(1) // "1"
    3. 3. JSON.stringify(false) // "false"
    4. 4. JSON.stringify([]) // "[]"
    5. 5. JSON.stringify({}) // "{}"
    6. 6.
    7. 7. JSON.stringify([1, "false", false])
    8. 8. // '[1,"false",false]'
    9. 9.
    10. 10. JSON.stringify({ name: "张三" })
    11. 11. // '{"name":"张三"}'

    上面代码将各种类型的值,转成 JSON 字符串。 注意,对于原始类型的字符串,转换结果会带双引号。

    1. 1. JSON.stringify('foo') === "foo" // false
    2. 2. JSON.stringify('foo') === "\"foo\"" // true

    上面代码中,字符串 foo ,被转成了 "\"foo\"" 。这是因为将来还原的时候,内层双引号可以让 JavaScript 引擎知道,这是一个字符串,而不是其他类型的值。

    1. 1. JSON.stringify(false) // "false"
    2. 2. JSON.stringify('false') // "\"false\""

    上面代码中,如果不是内层的双引号,将来还原的时候,引擎就无法知道原始值是布尔值还是字符串。 如果对象的属性是 undefined 、函数或 XML 对象,该属性会被 JSON.stringify 过滤。

    1. 1. var obj = {
    2. 2. a: undefined,
    3. 3. b: function () {}
    4. 4. };
    5. 5.
    6. 6. JSON.stringify(obj) // "{}"

    上面代码中,对象 obj 的 a 属性是 undefined ,而 b 属性是一个函数,结果都 被 JSON.stringify 过滤。 如果数组的成员是 undefined 、函数或 XML 对象,则这些值被转成 null 。

    1. 1. var arr = [undefined, function () {}];
    2. 2. JSON.stringify(arr) // "[null,null]"

    上面代码中,数组 arr 的成员是 undefined 和函数,它们都被转成了 null 。 正则对象会被转成空对象。

    1. JSON.stringify(/foo/) // "{}"
    

    JSON.stringify 方法会忽略对象的不可遍历的属性。

    1. 1. var obj = {};
    2. 2. Object.defineProperties(obj, {
    3. 3. 'foo': {
    4. 4. value: 1,
    5. 5. enumerable: true
    6. 6. },
    7. 7. 'bar': {
    8. 8. value: 2,
    9. 9. enumerable: false
    10. 10. }
    11. 11. });
    12. 12.
    13. 13. JSON.stringify(obj); // "{"foo":1}"

    上面代码中, bar 是 obj 对象的不可遍历属性, JSON.stringify 方法会忽略这个属性。

    第二个参数:

    JSON.stringify 方法还可以接受一个数组,作为第二个参数,指定需要转成字符串的属性。

    1. 1. var obj = {
    2. 2. 'prop1': 'value1',
    3. 3. 'prop2': 'value2',
    4. 4. 'prop3': 'value3'
    5. 5. };
    6. 6.
    7. 7. var selectedProperties = ['prop1', 'prop2'];
    8. 8.
    9. 9. JSON.stringify(obj, selectedProperties)
    10. 10. // "{"prop1":"value1","prop2":"value2"}"

    上面代码中,JSON.stringify 方法的第二个参数指定,只转 prop1 和 prop2 两个属性。 这个类似白名单的数组,只对对象的属性有效,对数组无效。

    1. 1. JSON.stringify(['a', 'b'], ['0'])
    2. 2. // "["a","b"]"
    3. 3.
    4. 4. JSON.stringify({0: 'a', 1: 'b'}, ['0'])
    5. 5. // "{"0":"a"}"

    上面代码中,第二个参数指定 JSON 格式只转 0 号属性,实际上对数组是无效的,只对对象有效。 第二个参数还可以是一个函数,用来更改 JSON.stringify 的返回值。

    1. 1. function f(key, value) {
    2. 2. if (typeof value === "number") {
    3. 3. value = 2 * value;
    4. 4. }
    5. 5. return value;
    6. 6. }
    7. 7.
    8. 8. JSON.stringify({ a: 1, b: 2 }, f)
    9. 9. // '{"a": 2,"b": 4}'

    上面代码中的 f 函数,接受两个参数,分别是被转换的对象的键名和键值。如果键值是数值,就将它 乘以 2 ,否则就原样返回。 注意,这个处理函数是递归处理所有的键。

    1. 1. var o = {a: {b: 1}};
    2. 2.
    3. 3. function f(key, value) {
    4. 4. console.log("["+ key +"]:" + value);
    5. 5. return value;
    6. 6. }
    7. 7.
    8. 8. JSON.stringify(o, f)
    9. 9. // []:[object Object]
    10. 10. // [a]:[object Object]
    11. 11. // [b]:1
    12. 12. // '{"a":{"b":1}}'

    上面代码中,对象 o 一共会被 f 函数处理三次,最后那行是 JSON.stringify 的输出。第一次 键名为空,键值是整个对象 o ;第二次键名为 a ,键值是 {b: 1} ;第三次键名为 b ,键值 为1。 递归处理中,每一次处理的对象,都是前一次返回的值。

    1. 1. var o = {a: 1};
    2. 2.
    3. 3. function f(key, value) {
    4. 4. if (typeof value === 'object') {
    5. 5. return {b: 2};
    6. 6. }
    7. 7. return value * 2;
    8. 8. }
    9. 9.
    10. 10. JSON.stringify(o, f)
    11. 11. // "{"b": 4}"

    上面代码中, f 函数修改了对象 o ,接着 JSON.stringify 方法就递归处理修改后的对 象 o 。 如果处理函数返回 undefined 或没有返回值,则该属性会被忽略。

    1. 1. function f(key, value) {
    2. 2. if (typeof(value) === "string") {
    3. 3. return undefined;
    4. 4. }
    5. 5. return value;
    6. 6. }
    7. 7.
    8. 8. JSON.stringify({ a: "abc", b: 123 }, f)
    9. 9. // '{"b": 123}'

    上面代码中, a 属性经过处理后,返回 undefined ,于是该属性被忽略了。

    第三个参数:

    JSON.stringify 还可以接受第三个参数,用于增加返回的 JSON 字符串的可读性。如果是数字, 表示每个属性前面添加的空格(最多不超过10个);如果是字符串(不超过10个字符),则该字符串会 添加在每行前面。

    1. 1. JSON.stringify({ p1: 1, p2: 2 }, null, 2);
    2. 2. /*
    3. 3. "{
    4. 4. "p1": 1,
    5. 5. "p2": 2
    6. 6. }"
    7. 7. */
    8. 8.
    9. 9. JSON.stringify({ p1:1, p2:2 }, null, '|-');
    10. 10. /*
    11. 11. "{
    12. 12. |-"p1": 1,
    13. 13. |-"p2": 2
    14. 14. }"
    15. 15. */

    参数对象的 toJSON 方法:

    如果参数对象有自定义的 toJSON 方法,那么 JSON.stringify 会使用这个方法的返回值作为参 数,而忽略原对象的其他属性。 下面是一个普通的对象。

    1. 1. var user = {
    2. 2. firstName: '三',
    3. 3. lastName: '张',
    4. 4.
    5. 5. get fullName(){
    6. 6. return this.lastName + this.firstName;
    7. 7. }
    8. 8. };
    9. 9.
    10. 10. JSON.stringify(user)
    11. 11. // "{"firstName":"三","lastName":"张","fullName":"张三"}"

    现在,为这个对象加上 toJSON 方法。

    1. 1. var user = {
    2. 2. firstName: '三',
    3. 3. lastName: '张',
    4. 4.
    5. 5. get fullName(){
    6. 6. return this.lastName + this.firstName;
    7. 7. },
    8. 8.
    9. 9. toJSON: function () {
    10. 10. return {
    11. 11. name: this.lastName + this.firstName
    12. 12. };
    13. 13. }
    14. 14. };
    15. 15.
    16. 16. JSON.stringify(user)
    17. 17. // "{"name":"张三"}"

    上面代码中, JSON.stringify 发现参数对象有 toJSON 方法,就直接使用这个方法的返回值作为 参数,而忽略原对象的其他参数。

    Date 对象就有一个自己的 toJSON 方法。

    1. 1. var date = new Date('2015-01-01');
    2. 2. date.toJSON() // "2015-01-01T00:00:00.000Z"
    3. 3. JSON.stringify(date) // ""2015-01-01T00:00:00.000Z""

    上面代码中, JSON.stringify 发现处理的是 Date 对象实例,就会调用这个实例对象 的 toJSON 方法,将该方法的返回值作为参数。 toJSON 方法的一个应用是,将正则对象自动转为字符串。因为 JSON.stringify 默认不能转换正 则对象,但是设置了 toJSON 方法以后,就可以转换正则对象了。

    1. 1. var obj = {
    2. 2. reg: /foo/
    3. 3. };
    4. 4.
    5. 5. // 不设置 toJSON 方法时
    6. 6. JSON.stringify(obj) // "{"reg":{}}"
    7. 7.
    8. 8. // 设置 toJSON 方法时
    9. 9. RegExp.prototype.toJSON = RegExp.prototype.toString;
    10. 10. JSON.stringify(/foo/) // ""/foo/""

    上面代码在正则对象的原型上面部署了 toJSON() 方法,将其指向 toString() 方法,因此转换成 JSON 格式时,正则对象就先调用 toJSON() 方法转为字符串,然后再被 JSON.stringify() 方法 处理。

    JSON.parse()

    JSON.parse 方法用于将 JSON 字符串转换成对应的值。

    1. 1. JSON.parse('{}') // {}
    2. 2. JSON.parse('true') // true
    3. 3. JSON.parse('"foo"') // "foo"
    4. 4. JSON.parse('[1, 5, "false"]') // [1, 5, "false"]
    5. 5. JSON.parse('null') // null
    6. 6.
    7. 7. var o = JSON.parse('{"name": "张三"}');
    8. 8. o.name // 张三

    如果传入的字符串不是有效的 JSON 格式, JSON.parse 方法将报错。

    1. 1. JSON.parse("'String'") // illegal single quotes
    2. 2. // SyntaxError: Unexpected token ILLEGAL

    上面代码中,双引号字符串中是一个单引号字符串,因为单引号字符串不符合 JSON 格式,所以报 错。 为了处理解析错误,可以将 JSON.parse 方法放在 try...catch 代码块中。

    1. 1. try {
    2. 2. JSON.parse("'String'");
    3. 3. } catch(e) {
    4. 4. console.log('parsing error');
    5. 5. }

    JSON.parse 方法可以接受一个处理函数,作为第二个参数,用法与 JSON.stringify 方法类似。

    1. 1. function f(key, value) {
    2. 2. if (key === 'a') {
    3. 3. return value + 10;
    4. 4. }
    5. 5. return value;
    6. 6. }
    7. 7.
    8. 8. JSON.parse('{"a": 1, "b": 2}', f)
    9. 9. // {a: 11, b: 2}

    上面代码中, JSON.parse 的第二个参数是一个函数,如果键名是 a ,该函数会将键值加上10。

  • 相关阅读:
    R语言爬虫程序自动爬取图片并下载
    提升用户留存率:如何集成直播实时美颜SDK来增强用户体验
    【数据结构】二叉搜索树——二叉搜索树的概念和介绍、二叉搜索树的简单实现、二叉搜索树的增删查改
    Docker-使用绑定挂载(持久化数据)
    C++面试八股文:了解位运算吗?
    最受欢迎的程序员副业排行榜TOP6
    Ansible自动化运维工具(二)playbook剧本
    小白学python系列————【Day48】设计模式之单例以及pickle模块
    我为什么要使用GPT????
    FreeRTOS的学习(二)——队列的介绍和操作
  • 原文地址:https://blog.csdn.net/zhan9le/article/details/125366923