Javascript学习笔记(三)

导论

Javascript可以将值分为两种类型,分别为原始值和对象类型值,而这章要介绍的就是对象Object

《JavaScript语言精辟》这本书里面提到的定义,可以很好理解所有的对象类型值:

数组是对象,函数是对象,正则表达式也是对象,当然,对象也是对象。

Object类型其实就是所有的对象类型值的基础,所有的对象类型值都是继承于Object对象,在Javascript中这称为这些对象类型的值都是Object的实例。

Object对象的方法和属性都分为两种,一种为Object对象本身的方法和属性,也称为静态方法和属性,一种为实例方法和属性,也称为实例方法和属性。

第一种指的是直接在Object对象本身上面定义的方法和属性,实例是无法调用的

Object.print = function () {
    console.log('Hello World');
};

第二种指的是直接在Object对象的prototype属性上定义的属性和方法,在prototype属性上定义的是可以被实例所使用的,具体的实现过程可以到《Javascript高级编程》目录的《原型继承》章节查看,而目前只需要知道定义在prototype属性上的方法和属性都是可以被实例使用的

Object.prototype.print = function () {
    console.log('This is print');
};

instanceof运算符

instanceof运算符可以用来检查一个对象是否为构造函数的实例

var a = new Array();
a instanceof Array //true

但是如果用任意的对象类型值去检查是否为Object类型的实例,那么始终都会返回true,因为所有的类型都是Object对象的实例

var a = new Array();
a instanceof Array //true
a instanceof Object //true

Object()函数

Object本身也是一个函数,该函数的作用是将任意值转换为对象,该方式可以用于保证一个值为对象,但如果传入的是一个对象,那么Object函数不会进行转换并直接返回该对象。

如果传入给Object函数的值为空值、undefinednull这三个值的话,会直接返回一个空的对象

var a = Object();
// 等同于
var a = Object(undefined);
// 等同于
var a = Object(null);

如果传入给Object函数的值为原始值,那么将会返回一个原始值对应的包装对象,所以这里需要注意的是传递原始值给Object函数,返回的是一个对象,而不是原始值了,比如下面返回的一个Number包装对象

var a = Object(1);
a instanceof Number // true

Object构造函数

Object本身也是一个构造函数,即在前面加new,构造函数主要的用途用于生成一个新的对象

var a = new Object();
//等价于字面量
var a = {};

Object构造函数如果传入的是一个对象,那么将会直接返回该对象,而不会生成一个新的对象

var a = new Object();
var b = new Object(a);

a === b // true

Object构造函数如果是传入的原始值,那么返回的是该原始值对应的包装对象

var a = Object(1);
a instanceof Number // true

静态方法

Object.keys()和Object.getOwnPropertyNames()

Object.keys()Object.getOwnPropertyNames()静态方法都是用来遍历实例对象的属性名(不包含继承属性),两个方法都接受一个对象作为参数,并返回一个数组,包含该对象所有本身的属性名。

Object.keys()会返回所有实例对象的属性名,但是不包括不可枚举的属性名。

Object.getOwnPropertyNames()会返回所有实例对象的属性名,会包括不可枚举的属性名。

var a = {
    a: 1,
    b: 2
};

Object.keys(a) // ['a', 'b']
// 在一般情况下两个方法返回的是一致的
Object.getOwnPropertyNames(a) // ['a', 'b']

Javascript没有提供可以返回对象属性个数的方法和属性,所以可以通过这两个方法来检查对象的个数,因为两个方法返回的都是数组,所以通过length属性可以直接来查询

var a = {
    a: 1,
    b: 2
};

Object.keys(a).length // 2
Object.getOwnPropertyNames(a).length //2

其他静态方法

(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.valueOf():返回当前对象对应的值。
  • Object.prototype.toString():返回当前对象对应的字符串形式。
  • Object.prototype.toLocaleString():返回当前对象对应的本地字符串形式。
  • Object.prototype.hasOwnProperty():判断某个属性是否为当前对象自身的属性,还是继承自原型对象的属性。
  • Object.prototype.isPrototypeOf():判断当前对象是否为另一个对象的原型。
  • Object.prototype.propertyIsEnumerable():判断某个属性是否可枚举。

Object.prototype.valueOf()

valueOf实例方法在默认情况下是返回的对象本身

var a = {a : 1};
a.valueOf() === a //true

该方法主要用于Javascript自动转换为预期值的时候使用的,其转换过程就会调用这个方法,那如果通过定义对象的本身的valueOf方法,从而覆盖继承的实例方法,就可以自定义返回值

var a = {
    valueOf : function(){return 1}
};

a.valueOf + 1 // 2

Object.prototype.toString()

Object.prototype.toString.toString()默认情况下会返回类型字符串,关于为什么返回类型字符串可以看类型转换的章节。

var a = {};

a.toString()    //[object Object]

该方法主要用于Javascript自动转换为字符串的时候使用的,其转换过程就会调用这个方法,那如果通过定义对象的本身的toString方法,从而覆盖继承的实例方法,就可以自定义返回值

var a = {
    toString : function(){return 'Hello '}
};

a + 'yizhou' // 'Hello yizhou'

数组、字符串、函数、Date 这几个对象都分别由ECMA规定了需要部署自定义的实现,所以这几个对象类型的值的toString方法表现的形式是不同的

var a = [1, 2];
a.toString()    //'1,2'

var a = Object('111');
a.toString()    //'111'

Object.prototype.toLocaleString()

Object.prototype.toLocaleString方法与toString的返回结果相同,这个方法主要的作用用于留出一个接口,让每个对象可以针对某些地域返回特定的值,目前有三个对象部署了自定义的Object.prototype.toLocaleString方法,分别为:

  • Array.prototype.toLocaleString()
  • Number.prototype.toLocaleString()
  • Date.prototype.toLocaleString()

最为直观的可以看见该方法的作用是Date对象, 该对象的Object.prototype.toLocaleString方法根据用户设定的所在相关地域返回特定的值

var date = new Date();

date.toString() //"Tue Nov 20 2018 15:16:17 GMT+0800 (China Standard Time)"
date.toLocaleString()   //"11/20/2018, 3:16:17 PM"

Object.prototype.hasOwnProperty()

Object.prototype.hasOwnProperty()方法用于判断该属性是否存在于该对象的本身,不包含继承对象,即只检查该对象的静态属性,该方法接受一个属性名,为字符串形式的。

var a = {
    print : 'hello'
};

a.hasOwnProperty('toString')    //false 因为该属性是继承的
a.hasOwnProperty('print')   //true

参考链接

《Object对象》 – 阮一峰

导论

该章节除了在基础数据类型章节中记录了一些基础的方法之外,主要记录的是由JavaScript定义的一些针对于Function对象标准库。

函数的作用是将一些常用的代码块进行封装,如果需要用到这些代码块的时候就可以直接调用这个函数,而不用重复再写这些代码。

JavaScript中的函数实际也是对象,和数组一样都是一种特殊的对象,只是函数内部存储的是一块代码,同样有属性和方法。

function language() {
    return 'javascript';
}

language['num'] = 1; //设置属性

language['num'] // 1 使用方括号操作符
language.num // 1 使用点操作符

声明函数有三种方法,分别为:

  • function命令创建
  • 函数表达式创建
  • 构造函数创建

function命令创建函数是通过function命令,然后后面跟着函数名以及圆括号()然后是大括号{},圆括号是定义参数,每个参数用逗号分隔,而大括号就是定义函数的执行代码,称为函数体。

下面的函数创建了一个名为language的函数,并要求传入一个编程语言的名称,并返回这个值。

function language(name) {
    return name;
};

language('javascript') //javascript  调用函数并返回传入参数的值

函数表示式也是通过function命令创建的,然后将这个函数返回给一个变量,但是可以不用创建函数名

var language = function(name) {
    return name;
};

language('javascript') //javascript

函数表达式也可以创建带有函数名的函数,但是加上了函数名过后,该函数名只有在函数体内部有效,而在外部是无效的

var language = function a(name) {
    return name;
};

a('javascript') //报错:ReferenceError: a is not defined

构造函数创建的方式是通过new命令加Function构造函数进行创建的,构造函数接收三个参数,第一和第二个参数都为定义的函数需要传入的参数,然后第三个参数为函数体的代码,如果只传入一个参数,这个参数即为函数体代码

var language = new Function(
    'x',
    'y',
    'return x + y;'
);

language('javascript', ' hello') // javascript hello

而重复声明函数将会以最后一个声明的为准

function a() {
  console.log(1);
}

function a() {
  console.log(2);
}

a() // 2

实例属性

Function对象没有自己的属性和方法, 但是因为它本身也是函数,所以它也会通过原型链从Function.prototype上继承部分属性和方法。

name

name用来返回函数的名称,比如下面这样

function a () {};
a.name  //"a"

但是根据定义的方式不同,返回的函数名称的行为也是不相同的,比如不带函数名的表达式,则返回变量名

var a = function () {};
a.name  //"a"

如果是带函数名的表达式则返回该函数名

var a = function b () {};
a.name  //"b"

使用构造函数Function创建或者使用Function函数生成的函数名称都为anonymous

var a = Function();
a.name  //"anonymous"

var b = new Function();
a.name  //"anonymous"

length

length 是函数对象的一个属性值,返回该函数有多少个要传入的参数。

function a (ac, ab, ad) {};
a.length    // 3

caller

caller方法用于返回调用该函数的函数本身,下面的代码通过caller返回调用函数本身

function b () {
    return b.caller;
};

function a () {
    console.log(b() === arguments.callee);  //true
};

那么就可以利用下面的代码来查找是哪一个函数调用了该函数

function b () {
    if (b.caller == null) {
      console.log("该函数在全局作用域内被调用!");
   } else {
      console.log("调用我的是函数是" + b.caller.name);
   }
};
function a () { b() }   // 调用我的是函数是a

实例方法

call和apply

call方法接受两个参数,一个是改变函数中的this对象,第二个则是一个数组或者类似数组的结构,其中的每个值都为传递的参数,如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装。

var a = {
    num: 1
}

function fc (arg1, arg2) {
    console.log('改变的this对象:'+ this.num);
    console.log('传递的参数1:' + arg1);
    console.log('传递的参数2:' + arg2);
}

fc.apply(a, ['参数1', '参数2'])

// 改变的this对象:1
// 传递的参数1:参数1
// 传递的参数2:参数2

call方法的作用和apply方法类似,区别就是call方法接受的是参数列表,而apply方法接受的是一个参数数组,如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装。

var a = {
    num: 1
}

function fc (arg1, arg2) {
    console.log('改变的this对象:'+ this.num);
    console.log('传递的参数1:' + arg1);
    console.log('传递的参数2:' + arg2);
}

fc.call(a, '参数1', '参数2')
// 改变的this对象:1
// 传递的参数1:参数1
// 传递的参数2:参数2

bind

bind方法用于绑定一个函数的this参数,并返回由指定的this值和初始化参数改造的原函数拷贝,bindcall方法的用法相似,都是以参数列表方式来接受参数,第一个为需要指定this对象。

需要注意的是bind只是绑定,并且返回的是由指定的this值和初始化参数改造的原函数拷贝,所以它不会像callapply一样调用过后马上执行,而是返回一个改造的函数,并当你需要执行的时候再执行

var a = {
    num: 1
}

function fc (arg1, arg2) {
    console.log('改变的this对象:'+ this.num);
    console.log('传递的参数1:' + arg1);
    console.log('传递的参数2:' + arg2);
}

var newfc = fc.bind(a, '参数1', '参数2');
//返回一个函数


newfc()
// 改变的this对象:1
// 传递的参数1:参数1
// 传递的参数2:参数2

toString

函数的toString方法返回函数内部的函数体源码,包括注释也会返回

function a() {
  console.log(1);
  // 注释
}

a.toString()
//"function a() {
//  console.log(1);
//  // 注释
//}"

参考链接

MDN》- Mozilla

导论

JavaScript原生提供了数组Array对象,该数组对象也是一个构造函数,一般创建一个数组有三种方式:

  • 字面量[]
  • 构造函数Array
  • 调用Array函数

这两种方式是相等的,比如下面这样

var a = [];
//等同
var b = new Array();
//等同构造函数和调用函数的行为相同的
var c = Array();

但是构造函数因参数的不同,导致构造函数的行为也是不一样的

var a = new Array();
a   
//返回空数组 []

var a = new Array(2);
a   
//  [empty × 2]
// 定义了一个长度为2的数组,没有任何值,仅用空位占用了两个位

var a = new Array('a');
a
// ['a'] 返回长度为1的数组,第一个值为'a'

var a = new Array(Object);
a   
// [Object] 返回长度为1的数组,第一个值为传递的对象

var a = new Array(1, 2, 3, 4);
// [1, 2, 3, 4] 多个参数时,每个参数作为数组的其中的值


所以在平常的使用中,建议直接用字面量进行定义,尽量的避免用构造函数进行创建数组。

静态方法

在ES2015(ES6)之前Array只有一个实例方法为isArray,而且本记录主要以ES6之前的版本记录,所以不会涉及到之后更新的新方法。

isArray方法

isArray方法可以用于检查一个值的类型是否为数组,该方法如果检查到数组类型的值,就会返回true,其他的都返回为false,该方法与Object.prototype.toString方法都可以达到检查是否为数组的效果

Array.isArray([])   //true
Array.isArray({})   //false
Array.isArray('2')  //false
// 也可以用toString方法
Object.prototype.toString.call([])  //"[object Array]"

实例属性

length 是Array的实例属性,返回或设置一个数组中的元素个数。该值是一个无符号 32位的整数,并且总是大于数组最高项的下标。

在所有的数组类型、类数组类型中,下标总是从0开始,所以第一个值的下标为0,后面的依次类推,所以length实例属性返回的总比数组下标多1

var a = [1, 2];
a.length    //2

a[0]    // 1
a[1]    // 2
a[2]    // undefined

实例方法

valueOf和toString

valueOf方法和toString方法都是所有对象类型的值通用的方法,但是每个对象类型针对于这两个方法返回的行为是不一样的

valueOf方法会返回本身

var a = [1];

a.valueOf() === a   //true

toString方法会返回数组的字符串形式,每个数组的元素用逗号分隔,并返回所有的值

var a = [1, 2, 'a', 'b'];
a.toString()    //"1,2,a,b"

pop和push

为数组在末尾追加一个值可以使用push方法,该方法会返回数组的新长度

var a = [1];
a.push(2)   //2

a   //[1, 2]

如果要在末尾删除一个值可以使用pop方法,该方法会返回数组的新长度,当数组为空时返回undefined

var a = [1, 2];
a.pop() //1

a   //[1]

shift和unshift

shiftunshift方法和poppush作用一样,但是作用的位置不一样,shiftunshift方法用于从第一位操作,即shift删除第一位元素,该方法会返回数组的新长度

var a = [1, 2];
a.shift()   //1
a   //[2]

unshift向第一位添加元素,并且可以一次性添加多个值,每个值为一个参数,该方法会返回数组的新长度

var a = [2];
a.unshift(1)    //2
a   //[1, 2]

//添加多个元素
var a = [3];
a.unshift(1, 2) //3
a   //[1, 2, 3]

splice

该方法用索引值来修改、删除、添加数组,并且该方法会影响到原数组,执行后返回被删除操作的原始值,比如下面的代码,第一个参数为索引位置,第二个为从索引位置到多少截止,第三个为替换元素值。

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

除此之外,这个方法还可以继续添加参数,而这些添加的参数都作为操作的位置的新元素,如果操作的位置小于后面的参数个数,那么还是会依次继续添加,比如下面的代码

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

a   //[3, 4, 5, 2]

如果想对一个地方只添加内容的话则可以设置第二个参数为0

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

a   //[3, 1, 2]

slice

slice方法用索引值提取数组中的一部分,并返回一个新数组,该方法有两个参数,第一个为提取的开始位置,第二个为提取的结束位置,但是返回的新数组中不包括结束位置的值。

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

如果不设置第二个参数,那么将会从指定位置一直到数组最后一个元素

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

reverse

reverse方法将数组中元素的位置颠倒,第一个数组元素成为最后一个数组元素,最后一个数组元素成为第一个,此方法将改变原始数组

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

sort

sort方法对数组成员进行排序,这里需要注意的是该方法并不是按照大小写排序,而是默认排序是按照Unicode位点进行排序,所以所有的值将会被先转换为字符串,然后用Unicode排序,该方法会改变原始数组。

比如10111,Unicode中101排在11前面

var a = [11, 101];
a.sort()    //[101, 11]

除此之外,你还可以该变默认的排序方式,即可以向sort方法传入一个函数,这个函数每次接收两个值,会依次接受处理元素,比如升序排序,如果该函数的返回值大于0,表示第一个成员排在第二个成员后面;其他情况下,都是第一个元素排在第二个元素前面。

var a = [3, 2, 1];
a.sort(function (a, b) {
    return a - b;
})
// [1, 2, 3]

map

map方法传入一个函数,并将函数每次返回的值作为新数组的元素,然后返回这个新数组,意思就是说需要在处理函数中显性的进行return,不然函数默认返回undefined

该处理接收三个参数,分别为:

  1. 当前成员
  2. 当前成员索引
  3. 数组本身
[1, 2, 3].map(function (item, index, arr) {
    return item + 1;
})
//[2, 3, 4]

map方法会跳过空位,但是不会跳过undefinednull,比如下面的代码,map不会对空位进行处理

[1, null, undefined,, 2].map(function (item, index, arr) {
    return 111;
})
// [111, 111, 111, empty, 111]

除此之外map方法还可以接受第二个参数,用于来绑定this对象

var a = ['a', 'b', 'c'];
[1, 2].map(function (item) {
    return this[item];
}, a)

//["b", "c"]

forEach

forEach方法与map方法基本一致,但是forEach方法不会返回值,所以forEach方法适合用来做操作值,而map方法适合用来得到返回值。

该方法接收三个参数,分别为:

  1. 当前成员
  2. 当前成员索引
  3. 数组本身
[1, 2, 3].forEach(function (item, index, arr) {
    console.log(item);
})
// 1
// 2
// 3

forEach方法会跳过空位,但是不会跳过undefinednull,比如下面的代码,forEach不会对空位进行处理

[1, null, undefined,, 2].forEach(function (item, index, arr) {
    console.log(item)
})
// 1
// null
// undefined
// 2

除此之外foreach方法还可以接受第二个参数,用于来绑定this对象

var a = ['a', 'b', 'c'];
[1, 2].forEach(function (item) {
    console.log(this[item]);
}, a)

// b
// c

在使用forEach方法的时候需要注意使用中是无法中断的,在forEach内部是无法使用breakcountine,如果使用的话,将会报错

[1, 2, 3].forEach(function (item, index, arr) {
    if (arr[1] == 2) continue;
    console.log(item);
})
//VM634:2 Uncaught SyntaxError: Illegal continue statement: no surrounding iteration statement

join

join方法用于将数组的每个元素用指定的符号分隔并拼接每个元素后返回一个字符串值,默认以逗号分隔

var a = [1, 2];
a.join()    //"1, 2"
a.join('-') //"1-2"
a.join(' ') //"1 2"

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

var a = [1, undefined, 2];
a.join('-') //"1--2"

concat

concat方法用于将一个或多个数组依次添加到数组当中,并返回一个新数组,传入该方法的参数如果是数组,会进行解构,即将传入的数组中的每个值依次添加到数组当中

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

该方法的如果遇见是基本值,就会将其当中元素添加进数组

var a = [1, 2];
a.concat('a')   //[1, 2 'a']

如果是其他对象类型的值,将会把该对象的地址传入数组当中作为元素

var a = [1, 2];
a.concat({})    //[1, 2, {}]

filter

filter方法用于将满足条件的元素返回,并组成一个新的数组,该方法接受一个函数,会依次处理数组中的每个元素,如果为true则返回,反之则不返回,函数接受三个参数:

  1. 当前成员
  2. 当前成员索引
  3. 数组本身
[1, 2, 3].filter(function (item, index, arr) {
    return item >= 2;
})
// [2, 3]

除此之外filter方法还可以接受第二个参数,用于来绑定this对象

var a = {Max: 2};
[1, 2].filter(function (item, index, arr) {
    if (item >= this.Max)   return true;
}, a)

// [2]

some和every

someevery方法类似断言,并且两种方法只会返回两种值,即truefalse,这两个方法都是会接受一个函数,函数接受同样的三种参数:

  1. 当前成员
  2. 当前成员索引
  3. 数组本身

但是someevery的行为不一致的地方在于,如果some方法其中一个元素经过函数处理后返回为true,那么则返回true

[1, 2, 3].some(function (item, index, arr) {
    if ( item >= 2 ) return true;
})
//true

every方法则是需要全部元素都满足了条件才会返回true

[1, 2, 3].every(function (item, index, arr) {
    if ( item >= 2 ) return true;
})
//false

这两个方法还可以接受第二个参数,用于来绑定this对象

var a = {Max: 2};
[1, 2, 3].some(function (item, index, arr) {
    if ( this.Max >= 2) return true;
}, a)

// true

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

reduce和reduceRight

reducereduceRight方法用于依次处理每一个元素,并最终累计成一个值,它们的差别是,reduce是从左到右处理(从第一个成员到最后一个成员),reduceRight则是从右到左(从最后一个成员到第一个成员),其他完全一样。

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

  1. 累积变量,默认为数组的第一个成员
  2. 当前变量,默认为数组的第二个成员
  3. 当前位置(从0开始)
  4. 原数组
[1, 2, 3].reduce(function (total, item, index, arr) {
    console.log(total, item);
    return total + item;
})
//1 2
//3 3
//6 最终返回为6

这四个参数之中,只有前两个是必须的,后两个则是可选的。

如果要对累积变量指定初值,可以把它放在reduce方法和reduceRight方法的第二个参数,由于空数组取不到初始值,reduce方法会报错。这时,加上第二个参数,就能保证总是会返回一个值。

[].reduce(function (total, item, index, arr) {
    return total + item;
}, 10)
//10

indexOf和lastIndexOf

indexOflastIndexOf两个方法用于搜索指定值出现在数组中的位置,如果查找则返回对应的索引值,如果没有查找到则返回-1,两个方法的区别在于indexOf是查找值首次出现的位置,而lastIndexOf则是查找值最后出现的位置,而这两个方法都接受两个参数:

  1. 查找的值
  2. 开始查找的位置
var a = ['a', 'b', 'c'];
a.indexOf('b')  //1

如果当indexOflastIndexOf两个方法都返回一致时,也能表明这个值是唯一的

var a = ['a', 'b', 'c'];
a.indexOf('b') === a.lastIndexOf('b')
//true

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

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

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

参考链接

《Array对象》 – 阮一峰

MDN》- Mozilla

Determining with absolute accuracy whether or not a JavaScript object is an array》 – jwalden

导论

Date对象库是原生的Javascript时间库,该库的时间是以1970年1月1日00:00:00作为时间的基点,可以表示的时间范围为前后各一亿天,单位为毫秒。

工具函数

如果当作工具函数使用,该工具函数将返回当前时间的字符串

Date()
"Wed Nov 28 2018 10:42:00 GMT+0800 (China Standard Time)"

构造函数

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

var a = new Date();

a   //Wed Nov 28 2018 10:39:39 GMT+0800

构造函数可以传入多种方式的参数,并且根据传入的参数不同,作出的反应也是不一样。

// 参数为时间零点开始计算的毫秒数
new Date(1000)  //返回基点时间后的一秒的时间
// Thu Jan 01 1970 08:00:01 GMT+0800

// 参数为日期字符串
new Date('January 6, 2013');
// Sun Jan 06 2013 00:00:00 GMT+0800

// 参数为多个整数,
// 代表年、月、日、小时、分钟、秒、毫秒
new Date(2013, 0, 1, 0, 0, 0, 0)
//Tue Jan 01 2013 00:00:00 GMT+0800

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

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

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

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

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

new Date(2013, 0)
// Tue Jan 01 2013 00:00:00 GMT+0800 (CST)
new Date(2013, 0, 1)
// Tue Jan 01 2013 00:00:00 GMT+0800 (CST)
new Date(2013, 0, 1, 0)
// Tue Jan 01 2013 00:00:00 GMT+0800 (CST)
new Date(2013, 0, 1, 0, 0, 0, 0)
// Tue Jan 01 2013 00:00:00 GMT+0800 (CST)

上面代码中,不管有几个参数,返回的都是2013年1月1日零点。

最后,各个参数的取值范围如下。

  • 年:使用四位数年份,比如2000。如果写成两位数或个位数,则加上1900,即10代表1910年。如果是负数,表示公元前。
  • 月:0表示一月,依次类推,11表示12月。
  • 日:131
  • 小时:023
  • 分钟:059
  • 秒:059
  • 毫秒:0999

注意,月份从0开始计算,但是,天数从1开始计算。另外,除了日期的默认值为1,小时、分钟、秒钟和毫秒的默认值都是0

这些参数如果超出了正常范围,会被自动折算。比如,如果月设为15,就折算为下一年的4月。

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

上面代码的第二个例子,日期设为0,就代表上个月的最后一天。

参数还可以使用负数,表示扣去的时间。

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

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

日期的运算

Date类型的实例转换为数值则是转换为相应的毫秒数,而转换为字符串则是转换为对应的日期字符串。

var a = new Date(1000);
Number(a)   //1000
String(a)   //"Thu Jan 01 1970 08:00:01 GMT+0800"

所以如果对Date类型的实例进行减法运算的时候实际上是先转换为毫秒数值,然后再进行相减,然后返回的是两个时间的毫秒差值。

var a = new Date(1000);
var b = new Date(1005);

b - a   //5

而如果对Date类型的实例进行加法运算的时候,实际上是先转为对应的日期字符串格式,然后再进行字符串拼接

var a = new Date(1000);
//Thu Jan 01 1970 08:00:01 GMT+0800
var b = new Date(1005);
//Thu Jan 01 1970 08:00:01 GMT+0800

a + b
//"Thu Jan 01 1970 08:00:01 GMT+0800 Thu Jan 01 1970 08:00:01 GMT+0800

静态方法

now

Date.now方法返回当前时间距离时间零点(1970年1月1日 00:00:00 UTC)的毫秒数,

Date.now() // 1543378470844

而Unix时间戳是以秒为单位的,而Javascript中是以毫秒为单位,所以用返回的值除以1000,然后取整,就可以得到Unix时间戳

Math.floor(Date.now() / 1000)
// 1543378535

parse

Date.parse方法用于解析日期字符串,返回当前解析的日期字符串离基础点的毫秒数

日期字符串应该符合 RFC 2822 和 ISO 8061 这两个标准,即YYYY-MM-DDTHH:mm:ss.sssZ格式,其中最后的Z表示时区。但是,其他格式也可以被解析,请看下面的例子。

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

如果解析失败将会返回NaN

Date.parse('hello word')    //NaN

UTC

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

// 格式
Date.UTC(year, month[, date[, hrs[, min[, sec[, ms]]]]])

// 用法
Date.UTC(2011, 0, 1, 2, 3, 4, 567)
// 1293847384567

该方法的参数用法与Date构造函数完全一致,比如月从0开始计算,日期从1开始计算。区别在于Date.UTC方法的参数,会被解释为 UTC 时间(世界标准时间),Date构造函数的参数会被解释为当前时区的时间。

实例方法

Date的实例对象,有几十个自己的方法,除了valueOftoString,可以分为以下三类。

  • to类:从Date对象返回一个字符串,表示指定的时间。
  • get类:获取Date对象的日期和时间。
  • set类:设置Date对象的日期和时间。

valueOf和toString

Date对象类型单独部署了valueOftoString这两个方法,其中valueOf方法返回与基础点的差值

var a = new Date(1000);
a.valueOf() //1000

toString方法返回实例的日期对应的字符串

var a = new Date(1000);
a.toString()
//"Thu Jan 01 1970 08:00:01 GMT+0800"

toUTCString

toUTCString方法返回实例的世界标准时间(UTC)的字符串形式,也就是比中国会晚8个小时的时间

new Date(1000).toUTCString()
//"Thu, 01 Jan 1970 00:00:01 GMT"

toISOString

toISOString方法返回实例的世界标准时间(UTC)的字符串形式,格式为ISO8601标准。

new Date(1000).toISOString()
//"1970-01-01T00:00:01.000Z"

toJSON

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

var d = new Date(2013, 0, 1);

d.toJSON()
// "2012-12-31T16:00:00.000Z"

toDateString和toTimeString

toDateString方法返回实例的日期字符串,不包含小时、分、秒

new Date().toDateString()   //"Wed Nov 28 2018"

toTimeString方法返回实例的时间字符串,与toDateString相反,不包含年月日

new Date().toTimeString()   //"15:39:19 GMT+0800"

本地时间

与本地时间相关的有三个方法,分别为:toLocaleStringtoLocaleDateString以及toLocaleTimeString

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

  • Date.prototype.toLocaleString():完整的本地时间。
  • Date.prototype.toLocaleDateString():本地日期(不含小时、分和秒)。
  • Date.prototype.toLocaleTimeString():本地时间(不含年月日)。

下面是用法实例。

var d = new Date(2013, 0, 1);

d.toLocaleString()
// 中文版浏览器为"2013年1月1日 上午12:00:00"
// 英文版浏览器为"1/1/2013 12:00:00 AM"

d.toLocaleDateString()
// 中文版浏览器为"2013年1月1日"
// 英文版浏览器为"1/1/2013"

d.toLocaleTimeString()
// 中文版浏览器为"上午12:00:00"
// 英文版浏览器为"12:00:00 AM"

这三个方法都有两个可选的参数,两个参数分别为:

  • locales:代表指定所用语言的字符串
  • options:配置对象

其中第一个参数的格式参照的规范是BCP 47,常见的有en-USzh-CN,指定了不同的语言后,返回的字符串格式也是不相同的

new Date().toLocaleString('en-US')
//"11/28/2018, 4:35:08 PM"

new Date().toLocaleString('zh-CN')
"2018/11/28 下午4:35:35"

其中第二个参数是针对于日期格式的一些配置,因为涉及的内容过多并且不常用,所以可以看MDN提供的相关文档,这些配置常见的有24小时制,指定时区等

new Date().toLocaleString('en-US', {
  timeZone: 'UTC',
  timeZoneName: 'short'
})
//"11/28/2018, 8:47:51 AM UTC"


new Date().toLocaleString('en-US', {
    hour12: false   //采用24小时制度
})
//"11/28/2018, 16:49:35"

get类

Date对象提供了一系列get*方法,用来获取实例对象某个方面的值。

  • getTime():返回实例距离1970年1月1日00:00:00的毫秒数,等同于valueOf方法。
  • getDate():返回实例对象对应每个月的几号(从1开始)。
  • getDay():返回星期几,星期日为0,星期一为1,以此类推。
  • getYear():返回距离1900的年数。
  • 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(十二月)
  • 年份:距离1900年的年数

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

  • getUTCDate()
  • getUTCFullYear()
  • getUTCMonth()
  • getUTCDay()
  • getUTCHours()
  • getUTCMinutes()
  • getUTCSeconds()
  • getUTCMilliseconds()

set类

Date对象提供了一系列set*方法,用来设置实例对象的各个方面。

  • setDate(date):设置实例对象对应的每个月的几号(1-31),返回改变后毫秒时间戳。
  • setYear(year): 设置距离1900年的年数。
  • 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):设置毫秒时间戳。

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

  • setUTCDate()
  • setUTCFullYear()
  • setUTCHours()
  • setUTCMilliseconds()
  • setUTCMinutes()
  • setUTCMonth()
  • setUTCSeconds()

参考链接

MDN》- Mozilla

Date》- 阮一峰

导论

JSON(JavaScript Object Notation)格式是一种数据交换格式,2001年由Douglas Crockford提出,目的是取代繁琐笨重的XML数据格式。

JSON格式优处在于Javascript引擎能够直接处理这样的格式,并且有着原生的JSON对象,还有一个优处在于JSON数据格式现在正在被更多的人接受,如微软、谷歌都在大量使用这样的数据交换格式。

JSON对类型和值有着严格的要求:

  1. 复合类型的值只有数组和对象,不能是函数、正则表达式、日期对象
  2. 原始类型的值只有四种:字符串、数值(必须十进制)、布尔值和null
  3. 字符串必须使用双引号,不能使用单引号
  4. 对象的键名必须放在双引号里面
  5. 数组和对象最后一个成员的后面不能添加逗号
{"a": 123}  //合法
{a: 123}    //不合法

[1, 2, 3, 0x22] //不合法

但是上面对于JSON的严格要求是基于直接定义的JSON格式,如果你将Javascript对象转换为JSON对象的中包含了一些不符合JSON格式的要求,会被自动转换,如函数、正则、日期都会被转换为null,而对应的其他进制的值,如八进制、十六进制等,都会被Javascript引擎转换为相应的字符,总之,在Javascript中合法的字符,最终都会被尽量的转换为合理的JSON格式

var a = {a: 123};
JSON.stringify(a)   
//"{"a":123}" 自动为属性名添加双引号


var a = [{a: 1}, undefined, 0x22, function a(){}];
JSON.stringify(a)
//"[{"a":1},null,34,null]"

JSON对象

JSON对象是JavaScript的原生对象,需要注意的是JSON这四个字符是全大写,而Javascript是需要辨别大小写的,该对象提供了两个方法,分别为:

  • stringify:用于将Javascript中的值转换为JSON对象
  • parse:用于将JSON对象转换为Javascript的值

stringify

stringify方法用于将Javascript中的值转换为JSON对象,并且会将所有的单引号转换为双引号

JSON.stringify({a: 1})
//"{"a":1}"

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

JSON.stringify([])
//"[]"

如果对象的属性是undefined、函数或 XML 对象,该属性会被JSON.stringify过滤。

var obj = {
  a: undefined,
  b: function () {}
};

JSON.stringify(obj) // "{}"

但是如果转换的是为数组,那么将会将这些值相应的位置替换为null

var a = [{a: 1}, undefined, 0x22, function a(){}];
JSON.stringify(a)
//"[{"a":1},null,34,null]"

而正则表达式将会被转换为空对象{}

JSON.stringify([/s/, 1])    //"[{},1]"
JSON.stringify({a: /s/})    //"{"a":{}}"

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

第二个参数

stringify第二个参数可以接受两种值,一种值为数组,一种值为函数,而两种类型的值的行为大不相同。

如果第二个参数为数组,那么数组当中的成员类似于白名单,会限定stringify转换的JSON数据,这个白名单数组中每个元素都对应对象的属性名,只有出现在白名单数组中的属性名才会被返回,该白名单数组只对需要转换的对象起作用,而数组不会起作用

var a = {
    p1: 1,
    p2: 2,
    p3: 3,
    p4: 4,
    p5: 5
};
JSON.stringify(a, ['p1', 'p2'])
//"{"p1":1,"p2":2}"


JSON.stringify([1, 2, 3], [0])
//"[1,2,3]" 不会生效

如果第二个参数为函数,则可以控制stringify方法的行为,该函数接受两个参数,一个为属性名key,一个为值value,比如下面的代码可以控制stringify的行为,如果是数值则加1返回

function json_format (k, v) {
    if(typeof v === 'number'){
        v = v + 1;
    }
    return v;
}

JSON.stringify({a: 1, b: 2}, json_format)
//"{"a":2,"b":3}"

如果该参数是一个数组,则只有包含在这个数组中的属性名才会被序列化到最终的 JSON 字符串中,还需要注意的是如果转换的是一个对象,那么第一次循环是一个空属性名

function json_format (k, v) {
    console.log('属性名:' + k);
    return v;
}

JSON.stringify({a: 1, b: 2}, json_format)
//属性名:  这里有一个空属性名
//属性名:a
//属性名:b
//"{"a":1,"b":2}"

第三个参数

stringify的第三个参数主要是为了增加转换后的可读性,。如果是数字,表示每个属性前面添加的空格(最多不超过10个);如果是字符串(不超过10个字符),则该字符串会添加在每行前面。

JSON.stringify({a: 1, b: 2}, null, '-|')
//"{
//-|"a": 1,
//-|"b": 2
//}"

JSON.stringify({a: 1, b: 2}, null, 5)
//
//"{
//     "a": 1,
//     "b": 2
//}"

参数对象的toJSON方法自定义

如果传入的转换对象中包含了自定义的toJSON方法,那么最后返回的内容将按照toJSON方法定义的返回值来返回,而不按照stringify方法默认的行为来返回,JSON.stringify发现参数对象有toJSON方法,就直接使用这个方法的返回值作为参数,而忽略原对象的其他参数。

var a = {
    a: 1,
    b: 2,
    toJSON: function () {
        return {
            value: 'toJSON'
        };
    }
}

JSON.stringify(a)
//"{"value":"toJSON"}"

toJSON方法的一个应用是,将正则对象自动转为字符串。因为JSON.stringify默认不能转换正则对象,但是设置了toJSON方法以后,就可以转换正则对象了。

var a = {
    a: /s/,
    b: 2
}

a.a.toJSON = RegExp.prototype.toString;

JSON.stringify(a)
//"{"a":"/s/","b":2}"

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

parse

parse方法用户将JSON字符串转换为Javascript的值,如果不是合法的JSON字符串,将会报错

JSON.parse('{}')    //{}
JSON.parse('[]')    //[]
JSON.parse('{"a": 1}')  //{a: 1}

JSON.parse('qqq')   //Unexpected token q in JSON at position 0

JSON.parse方法第二个参数,用法与JSON.stringify方法类似,但是需要注意的是,最外面的一个空的属性名是在最后显示的,也就是说parse方法是从内到外循环,而stringify是从外到内

function json_format (k, v) {
    console.log('属性名:' + k);
    return v;
}

JSON.parse('{"a": {"c": 3}, "b": 2}', json_format)
//属性名:c
//属性名:a
//属性名:b
//属性名:   空属性名
//{a: {…}, b: 2}

参考链接

MDN》- Mozilla

JSON对象》- 阮一峰

导论

Math是Javascript的原生对象,用于提供各种数学功能,Math不是一个构造函数,只是一个工具函数,所以只有静态属性和静态方法,如果使用new操作符,将会报错

new Math()
//VM1180:1 Uncaught TypeError: Math is not a constructor

静态属性

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

  • Math.E:常数e
  • Math.LN2:2 的自然对数。
  • Math.LN10:10 的自然对数。
  • Math.LOG2E:以 2 为底的e的对数。
  • Math.LOG10E:以 10 为底的e的对数。
  • Math.PI:常数π
  • Math.SQRT1_2:0.5 的平方根。
  • Math.SQRT2:2 的平方根。
Math.E // 2.718281828459045
Math.LN2 // 0.6931471805599453
Math.LN10 // 2.302585092994046
Math.LOG2E // 1.4426950408889634
Math.LOG10E // 0.4342944819032518
Math.PI // 3.141592653589793
Math.SQRT1_2 // 0.7071067811865476
Math.SQRT2 // 1.4142135623730951

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

静态方法

Math对象提供以下一些静态方法。

  • Math.abs():绝对值
  • Math.ceil():向上取整
  • Math.floor():向下取整
  • Math.max():最大值
  • Math.min():最小值
  • Math.pow():指数运算
  • Math.sqrt():平方根
  • Math.log():自然对数
  • Math.exp()e的指数
  • Math.round():四舍五入
  • Math.random():随机数

下面会挑选一部分静态方法讲解,而不是全部,因为有一部分的方法非常容易根据字面意思理解

max 和 min

maxmin都接受多个参数,并返回最大的参数和最小的参数,如果参数为空, Math.min返回Infinity, Math.max返回-Infinity

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

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

Math.min()  //Infinity
Math.max()  //-Infinity

floor 和 ceil

floorceil都是用于返回传入参数最接近的整数,其中floor返回小于参数的最大整数,而ceil则返回大于参数的最小整数

Math.floor(3.5) //3
Math.floor(-3.5)    //-4

Math.ceil(3.5)  //4
Math.ceil(-3.5) //-3

round

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

Math.round(0.1) // 0
Math.round(0.5) // 1
Math.round(0.6) // 1

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

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

pow

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

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

sqrt

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

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

log

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

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

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

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

exp

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

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

random

random方法主要用于生成0-1之间的随机数,产生的值可能会等于0,但不会大于或者等于1

Math.random()   //0.5581310114245439

比如生成1-10之间的随机整数以及生成1-100之间的随机整数

Math.floor(Math.random()*10)
Math.floor(Math.random()*100)

三角函数的方法

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

  • Math.sin():返回参数的正弦(参数为弧度值)
  • Math.cos():返回参数的余弦(参数为弧度值)
  • Math.tan():返回参数的正切(参数为弧度值)
  • Math.asin():返回参数的反正弦(返回值为弧度值)
  • Math.acos():返回参数的反余弦(返回值为弧度值)
  • Math.atan():返回参数的反正切(返回值为弧度值)
Math.sin(0) // 0
Math.cos(0) // 1
Math.tan(0) // 0

Math.sin(Math.PI / 2) // 1

Math.asin(1) // 1.5707963267948966
Math.acos(1) // 0
Math.atan(1) // 0.7853981633974483

参考链接

《Math对象》 – 阮一峰

Math》- MDN

导论

正则表达式是一种表达文字规则的工具,预先定义好一套文字规则,就可以用正则表达式去匹配,比如匹配Email地址,就可以定义一套Email的文字规则,就可以匹配到文字中所有的邮箱

正则表达式的创建方式有两种,分别为字面量//RegExp构造函数

var reg = /规则/修饰符;  //字面量
var reg = new RegExp('规则', '修饰符');  //构造函数

两个方法在效果上是对等的,而在效率上有一些不同,字面量是在引擎编译代码的时候,就会创建正则表达式,而构造函数的方式则是在运行时创建正则表达式,所以前者是效率较高的,并且字面量也容易理解。

比如下面就是一个正则表达式

var reg = /abc/i;
// 等同
var reg = new RegExp('abc', 'i');

实例属性

ignoreCase、global、multiline

ignoreCaseglobalmultiline都是与修饰符有关,分别表示正则表达式是否设置igm修饰符。

var a = /a/igm;
a.ignoreCase    //true
a.global    //true
a.multiline //true

这三个修饰符分别表示:

  • 忽略大小写i:忽略大小写匹配
  • 全局搜索g:找到所有匹配,而不是在第一个匹配后停止
  • 多行m:将开始和结束字符(^和$)视为在多行上工作(也就是,分别匹配每一行的开始和结束(由 \n 或 \r 分割),而不只是只匹配整个输入字符串的最开始和最末尾处。

lastIndex

lastIndex返回一个整数,表示下一次匹配开始的位置,该属性在全局匹配的时候是非常有用的,该方法会进行匹配的时候纪录下当前匹配到的字符所处于的位置,然后下次匹配从这个位置开始,如果没有匹配成功的时候,这个属性会置为0

var a = /a/g;

a.test('abcabc')    //true
a.lastIndex //1

a.test('abcabc')    //true
a.lastIndex //4

a.test('abcabc')    //false
a.lastIndex //0

lastIndex属性除非没有匹配成功才会置为0,所以同一个RegExp实例,对于不同字符串进行匹配的时候最好手动置0

var a = /a/g;

a.test('abcabc')    //true
a.lastIndex //1

a.test('abc')   //false
a.lastIndex //1

如果没有设置修饰符g则这个值会一直为0,因为正则表达式始终匹配到第一个成功的值就返回了

var a = /a/;

a.test('abcabc')    //true
a.test('abc')   //true

source

source方法返回正则表达式的规则,但不包含修饰符,即/规则/修饰符中的规则部分

var a = /abc/g;

a.source    //"abc"

实例方法

test

test方法执行一个检索,用来查看正则表达式与指定的字符串是否匹配。返回 truefalse

var a = /a/;

a.test('abc')   //true
a.test('cc')    //false

exec

方法在一个指定字符串中执行一个搜索匹配。返回一个结果数组或 null,如果该方法的实例不设置修饰符g那么只会匹配到第一次匹配的位置

var a = /a/g;

a.exec('abc')
//["a", index: 0, input: "abc", groups: undefined]

a.exec('sd')
//null

返回的数组当中的第一个成员为匹配的字符串,第二个、第三个、第四个都为捕获组的捕获到的内容,并且返回的数组还有indexinput属性,两个属性分别代表的为:本次匹配到的字符串的索引位置、以及匹配的完整字符串。

所以index是比实例的lastIndex1,而input则是等于原始匹配的字符串

var a = /a/g;

var str = 'abcabc';

var c = a.exec(a);

(c.index + 1) === a.lastIndex   //true

c.input === str //true

匹配规则:字面量字符和元字符

正则表达式中,字面量字符是字面表达的是什么则匹配什么,比如/a/则匹配字符a,但是除此之外的如字面量和实际表达的不一致的称为元字符,而一般这些元字符有着特殊的作用。

点字符.

点字符.表达的是匹配除回车(\r)、换行(\n) 、行分隔符(\u2028)和段分隔符(\u2029)以外的所有字符,比如下面的代码匹配一个开头为a,结尾为y的字符串。

var a = /a.y/;

a.test('any')   //true

但是需要注意的是对于码点大于0xFFFF字符,点字符不能正确匹配,会认为这是两个字符。

位置字符^和`

^$分别表示字符串的开始和字符串的结束位置,比如用来判断字符是不是存在开始或者尾部

var a = /^a.y$/;

a.test('any')   //true
a.test('an')    //false

选择字符|

竖线符号表示为,即其中匹配到一个就算匹配成功

var a = /any|aby/;

a.test('any')   //true
a.test('aby')   //true

匹配规则:转义字符

转义字符指的是哪些有些特殊意义的字符,如果你需要当作普通字面意思使用,那么需要进行转义,比如上面的点字符串,如果不转义,则代表匹配非空白字符,转义后则这个字符代表的就是所表达的意思

var a = /a.y/;
a.test('a.y')   //true,因为.代表任何非空白值
a.test('any')   //true,所以这样的值也能匹配

var a = /a\.y/;
a.test('a.y')   //true
a.test('any')   //false

正则表达式中,需要反斜杠转义的,一共有12个字符:^.[$()|*+?{\。需要特别注意的是,如果使用RegExp方法生成正则对象,转义需要使用两个斜杠,因为在Javascript中,字符串中的\也是转义字符。

就好比下面这样,你想匹配的其实为a\t,但是因为字符串遇见\反斜杠就会对后面的紧跟着的字符进行转义,所以下面字符串转义后等于a制表符

var a = new RegExp('a\t');

所以需要在反斜杠前再加一个反斜杠,即a\\t,当字符串解析过后得到的为a\t,然后就可以正常的解析为正则表达式了。

匹配规则:特殊字符

正则表达式对一些不能打印的特殊字符,提供了表达方法。

  • \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 字符。

匹配规则:字符组

正则表达式中用中括号[]表示字符组,字符组相当于多选择,放在字符组中的字符只要其中一个字符存在于匹配的字符串中都会表示匹配成功,比如下面的字符串中包含了字符组中的a字符,所以返回true

/[ab]/.test('ffaff')    //true

在字符组中还有两个字符属于特殊字符,它们和字面意思不一样,分别为:

  • ^脱字符:该字符只会在字符组的第一个位置才生效,表示匹配除了该字符组中的所有字符
  • -连字符:该字符可以用来表示一些连续序列的字符,如a-z0-9、大写A-Z
/[^ab]/.test('ab')  //false

/[^ab]/.test('c')   //true

上面的代码中,脱字符出现在字符组的第一个则表示匹配除了字符组中所有的字符,但是因为字符串中只包含了ab字符串,所以返回false,而第二个代表则匹配的字符c,而字符c没有包含在字符组中,所以返回匹配成功。

连字符可以简述字符组的一些连续序列字符

/[abcdefg]/
//等同
/[a-g]/

/[0123456789]/
//等同
/[0-9]

但是需要注意的是,连字符只会连接前后的一个字符,所以像下面这样只会表示为1-5,而不是1-51

/[1-51]/

除此之外,连字符还支持Unicode字符的范围,下面表示匹配码点在0128FFFF 之间的字符。

var str = "\u0130";
/[\u0128-\uFFFF]/.test(str) //true

匹配规则:预定义模式

预定义模式指的是Javascript实现的正则表达式里面已提前定义好的一些模式,分别有以下的一些预定义模式:

  • \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 匹配非词边界,即在词的内部。

其中除了\b\B有点不好理解之外,其他都比较好理解,\b用于放在字符的前或者后面,表示这个字符是否独立,比如all tall,那么我将匹配all是不是一个独立的单词,就可以将\b放在all的后面,就能够匹配到这个词的后面是否为边界,但是如果alltall这样,如果将\b放在后面,就不能匹配,因为all后面紧跟的是tall,反之\B是相反的概念

/all\b/.test('all tall')    //true
/all\b/.test('alltall') //false

/all\B/.test('alltall') //true

匹配规则:重复类

重复类{}可以定义一个字符出现重复的次数,重复类可以设定两个值,两个值用逗号,分隔,一个为最少重复数,一个为最多重复数,但不包括这个最多重复数。如果是单个值的话则是精确的指定重复的数量

/o{2}/.test('fool') //true
/o{3,5}/.test('fool')   //false
/o{3,5}/.test('foool')  //true

需要注意的是重复类只会定义一个字符串的重复数,如果是/oa{2}/,则表示的为重复a字符两次

匹配规则:量词符

量词符可以用来设定一个字符出现的次数,分别有三种量词符:

  • ? 问号表示某个模式出现0次或1次,等同于{0, 1}
  • * 星号表示某个模式出现0次或多次,等同于{0,}
  • + 加号表示某个模式出现1次或多次,等同于{1,}
/o?/.test('fool')   //true
//等同
/o{0,1}/.test('fool')   //true

/o*/.test('fool')   //true
//等同
/o{0,}/.test('fool')    //true

/o+/.test('fool')   //true
/o{1,}/.test('fool') //true

匹配规则:贪婪模式

贪婪模式指的是会一直匹配,匹配到没有匹配的为止,比如+量词符

var a = 'aaaa';

a.match(/a+/)
//["aaaa"]

上面的这样情况,称之为贪婪匹配,因为会一直匹配,直到没有满足匹配条件为止,反之也就是非贪婪模式。

如果要将贪婪模式改为非贪婪模式可以在类似的量词符号后面加?,则表示非贪婪模式,只会匹配到第一个匹配到的字符就停止匹配了。

var a = 'aaaa';

a.match(/a+?/)
//["a"]

匹配规则:修饰符

这三个修饰符分别表示:

  • 忽略大小写i:忽略大小写匹配
  • 全局搜索g:找到所有匹配,而不是在第一个匹配后停止
  • 多行m:将开始和结束字符(^和$)视为在多行上工作(也就是,分别匹配每一行的开始和结束(由 \n 或 \r 分割),而不只是只匹配整个输入字符串的最开始和最末尾处。

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

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

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

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

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

组匹配

组匹配()用于将多个字符包裹在()内,然后再去使用量词符等模式,就可以达到单词匹配的效果

/(ab)+/.test('abab')    //true
/ab+/.test('abab')  //true

上面的正则表达式相当于匹配ab两个字符至少一次,而第二个正则表达式则表示匹配b字符至少一次

捕获组

除了可以用来将多个字符进行量词匹配等,()内的内容也将会被单独匹配到,比如exec方法,返回的数组中,第一个为完整匹配到的字符串,第二个则为捕获组1,第三个则为捕获组2,依次类推

var a = /(a)b(c)/.exec('abc');

a   //["abc", "a", "c"]

a[0]    //'abc'
a[1]    //'a'
a[2]    //'c'

捕获组捕获的内容还可以在匹配模式中使用\1表示捕获组1,\2表示捕获组2,依次类推,而这个捕获组的顺序,则按照括号()的顺序来定义。

/(a)b(c)\1\2/.exec('abcac')
//["abcac", "a", "c"]

括号还可以嵌套匹配,而捕获组的顺序则由外到内,最外层的则是\1,第二层的为\2,依次类推

/((a)b)\1\2/.exec('ababa')
//(3) ["ababa", "ab", "a"]

非捕获组

(?:x)非捕获组与捕获组第一个用法相同,即可以用来包裹多个字符组成的字符串进行量词匹配,但是非捕获组不会返回捕获的结果,比如下面的代码中,第一个括号为非捕获组,所以结果中没有捕获第一个,而第二个为捕获组,所以捕获了第二个。

/(?:a)b(c)/.exec('abc')
//["abc", "c"]

先行断言

x(?=y)这样的模式为先行断言,即只有xy前面字符串才会被匹配,而先行断言不会捕获括号内的内容,也只会返回x,即x后面跟着y则匹配

/a(?=b)/.exec('abc')

先行否定断言

x(?!y)称为先行否定断言,即只有x不在y前面字符串才会被匹配,而先行否定断言不会捕获括号内的内容,也只会返回x,即x后面没有y则匹配

'abd'.match(/b(?!c)/)
["b", index: 1]

参考链接

《RegExp对象》 – 阮一峰

《RegExp》– MDN

导论

console是Javascript的原声对象,该对象类似于output输出功能,该对象常见的用处是有两个:

  • 调试程序,显示网页中运行时的错误,以及进行调试输出
  • 提供了一个命令行接口,用来与网页进行互动

浏览器对于console对象的实现是包含在开发者工具当中的,以Chrome浏览器来介绍,打开开发者工具可以用:

  1. 按 F12 或者Control + Shift + i(PC)/ Alt + Command + i(Mac)。
  2. 浏览器菜单选择“工具/开发者工具”。
  3. 在一个页面元素上,打开右键菜单,选择其中的“Inspect Element”。

打开开发者工具以后,顶端有多个面板。

  • Elements:查看网页的 HTML 源码和 CSS 代码。
  • Resources:查看网页加载的各种资源文件(比如代码文件、字体文件 CSS 文件等),以及在硬盘上创建的各种内容(比如本地缓存、Cookie、Local Storage等)。
  • Network:查看网页的 HTTP 通信情况。
  • Sources:查看网页加载的脚本源码。
  • Timeline:查看各种网页行为随时间变化的情况。
  • Performance:查看网页的性能情况,比如 CPU 和内存消耗。
  • Console:用来运行 JavaScript 命令。

Console面板就是浏览器针对于console对象的实现,Console面板基本上就是一个命令行,而通过输入一些命令就可以与Console面板互动,并且console对象的所有方法都是可以自定义的,也就是所有的方法都是可以根据自己的需要来自定义行为。

静态方法

log、info、debug

log方法用于将值输出到控制台,该方法接受多个参数,会将多个参数拼接起来,并且每个拼接的参数后面会有一个空格,而最后一个参数后面会自动加上换行符\n

console.log('a', 'b', 'c', 'd', 'e')
//a b c d e

但是该方法第一个参数中的字符串包含有格式占位符,那么从第二个参数开始,则是该格式占位符的替换值,log有以下几种占位符,而不同类型的数据必须使用对应的格式占位符:

  • %s 字符串
  • %d 整数
  • %i 整数
  • %f 浮点数
  • %o 对象的链接
  • %c CSS 格式字符串
console.log('%sqwe', 'aa-')
//aa-qwe

console.log('%fqwe', 2.23)
//2.23qwe

%o的对象链接可以将对象和字符串一起拼接在一行进行输出,但是该方法和普通的拼接输出结果相等

console.log('%o-This is an Object', {a: 1})
//{a: 1}-This is an Object

console.log({a: 1} ,'This is an Object')
//{a: 1} "This is an Object"

%c可以添加Css代码,该Css代码渲染格式占位符所在的参数字符串中生效,并且从格式占位符开始的位置后面的字符才会进行渲染

console.log(
    'abc-%c123',
    'color: blue; font-size:18px'
)

如果参数是一个对象,console.log会显示该对象的值。

console.log({foo: 'bar'})
// Object {foo: "bar"}
console.log(Date)
// function Date() { [native code] }

但是需要注意的是当格式占位符替换完毕后,就会将后面的参数继续拼接字符串

console.log(
    '%oabc-',
    {a: 1},
    '123'
)
//{a: 1}abc- 123

console.infoconsole.log方法的别名,用法完全一样。只不过console.info方法会在输出信息的前面,加上一个蓝色图标,对于Chrome的70.0.3538.110版本中是无法看出区别的,而Safari的12.0.1 (14606.2.104.1.1)是能够明显看出蓝色的图标

console.debug方法与console.log方法类似,会在控制台输出调试信息。但是,默认情况下,console.debug输出的信息不会显示,只有在打开显示级别在verbose的情况下,才会显示。

而在上面的两个浏览器对应版本的时候,其中Safari浏览器是能够打印出来的,并且显示的是一个蓝色的箭头图标,而Chrome是需要在打开显示级别为verbose情况下才能显示。

warn、error

warnerror方法和log以及debug性质一样,都是用于输出信息,warn输出的时候图标为一个三角形的感叹号图标,用于表示警告作用,而error输出的时候图标为一个圆圈的错误图标,并且输出的行为红色底色,表示错误信息。

console.warn(1)
console.error(1)

table

table方法用于将对象类型的数据以表格的信息输出,如果输出的是基本值,将输出为空格,下面是输出数组和对象两种对象类型的结果

console.table({
    a: 1,
    b: 2
});
(index) Value
a 1
b 2
console.table([1, 2])
(index) Value
0 1
1 2

count

count方法用于计数该方法被调用了多少次,可以将该方法放置在函数中,或者循环的中,来显示次数

function a () {
    console.count();
}

a()
//default: 1
a()
//default: 2
a()
//default: 3

count方法还可以接受一个参数,该参数可以当作count方法的分组统计标签

function a (str) {
    console.count(str);
}

function b (str) {
    console.count(str);
}

a('a')
// a: 1
a('a')
// a: 2
b('b')
// b: 1
b('b')
// b: 2

dir

dir方法主要用于将对象类型的值以更加便于理解的方式打印出来

console.log({
    a: 1,
    b: 2
})
//{a: 1, b: 2}


console.dir({
    a: 1,
    b: 2
})
//Object
//  a: 1
//  b: 2
//  __proto__: Object

assert

assert方法主要用于对表达式进行判断,该方法接受两个参数,第一个参数为表达式,第二个参数为当一个表达式输出为false的时候输出的错误信息,该信息输出以红色底色,红字、图标为红圈的错误信息。

console.assert(false, '错误信息')
//Assertion failed: 错误信息

time、timeEnd

timetimeEnd两个方法用于计算一个操作所消耗的时间,一般来说是成对使用的,两个方法都接受一个字符串,该字符串为计数器的名称,而最终timeEnd方法会输出计数器所计算的时间

console.time('test')

for (var i = 0; i < 10000; i++) {}

console.timeEnd('test')
//test: 0.4521484375ms

group、groupEnd、groupCollapsed

groupgroupEnd用于分级来展示内容,只有在数据复杂,数据不容易阅读的时候,这两个方法非常有用,其中第一个group代表的最外层分组开始,遇见的最后一个groupEnd表示最外层分组结束,如果中间有定义多个group则按照上面的规则。

输出的每个组可以用鼠标点击折叠按钮进行折叠该组下面的内容

console.group('外层分组')
console.log('外层分组数据1')
console.log('外层分组数据2')

console.group('第二层分组')
console.log('第二层分组数据1')
console.log('第二层报错数据1')

console.groupEnd()  //第二层分组结束
console.groupEnd()  //外层分组结束

groupCollapsed方法与group方法基本一致,都是可以用来定义分组,但是groupCollapsed方法默认是折叠的,而group方法默认展开

console.group('外层分组')
console.log('外层分组数据1')
console.log('外层分组数据2')

console.groupCollapsed('第二层分组')
console.log('第二层分组数据1')
console.log('第二层报错数据1')

console.groupEnd()  //第二层分组结束
console.groupEnd()  //外层分组结束

trace

trace方法支持输出堆栈跟踪; 这将显示到达呼叫点的呼叫路径console.trace(),这个方法几乎不会使用

function foo() {
  function bar() {
    console.trace();
  }
  bar();
}

foo();
//
console.trace
bar @ VM170:3
foo @ VM170:5
(anonymous) @ VM170:8

clear

clear方法用于将控制台的所有输出全部清空,并将光标置回第一行,如果用户选中了控制台的Preserve log选项,clear方法将不起作用。

控制台命令行API

浏览器的控制台中,还包含了一些除了console对象之外的属性和方法

$_

$_属性用于放回在命令行执行的上一个表达式的值

1 + 1   //2
$_  //2

$0-4

$0$1$2$3$4五个属性分别从0开始记录了5次在Elements面板中选中的DOM元素,$0代表最近选择的一个DOM元素,后面的依次记录

$

$方法返回第一个匹配的DOM元素,等同于document.querySelector方法,需要注意的是JQuery库也是针对于$定义的,所以页面如果载入了JQuery库,那么这个方法将会被覆盖

$

$$方法会返回所有匹配的DOM元素,等同于document.querySelectorAll方法

$x

$x方法用于XPath表达式,将匹配到XPath表达式的所有元素返回一个数组,比如下面返回所有包含a元素的p元素

$x('//p[a]')

inspect

inspect方法可以将一个获取到的DOM元素定位在Elements面中选中

inspect(document.body)

getEventListeners

getEventListeners方法用于返回一个DOM元素登记的事件以及回调函数,该方法方法返回一个对象,对象中每个事件为一个属性,而每个事件绑定的函数为数组成员

getEventListener(document)
//{DOMContentLoaded: Array(2), readystatechange: Array(1), visibilitychange: Array(1)}

keys、values

keysvalues两个方法分别返回一个对象的所有键名和所有值,并将每个键名和值以数组元素形式存放并返回

keys({a: 1, b: 2})
// ["a", "b"]

values({a: 1, b: 2})
// [1, 2]

monitorEvents、unmonitorEvents

monitorEventsunmonitorEvents两个方法都是用于监听一个对象的事件,其中monitorEvents用于监听一个对象一个或多个事件,当监听的事件发生时,则返回一个Event对象。

monitorEvents(window, 'resize')
//当缩放窗口的时候就会返回下列的Event对象
//resize Event {isTrusted: true, type: "resize", target: Window, currentTarget: Window, eventPhase: 2, …}

unmonitorEvents方法则是停止监听对象

unmonitorEvents(window, 'resize')

monitorEvents允许监听同一大类的事件。所有事件可以分成四个大类。

  • mouse:”mousedown”, “mouseup”, “click”, “dblclick”, “mousemove”, “mouseover”, “mouseout”, “mousewheel”
  • key:”keydown”, “keyup”, “keypress”, “textInput”
  • touch:”touchstart”, “touchmove”, “touchend”, “touchcancel”
  • control:”resize”, “scroll”, “zoom”, “focus”, “blur”, “select”, “change”, “submit”, “reset”

copy

copy方法可以将一个DOM元素的所有内容,包括注释,都可以复制到剪贴板

copy(document.body)

debugger

debugger语句主要用于来设置断点进行排出错误,如果当放置了debugger语句的话,将在放置的位置暂停执行,然后跳到源码处,等待处理

for(var i = 0; i < 5; i++){
  console.log(i);
  if (i === 2) debugger;
}

参考链接

《console对象与控制台》 – 阮一峰

console》- MDN

事件类型》- MDN

导论

Javascript基本类型的中最为主要的布尔值、字符串、数值三种类型,这三种类型会在某些时候自动转换为对象,而将基本类型转换为对象的函数为称为包装对象,它们分别为BooleanNumberString包装对象。

这三个包装对象也是一个函数,用于将值转换为相应的类型的值,而具体的转换规则可以查看数据类型和类型转换的章节。

Number('123')   // 123
Boolean(null)   //false
String(123)     //"123"

而这三个包装对象如果在前面加上new操作符,就可以将一个基本值包装为一个包装对象

typeof new Number(123)  //"object"
typeof new Boolean(null)    //"object"
typeof new String(123)  //"object"

包装对象本质还是对象,但是包装对象最大的目的在于可以在基本值上调用属性方法,还可以定义一些属性和方法。

原始值与包装对象的自动转换

原始值是没有任何的方法和属性的,因为只有对象才能属性和方法一说,但是所看到的JavaScript代码中可能会存在下面这样的代码

var a = '123';
a.length    //3

上面的代码实际上是JavaScript引擎自动的对原始值进行了转换为包装对象,所以能够调用length属性,但是当我们调用了过后,这个包装对象也就自动销毁了,那么上面的代码实际上是这样的。

var a = '123';
String(a).length    //3

因为自动转换过后会进行销毁,所以给原始值进行添加属性是不会报错的,也会返回赋的值,但是因为自动转换过后会销毁,所以你添加属性的临时包装对象已经销毁了,当你再次调用这个属性的时候,实际上是在一个新的包装对象上面调用这个属性,所以会返回undefined

var a = '123';
a.x = 123;  //123
a.x //undefined

共有实例方法

这三个包装对象都有两个共有方法,分别为valueOftoString,其中的valueOf方法返回这个包装对象的原始值

(new Number(123)).valueOf() //123
(new Boolean(true)).valueOf()   //true
(new String('123')).valueOf()   //"123"

toString方法则是返回对应的字符串形式

(new Number(123)).toString()    //"123"
(new Boolean(true)).toString()  //"true"
(new String('123')).toString()  //"123"

Boolean对象

Boolean对象,也是构造函数,其主要用于将原始值转换为包装对象

typeof (new Boolean(true))  //"object"

这里需要注意的是包装对象转换过后的类型为对象,那么根据逻辑运算的规则,所有的对象将会被转换为true,那么如果包装一个原始值为false的值,可能最终得到的结果为true

var a = new Boolean(false);

if (a) {
    console.log('a为true')
}
//a为true

如果Boolean对象前面不加new操作符,那么Boolean就是一个转换函数,可以将任意值转换为Boolean原始值类型

Boolean('123')  //true
Boolean({}) //true
Boolean(null)   //false

Number对象

Number对象是数值的包装对象,该对象可以当构造函数使用,即创建一个Number对象类型的值

var a = Number(123);

typeof a    //"object"

Number对象也可以当做工具函数使用

Number('123')   //123

基本值调用方法的时候需要注意的时候避免直接调用,尽量用括号包裹数值,或者用方括号运算符,因为如果开头为数值,那么JavaScript引擎将默认认为这是一个小数点,而不是调用,则会报错

5.toString()
//Uncaught SyntaxError: Invalid or unexpected token

(5).toString()  //"5"
5['toString']() //"5"

静态属性

Number对象有一些静态的属性,这些属性都和数值有关

  • Number.POSITIVE_INFINITY:正的无限,指向Infinity
  • Number.NEGATIVE_INFINITY:负的无限,指向-Infinity
  • Number.NaN:表示非数值,指向NaN
  • Number.MIN_VALUE:表示最小的正数(即最接近0的正数,在64位浮点数体系中为5e-324),相应的,最接近0的负数为-Number.MIN_VALUE
  • Number.MAX_SAFE_INTEGER:表示能够精确表示的最大整数,即9007199254740991
  • Number.MIN_SAFE_INTEGER:表示能够精确表示的最小整数,即-9007199254740991

实例方法

toString

Number对象的实例方法toString是单独部署的,这个方法接受一个参数,这个参数表示的是转换的进制,默认为十进制,并返回一个转换后的字符串

(2).toString()  //"2" 转换为十进制
(2).toString(2) //"10" 转换为2进制
(2).toString(8) //"2"   转换为8进制

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

toFixed

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

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

上面代码中,1010.005转成2位小数,其中10必须放在括号里,否则后面的点会被处理成小数点,并取四舍五入。

toFixed方法的参数为小数位数,有效范围为0到20,超出这个范围将抛出 RangeError错误。

toExponential

toExponential为实例方法,该方法用于将一个数转换为科学计数法形式,并返回一个转换后的字符串

(10).toExponential()  // "1e+1"
(10).toExponential(1) // "1.0e+1"
(10).toExponential(2) // "1.00e+1"

(1234).toExponential()  // "1.234e+3"
(1234).toExponential(1) // "1.2e+3"
(1234).toExponential(2) // "1.23e+3"

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

toPrecision

toPrecision方法用于将一个数转为指定位数的有效数字,如果大于数值的位数,则用小数为0填充,并返回一个转换后的字符串。

(1).toPrecision(3)  //1.00

toPrecision方法的参数为有效数字的位数,范围是1到21,超出这个范围会抛出 RangeError 错误。

toPrecision方法用于四舍五入时不太可靠,跟浮点数不是精确储存有关。

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

String对象

String对象是字符串的包装对象,该对象可以当构造函数使用,即创建一个String对象类型的值

typeof new String('123')    //"object"

String对象也可以当做工具函数使用

String(123) //"123"

字符串是一个类数组结构,但不是真正的数组,因为字符串有length属性,以及可以使用方括号运算符

'123'[0]    //"1"
'123'.length    //3

静态方法

fromCharCode静态方法用于接收多个参数,这些参数为Unicode的码点,然后返回这些码点对应的字符组成的字符串

String.fromCharCode(0x4f60,0x597d)
//"你好"

如果该方法没有参数则返回一个空的字符串

String.fromCharCode()   //""

JavaScript默认支持两个字节的字符,所以如果大于0xFFFF的字符,将会舍弃多余位,比如0x20BB7对应的字符为𠮷,并且大于oxFFFF,但是返回的确不是这个字符串

String.fromCharCode(0x20BB7) //"ஷ"

造成上面的原因是因为该JavaScript抛弃多余位后,实际上转换的码点为0x0BB7

String.fromCharCode(0x20BB7) === String.fromCharCode(0x0BB7)    //true

如果需要表示这样的方法,则可以用由 UTF-16 编码方法,将这个字符拆成两个字符的码点,然后组合

String.fromCharCode(0xD842, 0xDFB7) //"𠮷"

实例属性

length属性为实例属性,该属性主要返回字符串的长度

'123'.length    //3

但是因为JavaScript默认支持两个字节的字符,所以遇见大于0xFFFF的字符都返回为两个长度

"𠮷".length  //2

实例方法

charAt

该方法用于返回指定索引位置的字符

'123'.charAt(0) //"1"

该方法如果为负数或者大于字符串长度,则返回空字符串,该方法完全可以用方括号运算符来替代

'123'[0]    //"1"

charCodeAt

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

'abc'.charCodeAt(1) // 98

上面代码中,abc1号位置的字符是b,它的 Unicode 码点是98

如果没有任何参数,charCodeAt返回首字符的 Unicode 码点。

'abc'.charCodeAt() // 97

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

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

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

"𠮷".charCodeAt(0)   //55362
"𠮷".charCodeAt(1)   //57271

concat

concat方法用于拼接两个字符串,并返回一个新的拼接字符串

'a'.concat('b') //"ab"

如果拼接的不是字符串,那么将会按照String的转换规则将其转换为字符串

'a'.concat({})  //"a[object Object]"

slice

slice方法用于提取指定范围的字符串,并将其组成新字符串返回,该方法接受两个参数,第一个参数为开始位置,第二个参数为截止位置,但不包含截止位置的值,如果该方法没有传入参数,则返回一个一样的字符串。

'0123456'.slice(1, 5)   //"1234"

如果没有第二个参数,则从其开始位置一直到字符串结束

'0123456'.slice(1)  //"123456"

如果是负数,则代表从倒数开始,即负数加上字符串长度

'0123456'.slice(-3) //"456"

如果第一个参数大于第二个参数,则返回一个空字符串

'0123456'.slice(3, 1)   //""

substring

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

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

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

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

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

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

上面代码中,调换substring方法的两个参数,都得到同样的结果。

如果参数是负数,substring方法会自动将负数转为0。

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

上面代码中,第二个例子的参数-3会自动变成0,等同于'JavaScript'.substring(4, 0)。由于第二个参数小于第一个参数,会自动互换位置,所以返回Java

由于这些规则违反直觉,因此不建议使用substring方法,应该优先使用slice

substr

substr方法用于从原字符串取出子字符串并返回,不改变原字符串,跟slicesubstring方法的作用相同。

substr方法的第一个参数是子字符串的开始位置(从0开始计算),第二个参数是子字符串的长度。

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

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

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

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

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

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

indexOf和lastIndexOf

indexOflastIndexOf都是用于查找字符第一次出现在字符串中的位置,他们唯一不同的在于,indexOf是从正序查找,而lastIndexOf则是倒序查找,这两个方法返回一个值,如果为-1则表示没有查找到,如果是其他值则为该字符在字符串中的索引值。

他们都接受两个参数,一个为查找的字符,第二个为开始查找的位置,如果是indexOf,则会从指定开始位置往后查找,如果是lastIndexOf则会从指定开始位置往前查找。

'1234'.indexOf('2', 1)  //1
'1234'.indexOf('2', 2)  //-1 从3开始,所以没有找到

'1234'.lastIndexOf('3', 2)  //2
'1234'.lastIndexOf('3', 1)  //-1 从2开始往前找,前面只有1,所以返回-1

trim

trim方法用于去除字符串两端的空格、制表符(\t\v)、换行符(\n)和回车符(\r) ,并返回一个新的字符串。

var a = '    asdasd asdasdasd   \t'
//"asdasd asdasdasd"

toLowerCase 和 toUpperCase

两个方法用于将字符串中的英文字符转换为大小写,其中toLowerCase转换为小写,toUpperCase转换为大写,如果不是英文字母则不转换,两个方法都返回一个新的字符串

'aBcD-123'.toLowerCase()    //"abcd-123"
'aBcD-123'.toUpperCase()    //"ABCD-123"

match

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

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

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

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

match方法还可以使用正则表达式作为参数,详细的内容见Regexp章节。

search 和 replace

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

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

search方法还可以使用正则表达式作为参数,详见《正则表达式》一节。

replace方法用于替换匹配的子字符串,一般情况下只替换第一个匹配(除非使用带有g修饰符的正则表达式)。

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

replace方法还可以使用正则表达式作为参数,详细的内容见Regexp章节。

split

split方法用于切割字符串,并按照指定的规则,该方法会返回一个数组,每个切割的值作为元素,该方法的第一个值为指定的切割符号,而第二个值为返回的数组最大长度

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

如果是切割的符号后面没有跟着值则会用空字符来替代

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

如果设定了第二个值,则会限制返回数组的最大长度

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

localeCompare

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

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

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

'B' > 'a' // false

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

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

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

上面代码中,localeCompare方法返回整数1,表示B较大。

localeCompare还可以有第二个参数,指定所使用的语言(默认是英语),然后根据该语言的规则进行比较。

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

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

参考链接

《包装对象》 – 阮一峰

《Boolean对象》 – 阮一峰

《Number对象》 – 阮一峰

《String对象》 – 阮一峰

String》- MDN

Number》- MDN

Boolean》- MDN

导论

Javascript中的每个对象中的每个属性都包含了一个内部结构的属性描述对象,用于控制属性的行为,如是否可以枚举、是否可以修改等,该对象同样继承Object对象。

属性描述对象一共有6个属性值,分别表示的内容也是不同的:

  • valuevalue是该属性的属性值,默认为undefined

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

  • enumerableenumerable是一个布尔值,表示该属性是否可枚举,默认为true

  • configurableconfigurable是一个布尔值,表示属性的描述对象可配置性,默认为true

  • getget是一个函数,表示该属性的取值函数(getter),默认为undefined

  • setset是一个函数,表示该属性的存值函数(setter),默认为undefined

属性描述对象相关方法

Object.getOwnPropertyDescriptor()

Object.getOwnPropertyDescriptor()方法用于返回对象属性的描述对象,该方法接受两个参数,一个为对象,一个为属性名对应的字符串,返回一个描述对象,比如下面的代码中,用这个方法查看对象a中的a属性的描述对象。

var a = { a: 1 };

Object.getOwnPropertyDescriptor(a, 'a')
//{value: 1, writable: true, enumerable: true, configurable: true}

Object.getOwnPropertyDescriptor()方法只能用于本身的静态属性,如果应用到继承属性,会返回undefined

var a = {};
Object.getOwnPropertyDescriptor(a, 'toString')
//undefined

Object.defineProperty(),Object.defineProperties()

Object.defineProperty()用于更新一个属性的描述对象,并返回该对象,该方法接受三个参数,分别对应为:

  • 修改属性的对象
  • 属性名对应的字符串
  • 要修改的描述对象

比如下面的代码修改了对象a中的属性b的描述对象,并为该属性设置了一个新值,并返回该对象

var a = { b: 1 };
Object.defineProperty(a, 'b', {
    value: 2222
})
// {b: 2222}

a // {b: 2222}

Object.defineProperties()方法用于一次性更新多个属性的描述对象,并返回该对象,该对象接受两个参数,分别对应为:

  • 修改属性的对象
  • 需要修改的描述对象的多个属性

比如下面的代码,修改了对象a中的属性nameprint的描述对象,并设置了一个新值

var a = {
    name: 'js',
    print: 'javascript'
};

Object.defineProperties(a, {
    name: { value: 'js2018' },
    print: { value: 'javascript2018' }
})
//{name: "js2018", print: "javascript2018"}

a // {name: "js2018", print: "javascript2018"}

Object.prototype.propertyIsEnumerable()

Object.prototype.propertyIsEnumerable()方法是一个实例方法,也就是在对象本身上调用,该方法接受一个参数,该参数为对应属性名的字符串,用于检查对象本身的静态属性是否可以枚举,并返回一个布尔值,如果可以就会返回true,否则返回false,该方法对于继承属性一律返回false

var a = {
    name: 'js',
    print: 'javascript'
};

a.propertyIsEnumerable('name') //true
a.propertyIsEnumerable('toString') //false

描述属性

value

value属性是对应属性的值,可以通过value属性来设置一个属性的值,是等同于直接给该属性设置值,如果该值不存在对象中,那么会添加一个新的属性到该对象,并返回

var a = { b: 1 };

Object.defineProperty(a, 'b', {value: 2})

a // {b: 2}

Object.defineProperty(a, 'p', {value: 3})

a // {b: 2, p: 3}

writable

writable属性是一个布尔值,决定了目标属性是否可以被改变,如果为true表示可以改变,如果为false表示不可改变。

一旦设置了一个对象中的这个属性不可变,那么新对象继承这个对象,那么这个新对象也不可对属性进行改变。

var a = { b: 2 };
Object.defineProperty(a, 'b', {
    writable: false
})

a.b = 3;

a   //{b: 2}

普通模式下,对不可改变的属性进行改变会默认失败,但不会报错,但是如果在严格模式下,就会报错

'use strict'

Object.defineProperty(a, 'b', {
    writable: false
})

a.b = 3;
// VM1016:7 Uncaught TypeError: Cannot assign to read only property 'b' of object '#<Object>'

如果需要修改该属性,有两种方式,一种是直接可以重新修改这个属性的描述对象的writable属性为true之后就可以修改该属性的值

var a = { b: 2 };
Object.defineProperty(a, 'b', {
    writable: true
})

a.b = 5;

a   //{b: 5}

还有一种方式是直接可以通过描述对象中的value属性来改变这个属性的值

var a = { b: 2 };
Object.defineProperty(a, 'b', {
    value: 3
})

a   // {b: 3}

enumerable

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

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

var obj = {};
'toString' in obj // true

上面代码中,toString不是obj对象自身的属性,但是in运算符也返回true,这导致了toString属性也会被for...in循环遍历。

这显然不太合理,后来就引入了“可遍历性”这个概念。只有可遍历的属性,才会被for...in循环遍历,同时还规定toString这一类实例对象继承的原生属性,都是不可遍历的,这样就保证了for...in循环的可用性。

具体来说,如果一个属性的enumerablefalse,下面三个操作不会取到该属性。

  • for..in:会包含继承属性
  • Object.keys:不包含继承属性
  • JSON.stringify:输出为JSON格式,但是会排出不可枚举的属性

因此,enumerable可以用来设置“秘密”属性。

var a = {};
Object.defineProperty(a, 'p', {
    value: 111,
    enumerable: false
})

a   //{p: 111}

for (var item in a ){
    console.log(item)
}
// undefined

如果需要获取所有的对象属性,不管是否可不可以枚举,可以使用Object.getOwnPropertyNames方法

var a = {};
Object.defineProperty(a, 'p', {
    value: 111,
    enumerable: false
})

Object.getOwnPropertyNames(a) //["p"]

configurable

configurable属性返回一个布尔值,该布尔值为true的时候表示可以改变该属性的描述对象,如果为false的时候,那么将无法对描述对象进行修改,如果修改描述对象将会得到错误,但是如果通过赋值的方式给属性赋值,不会得到错误,但也不会赋值成功。

var a = {};
Object.defineProperty(a, 'p', {
    value: 1,
    configurable: false
})

Object.defineProperty(a, 'p', {
    value: 2
})

//VM1338:7 Uncaught TypeError: Cannot redefine property: p

Object.defineProperty(a, 'p', {
    writable: true
})
//VM1341:1 Uncaught TypeError: Cannot redefine property: p

Object.defineProperty(a, 'p', {
    enumerable: true
})
// VM1344:1 Uncaught TypeError: Cannot redefine property: p

Object.defineProperty(a, 'p', {
    configurable: true
})
// VM1347:1 Uncaught TypeError: Cannot redefine property: p

但是可以将的描述对象的writableenumerableconfigurable属性修改为false这不会得到错误

var a = {};
Object.defineProperty(a, 'p', {
    value: 1,
    configurable: false
})

Object.defineProperty(a, 'p', {
    writable: false
})
//{p: 1}

Object.defineProperty(a, 'p', {
    enumerable: false
})
// {p: 1}

Object.defineProperty(a, 'p', {
    configurable: false
})
//{p: 1}

而对于描述对象的value属性,只要configurablewritable其中一个为true就是可以修改的

var a = {};
Object.defineProperty(a, 'p', {
    value: 1,
    configurable: false,
    writable: true
})

a //{p: 1}

Object.defineProperty(a, 'p', {
    value: 2
})

a //{p: 2}

//-------------------------------
var a = {};
Object.defineProperty(a, 'p', {
    value: 1,
    configurable: true,
    writable: false
})

a //{p: 1}

Object.defineProperty(a, 'p', {
    value: 5
})

a // {p:5}

除此之外,该描述属性一旦设置,将无法对该属性删除,但是删除也不会报错,会返回false

var a = {};
Object.defineProperty(a, 'p', {
    value: 1,
    configurable: false
})


delete a.p //false

get和set

描述属性除了上面的属性之外,还有存取器属性,这两个属性分别对应一个函数,存值函数称为setter并用描述对象中的set属性设置,取值函数称为getter并用描述对象中的get属性设置。

这两个属性的作用用于覆盖原生的存取行为,比如下面的代码,定义了getset,那么我们就可以直接读取该属性的时候怎么返回,对该属性赋值的时候该怎么处理,比如利用的set属性,可以实现让该属性无法赋值

var obj = {
    a: 'b'
};

Object.defineProperty(obj, 'a', {
    get: function () {
        console.log('this is getter return');
    },
    set: function (value) {
        console.log('input value is' + value);
    }
});


obj.a   //this is getter return
obj.a = 3;  //input value is 3

上面的代码中,get默认不接受参数,而set则会接受一个参数,即赋值的时候的值。

除此之外,Javascript还提供了setget的另外一种写法

var obj = {
    get p() {
        return 1;
    },
    set p(value) {
        return value;
    }
}

obj.p   // 1
obj.p = 2;  //2

如果要对一个对象进行拷贝,那么遇见上面这样的情况设置了get就会造成一定问题,比如拷贝的值可能是get返回的值这样的情况

var extend = function (to, from) {
    for (var item in from) {
        to[item] = from[item]
    }
    return to;
}

var to = extend({}, {
    get p() {
        return 1;
    }
});


to  //{p: 1}

解决这个问题,所以要拷贝这个属性,而不只拷贝这个属性返回的值,所以连同描述对象也需要一同拷贝,那么使用Object.definePropertyObject.getOwnPropertyDescriptor以及Object.hasOwnProperty来进行拷贝。

其中的Object.defineProperty用来设置新的对象的描述对象,Object.getOwnPropertyDescriptor来获取需要拷贝属性的描述对象,Object.hasOwnProperty用于检查是否为本身的属性,因为Object.getOwnPropertyDescriptor只能检查本身的属性,如果不是本身的属于,如继承属性,就会返回undefine,然后传递给Object.hasOwnProperty就会报错

var extend = function (to, from) {
    for (var item in from) {
        if ( !from.hasOwnProperty(item) ) continue;
        Object.defineProperty(to, item, Object.getOwnPropertyDescriptor(from, item));
    }
    return to;
}

var to = extend({}, {
    get p() {
        return 1;
    }
});

to.p    // 1
to.p = 2    //2

对象冻结

Javascript提供了三种方法来冻结对象的读写,分别为:

  • Object.preventExtensions:初级冻结,对象无法添加属性,包括无法通过赋值以及描述对象来添加
  • Object.seal:中级冻结,对象无法添加属性,也无法删除属性,包括无法通过赋值以及描述对象来添加
  • Object.freeze:强级冻结,对象无法添加、删除、修改属性,包括无法通过赋值以及描述对象来添加

Object.preventExtensions

Object.preventExtensions方法可以冻结一个对象无法添加属性

var obj = {
    a: 1
};

Object.preventExtensions(obj)

obj.p = 123;    //添加新属性并赋值,不会报错,返回赋值的值

obj.a = 2;  // 但是可以赋值

delete obj.a    // true 可以删除属性 

Object.defineProperty(obj, 'p', {
    value: 123
});
// VM301:1 Uncaught TypeError: Cannot define property p, object is not extensible
// 报错

如果要检查一个对象是否设置Object.preventExtensions冻结,可以使用Object.isExtensible方法来检查,该方法返回true表示可以操作属性,如果返回false表示该对象不能够添加属性

var obj = {};
Object.isExtensible(obj);   //true
Object.preventExtensions(obj);
Object.isExtensible(obj);   //false

Object.seal

Object.seal方法可以冻结一个对象无法添加属性,也无法删除属性

var obj = {
    a: 1
};

Object.seal(obj);

obj.p = 123;    //添加新属性并赋值,不会报错,返回赋值的值

obj.a = 2;  // 但是可以赋值

delete obj.a    // false 无法删除属性 

如果要检查一个对象是否设置Object.seal冻结,可以使用Object.isSealed方法来检查,该方法返回true表示可以操作属性,如果返回false表示该对象不能够添加、删除属性。

var obj = {};
Object.isSealed(obj);   //true
Object.seal(obj);
Object.isSealed(obj);   //false

Object.freeze

Object.freeze方法可以冻结一个对象,对象将无法添加、修改、删除属性

var obj = {
    a: 1
};

Object.freeze(obj);

obj.p = 123;    //添加新属性并赋值,不会报错,返回赋值的值,但添加失败

obj.a = 2;  // 为属性赋值,不会报错,返回赋值的值,但赋值失败

delete obj.a    // false 无法删除属性 

如果要检查一个对象是否设置Object.freeze冻结,可以使用Object.isFrozen方法来检查,该方法返回true表示可以操作属性,如果返回false表示该对象不能够添加、删除、修改属性。

var obj = {};
Object.isFrozen(obj);   //true
Object.freeze(obj);
Object.isFrozen(obj);   //false

冻结对象的局限性

冻结对象是有局限性的,都知道Javascript是基于原型链的,即一个对象如果继承另外一个对象,那么继承的对象中调用一个本身没有的属性,那么Javascript引擎会继续查询继承对象的父对象的prototype属性上定义的实例方法。

那么直接在父对象上定义一个实例方法,继承对象一样可以调用使用

var obj = new Object();
Object.preventExtensions(obj);

var proto = Object.getPrototypeOf(obj);
proto.t = 'hello';
obj.t
// hello

针对这样的办法是冻结继承对象的父对象

var obj = new Object();
Object.preventExtensions(obj);

var proto = Object.getPrototypeOf(obj);
Object.preventExtensions(proto);

proto.t = 'hello';
obj.t // undefined

还有一种漏洞就是,如果冻结对象的属性是一个对象,那么这个属性指向对象的值是冻结的,但是指向的这个对象是没有冻结的

var obj = {
    a: {}
};
Object.preventExtensions(obj);

obj.a.p = 123;  
obj.a   //{p: 123}

参考链接

《属性描述对象》 – 阮一峰

Object》- MDN

Leave a Reply

Your email address will not be published. Required fields are marked *