一、标准库
1. Object 对象
- Object对象的原生方法分成两类
- Object本身的方法
- Object的实例方法
// Object对象本身的方法Object.print = function (o) { console.log(o) };// Object的实例方法Object.prototype.print = function () {console.log(this);};var obj = new Object();obj.print() // Object
Object()
- Object本身是一个函数,可以当作工具方法使用,将任意值转为对象
- 这个方法常用于保证某个值一定是对象
var obj = Object();// 等同于var obj = Object(undefined);var obj = Object(null);obj instanceof Object // true// instanceof运算符用来验证,一个对象是否为指定的构造函数的实例// 参数为各种原始类型的值var obj = Object(1);obj instanceof Object // trueobj instanceof Number // truevar obj = Object('foo');obj instanceof Object // trueobj instanceof String // truevar obj = Object(true);obj instanceof Object // trueobj instanceof Boolean // true// 参数为一个对象var arr = [];var obj = Object(arr); // 返回原数组obj === arr // truevar value = {};var obj = Object(value) // 返回原对象obj === value // truevar fn = function () {};var obj = Object(fn); // 返回原函数obj === fn // true// 写一个判断变量是否为对象的函数function isObject(value) {return value === Object(value);}isObject([]) // trueisObject(true) // false
Object 构造函数
- Object不仅可以当作工具函数使用,还可以当作构造函数使用
- 即前面可以使用new命令
var obj = new Object();// var obj = new Object()的写法与 var obj = {}是等价的var o1 = {a: 1};var o2 = new Object(o1);o1 === o2 // truevar obj = new Object(123);obj instanceof Number // true// Object(value)与new Object(value)两者的语义是不同的// Object(value)表示将value转成一个对象// new Object(value)则表示新生成一个对象,它的值是value
Object.keys(),Object.getOwnPropertyNames()
var obj = {p1: 123,p2: 456};Object.keys(obj) // ["p1", "p2"]var obj = {p1: 123,p2: 456};Object.getOwnPropertyNames(obj) // ["p1", "p2"]// 对于一般的对象来说,Object.keys()和Object.getOwnPropertyNames()返回的结果是一样的// 只有涉及不可枚举属性时,才会有不一样的结果var a = ['Hello', 'World'];Object.keys(a) // ["0", "1"]Object.getOwnPropertyNames(a) // ["0", "1", "length"]// 数组的length属性是不可枚举的属性// 由于 JavaScript 没有提供计算对象属性个数的方法,所以可以用这两个方法代替var obj = {p1: 123,p2: 456};Object.keys(obj).length // 2Object.getOwnPropertyNames(obj).length // 2// 一般情况下,几乎总是使用Object.keys方法,遍历对象的属性
Object 本身的其他方法
- 对象属性模型的相关方法
- Object.getOwnPropertyDescriptor():获取某个属性的描述对象
- Object.defineProperty():通过描述对象,定义某个属性
- Object.defineProperties():通过描述对象,定义多个属性
- 控制对象状态的方法
- Object.preventExtensions():防止对象扩展
- Object.isExtensible():判断对象是否可扩展
- Object.seal():禁止对象配置
- Object.isSealed():判断一个对象是否可配置
- Object.freeze():冻结一个对象
- Object.isFrozen():判断一个对象是否被冻结。
- 原型链相关方法
- Object.create():该方法可以指定原型对象和属性,返回一个新的对象
- Object.getPrototypeOf():获取对象的Prototype对象
Object 的实例方法
- 主要有以下六个
- Object.prototype.valueOf():返回当前对象对应的值
- Object.prototype.toString():返回当前对象对应的字符串形式
- Object.prototype.toLocaleString():返回当前对象对应的本地字符串形式
- Object.prototype.hasOwnProperty():判断某个属性是否为当前对象自身的属性,还是继承自原型对象的属性
- Object.prototype.isPrototypeOf():判断当前对象是否为另一个对象的原型
- Object.prototype.propertyIsEnumerable():判断某个属性是否可枚举。
// valueOf方法的作用是返回一个对象的“值”,默认情况下返回对象本身var obj = new Object();obj.valueOf() === obj // truevar obj = new Object();1 + obj // "1[object Object]"var obj = new Object();obj.valueOf = function () {return 2;};1 + obj // 3// toString方法的作用是返回一个对象的字符串形式,默认情况下返回类型字符串var o1 = new Object();o1.toString() // "[object Object]"var o2 = {a:1};o2.toString() // "[object Object]"var obj = new Object();obj.toString = function () {return 'hello';};obj + ' ' + 'world' // "hello world"// 数组、字符串、函数、Date 对象都分别部署了自定义的toString方法// 覆盖了Object.prototype.toString方法[1, 2, 3].toString() // "1,2,3"'123'.toString() // "123"(function () {return 123;}).toString()// "function () {// return 123;// }"(new Date()).toString()// "Tue May 10 2016 09:11:31 GMT+0800 (CST)"// 上面代码中,数组、字符串、函数、Date 对象调用toString方法// 并不会返回[object Object],因为它们都自定义了toString方法,覆盖原始方法// toString() 的应用:判断数据类型var obj = {};obj.toString() // "[object Object]"// 其中第二个Object表示该值的构造函数。这是一个十分有用的判断数据类型的方法// 由于实例对象可能会自定义toString方法,覆盖掉Object.prototype.toString方法// 所以为了得到类型字符串,最好直接使用Object.prototype.toString方法// Object.prototype.toString.call(value)// 不同数据类型的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.call(2) // "[object Number]"Object.prototype.toString.call('') // "[object String]"Object.prototype.toString.call(true) // "[object Boolean]"Object.prototype.toString.call(undefined) // "[object Undefined]"Object.prototype.toString.call(null) // "[object Null]"Object.prototype.toString.call(Math) // "[object Math]"Object.prototype.toString.call({}) // "[object Object]"Object.prototype.toString.call([]) // "[object Array]"// 利用这个特性,可以写出一个比typeof运算符更准确的类型判断函数var type = function (o){var s = Object.prototype.toString.call(o);return s.match(/\[object (.*?)\]/)[1].toLowerCase();};type({}); // "object"type([]); // "array"type(5); // "number"type(null); // "null"type(); // "undefined"type(/abcd/); // "regex"type(new Date()); // "date"// 在上面这个type函数的基础上,还可以加上专门判断某种类型数据的方法var type = function (o){var s = Object.prototype.toString.call(o);return s.match(/\[object (.*?)\]/)[1].toLowerCase();};['Null','Undefined','Object','Array','String','Number','Boolean','Function','RegExp'].forEach(function (t) {type['is' + t] = function (o) {return type(o) === t.toLowerCase();};});type.isObject({}) // truetype.isNumber(NaN) // truetype.isRegExp(/abc/) // true// Object.prototype.toLocaleString方法与toString的返回结果相同,也是返回一个值的字符串形式var obj = {};obj.toString(obj) // "[object Object]"obj.toLocaleString(obj) // "[object Object]"// 这个方法的主要作用是留出一个接口,让各种不同的对象实现自己版本的toLocaleString// 用来返回针对某些地域的特定的值var person = {toString: function () {return 'Henry Norman Bethune';},toLocaleString: function () {return '白求恩';}};person.toString() // Henry Norman Bethuneperson.toLocaleString() // 白求恩// 目前,主要有三个对象自定义了toLocaleString方法Array.prototype.toLocaleString()Number.prototype.toLocaleString()Date.prototype.toLocaleString()// 举例来说,日期的实例对象的toString和toLocaleString返回值就不一样// 而且toLocaleString的返回值跟用户设定的所在地域相关var date = new Date();date.toString() // "Tue Jan 01 2018 12:01:33 GMT+0800 (CST)"date.toLocaleString() // "1/01/2018, 12:01:33 PM"// Object.prototype.hasOwnProperty方法接受一个字符串作为参数,返回一个布尔值// 表示该实例对象自身是否具有该属性var obj = {p: 123};obj.hasOwnProperty('p') // trueobj.hasOwnProperty('toString') // false// 上面代码中,对象obj自身具有p属性,所以返回truetoString属性是继承的,所以返回false
2. 属性描述对象
- JavaScript 提供了一个内部数据结构,用来描述对象的属性,控制它的行为
- 比如该属性是否可写、可遍历等等
- 这个内部数据结构称为“属性描述对象”(attributes object)
- 每个属性都有自己对应的属性描述对象,保存该属性的一些元信息
// 属性描述对象的一个例子{value: 123, // 该属性的属性值,默认为undefinedwritable: false, // 表示属性值(value)是否可改变(即是否可写),默认为trueenumerable: true, // 表示该属性是否可遍历configurable: false, // 表示可配置性,默认为trueget: undefined, // get是一个函数,表示该属性的取值函数(getter),默认为undefinedset: undefined // set是一个函数,表示该属性的存值函数(setter),默认为undefined}
Object.getOwnPropertyDescriptor(),Object.getOwnPropertyNames
// Object.getOwnPropertyDescriptor()方法可以获取属性描述对象// 它的第一个参数是目标对象,第二个参数是一个字符串,对应目标对象的某个属性名var obj = { p: 'a' };Object.getOwnPropertyDescriptor(obj, 'p')// Object { value: "a",// writable: true,// enumerable: true,// configurable: true// }// 注意,Object.getOwnPropertyDescriptor()方法只能用于对象自身的属性,不能用于继承的属性var obj = { p: 'a' };Object.getOwnPropertyDescriptor(obj, 'toString')// undefined// Object.getOwnPropertyNames方法返回一个数组,成员是参数对象自身的全部属性的属性名// 不管该属性是否可遍历var obj = Object.defineProperties({}, {p1: { value: 1, enumerable: true },p2: { value: 2, enumerable: false }});Object.getOwnPropertyNames(obj)// ["p1", "p2"]Object.keys([]) // []Object.getOwnPropertyNames([]) // [ 'length' ]// 这跟Object.keys的行为不同,Object.keys只返回对象自身的可遍历属性的全部属性名Object.keys(Object.prototype) // []Object.getOwnPropertyNames(Object.prototype)// ['hasOwnProperty',// 'valueOf',// 'constructor',// 'toLocaleString',// 'isPrototypeOf',// 'propertyIsEnumerable',// 'toString']
Object.defineProperty(),Object.defineProperties(),Object.prototype.propertyIsEnumerable()
// Object.defineProperty()方法允许通过属性描述对象,定义或修改一个属性// 然后返回修改后的对象,它的用法如下Object.defineProperty(object, propertyName, attributesObject)var obj = Object.defineProperty({}, 'p', {value: 123,writable: false,enumerable: true,configurable: false});obj.p // 123obj.p = 246;obj.p // 123// 一次性定义或修改多个属性var obj = Object.defineProperties({}, {p1: { value: 123, enumerable: true },p2: { value: 'abc', enumerable: true },p3: { get: function () { return this.p1 + this.p2 },enumerable:true,configurable:true}});obj.p1 // 123obj.p2 // "abc"obj.p3 // "123abc"// 其中,p3属性定义了取值函数get,即每次读取该属性,都会调用这个取值函数// 注意,一旦定义了取值函数get(或存值函数set)// 就不能将writable属性设为true,或者同时定义value属性,否则会报错var obj = {};Object.defineProperty(obj, 'p', {value: 123,get: function() { return 456; }});// TypeError: Invalid property.// A property cannot both have accessors and be writable or have a valueObject.defineProperty(obj, 'p', {writable: true,get: function() { return 456; }});// TypeError: Invalid property descriptor.// Cannot both specify accessors and a value or writable attribute// 上面代码中,同时定义了get属性和value属性,以及将writable属性设为true,就会报错。// Object.defineProperty()和Object.defineProperties()参数里面的属性描述对象中,// writable、configurable、enumerable这三个属性的默认值都为falsevar obj = {};Object.defineProperty(obj, 'foo', {});Object.getOwnPropertyDescriptor(obj, 'foo')// {// value: undefined,// writable: false,// enumerable: false,// configurable: false// }// 实例对象的propertyIsEnumerable()方法返回一个布尔值,用来判断某个属性是否可遍历// 注意,这个方法只能用于判断对象自身的属性,对于继承的属性一律返回falsevar obj = {};obj.p = 123;obj.propertyIsEnumerable('p') // trueobj.propertyIsEnumerable('toString') // false// 上面代码中,obj.p是可遍历的,而obj.toString是继承的属性
元属性
- 属性描述对象的各个属性称为“元属性”,因为它们可以看作是控制属性的属性
// value属性是目标属性的值var obj = {};obj.p = 123;Object.getOwnPropertyDescriptor(obj, 'p').value// 123Object.defineProperty(obj, 'p', { value: 246 });obj.p // 246// 上面代码是通过value属性,读取或改写obj.p的例子// writable属性是一个布尔值,决定了目标属性的值(value)是否可以被改变var obj = {};Object.defineProperty(obj, 'a', {value: 37,writable: false});obj.a // 37obj.a = 25;obj.a // 37// obj.a的writable属性是false。然后,改变obj.a的值,不会有任何效果// enumerable(可遍历性)返回一个布尔值,表示目标属性是否可遍历var obj = {};'toString' in obj // true// 上面代码中,toString不是obj对象自身的属性,但是in运算符也返回true// 这导致了toString属性也会被for...in循环遍历// 这显然不太合理,后来就引入了“可遍历性”这个概念// 只有可遍历的属性,才会被for...in循环遍历// 同时还规定toString这一类实例对象继承的原生属性,都是不可遍历的// 这样就保证了for...in循环的可用性// 具体来说,如果一个属性的enumerable为false,下面三个操作不会取到该属性for..in循环Object.keys方法JSON.stringify方法var obj = {};Object.defineProperty(obj, 'x', {value: 123,enumerable: false});obj.x // 123for (var key in obj) {console.log(key);}// undefinedObject.keys(obj) // []JSON.stringify(obj) // "{}"// 注意,for...in循环包括继承的属性,Object.keys方法不包括继承的属性// 如果需要获取对象自身的所有属性,不管是否可遍历,可以使用Object.getOwnPropertyNames方法// configurable(可配置性)返回一个布尔值,决定了是否可以修改属性描述对象// 也就是说,configurable为false时,value、writable、enumerable和configurable都不能被修改了var obj = Object.defineProperty({}, 'p', {value: 1,writable: false,enumerable: false,configurable: false});Object.defineProperty(obj, 'p', {value: 2})// TypeError: Cannot redefine property: pObject.defineProperty(obj, 'p', {writable: true})// TypeError: Cannot redefine property: pObject.defineProperty(obj, 'p', {enumerable: true})// TypeError: Cannot redefine property: pObject.defineProperty(obj, 'p', {configurable: true})// TypeError: Cannot redefine property: p// 注意,writable只有在false改为true会报错,true改为false是允许的// 至于value,只要writable和configurable有一个为true,就允许改动
存取器
- 除了直接定义以外,属性还可以用存取器(accessor)定义
- 存值函数称为setter,使用属性描述对象的set属性
- 取值函数称为getter,使用属性描述对象的get属性
var obj = Object.defineProperty({}, 'p', {get: function () {return 'getter';},set: function (value) {console.log('setter: ' + value);}});obj.p // "getter"obj.p = 123 // "setter: 123"// obj.p定义了get和set属性。obj.p取值时,就会调用get;赋值时,就会调用set// 写法二var obj = {get p() {return 'getter';},set p(value) {console.log('setter: ' + value);}};// 上面两种写法,虽然属性p的读取和赋值行为是一样的,但是有一些细微的区别// 一种写法,属性p的configurable和enumerable都为false// 第二种写法,属性p的configurable和enumerable都为true,因此属性 p 是可遍历的// 因此属性p是可遍历的。实际开发中,写法二更常用// 存取器往往用于,属性的值依赖对象内部数据的场合var obj ={$n : 5,get next() { return this.$n++ },set next(n) {if (n >= this.$n) this.$n = n;else throw new Error('新的值必须大于当前值');}};obj.next // 5obj.next = 10;obj.next // 10obj.next = 5;// Uncaught Error: 新的值必须大于当前值// 上面代码中,next属性的存值函数和取值函数,都依赖于内部属性$n
对象的拷贝
// 将一个对象的所有属性,拷贝到另一个对象,可以用下面的方法实现var extend = function (to, from) {for (var property in from) {to[property] = from[property];}return to;}extend({}, {a: 1})// {a: 1}// 上面这个方法的问题在于,如果遇到存取器定义的属性,会只拷贝值extend({}, {get a() { return 1 }})// {a: 1}// 为了解决这个问题,我们可以通过Object.defineProperty方法来拷贝属性var extend = function (to, from) {for (var property in from) {if (!from.hasOwnProperty(property)) continue;Object.defineProperty(to,property,Object.getOwnPropertyDescriptor(from, property));}return to;}extend({}, { get a(){ return 1 } })// { get a(){ return 1 } })
冻结对象的读写状态,防止对象被改变,有三种冻结方式
- 最弱的一种是Object.preventExtensions
- 其次是Object.seal
- 最强的是Object.freeze
// Object.preventExtensions方法可以使得一个对象无法再添加新的属性var obj = new Object();Object.preventExtensions(obj);Object.defineProperty(obj, 'p', {value: 'hello'});// TypeError: Cannot define property:p, object is not extensible.obj.p = 1;obj.p // undefined// Object.isExtensible方法用于检查一个对象是否使用了Object.preventExtensions方法// 也就是说,检查是否可以为一个对象添加属性var obj = new Object();Object.isExtensible(obj) // trueObject.preventExtensions(obj);Object.isExtensible(obj) // false// Object.seal方法使得一个对象既无法添加新属性,也无法删除旧属性var obj = { p: 'hello' };Object.seal(obj);delete obj.p;obj.p // "hello"obj.x = 'world';obj.x // undefined// Object.seal实质是把属性描述对象的configurable属性设为false,因此属性描述对象不再能改变了// Object.seal只是禁止新增或删除属性,并不影响修改某个属性的值// Object.isSealed方法用于检查一个对象是否使用了Object.seal方法var obj = { p: 'a' };Object.seal(obj);Object.isSealed(obj) // true// 这时,Object.isExtensible方法也返回falsevar obj = { p: 'a' };Object.seal(obj);Object.isExtensible(obj) // false// Object.freeze方法可以使得一个对象无法添加新属性、无法删除旧属性// 也无法改变属性的值,使得这个对象实际上变成了常量var obj = {p: 'hello'};Object.freeze(obj);obj.p = 'world';obj.p // "hello"obj.t = 'hello';obj.t // undefineddelete obj.p // falseobj.p // "hello"// 上面代码中,对obj对象进行Object.freeze()以后,修改属性、新增属性、删除属性都无效了// 这些操作并不报错,只是默默地失败,如果在严格模式下,则会报错// Object.isFrozen方法用于检查一个对象是否使用了Object.freeze方法var obj = {p: 'hello'};Object.freeze(obj);Object.isFrozen(obj) // true// 使用Object.freeze方法以后,Object.isSealed将会返回true,Object.isExtensible返回falsevar obj = {p: 'hello'};Object.freeze(obj);Object.isSealed(obj) // trueObject.isExtensible(obj) // false// Object.isFrozen的一个用途是,确认某个对象没有被冻结后,再对它的属性赋值var obj = {p: 'hello'};Object.freeze(obj);if (!Object.isFrozen(obj)) {obj.p = 'world';}// 局限性// 上面的三个方法锁定对象的可写性有一个漏洞:可以通过改变原型对象,来为对象增加属性var obj = new Object();Object.preventExtensions(obj);var proto = Object.getPrototypeOf(obj);proto.t = 'hello';obj.t// hello// 对象obj本身不能新增属性,但是可以在它的原型对象上新增属性,就依然能够在obj上读到// 另外一个局限是,如果属性值是对象// 上面这些方法只能冻结属性指向的对象,而不能冻结对象本身的内容var obj = {foo: 1,bar: ['a', 'b']};Object.freeze(obj);obj.bar.push('c');obj.bar // ["a", "b", "c"]// 上面代码中,obj.bar属性指向一个数组,obj对象被冻结以后// 这个指向无法改变,即无法指向其他值,但是所指向的数组是可以改变的
3. Array 对象
var arr = new Array(2);arr.length // 2arr // [ empty x 2 ]// 生成一个两个成员的数组,每个位置都是空值var arr = new Array(2);// 等同于var arr = Array(2);// 考虑到语义性,以及与其他构造函数用户保持一致,建议总是加上new// rray()作为构造函数,行为很不一致因此,不建议使用它生成新数组,直接使用数组字面量是更好的做法// badvar arr = new Array(1, 2);// goodvar arr = [1, 2];var a = new Array(3);var b = [undefined, undefined, undefined];a.length // 3b.length // 3a[0] // undefinedb[0] // undefined0 in a // false0 in b // true
Array.isArray()
- Array.isArray方法返回一个布尔值,表示参数是否为数组。它可以弥补typeof运算符的不足
var arr = [1, 2, 3];typeof arr // "object"Array.isArray(arr) // true// typeof运算符只能显示数组的类型是Object,而Array.isArray方法可以识别数组
valueOf(),toString()
// 数组的valueOf方法返回数组本身var arr = [1, 2, 3];arr.valueOf() // [1, 2, 3]// 数组的toString方法返回数组的字符串形式var arr = [1, 2, 3];arr.toString() // "1,2,3"var arr = [1, 2, 3, [4, 5, 6]];arr.toString() // "1,2,3,4,5,6"
push(),pop()
// push方法用于在数组的末端添加一个或多个元素,并返回添加新元素后的数组长度// 注意,该方法会改变原数组var arr = [];arr.push(1) // 1arr.push('a') // 2arr.push(true, {}) // 4arr // [1, 'a', true, {}]// pop方法用于删除数组的最后一个元素,并返回该元素// 注意,该方法会改变原数组var arr = ['a', 'b', 'c'];arr.pop() // 'c'arr // ['a', 'b']// 对空数组使用pop方法,不会报错,而是返回undefined[].pop() // undefined// push和pop结合使用,就构成了“后进先出”的栈结构(stack)var arr = [];arr.push(1, 2);arr.push(3);arr.pop();arr // [1, 2]
shift(),unshift()
// shift()方法用于删除数组的第一个元素,并返回该元素// 注意,该方法会改变原数组var a = ['a', 'b', 'c'];a.shift() // 'a'a // ['b', 'c']// shift()方法可以遍历并清空一个数组var list = [1, 2, 3, 4];var item;while (item = list.shift()) {console.log(item);}list // []// 它的前提是数组元素不能是0或任何布尔值等于false的元素// 因此这样的遍历不是很可靠// push()和shift()结合使用,就构成了“先进先出”的队列结构(queue)// unshift()方法用于在数组的第一个位置添加元素,并返回添加新元素后的数组长度注意,该方法会改变原数组var a = ['a', 'b', 'c'];a.unshift('x'); // 4a // ['x', 'a', 'b', 'c']// unshift()方法可以接受多个参数,这些参数都会添加到目标数组头部var arr = [ 'c', 'd' ];arr.unshift('a', 'b') // 4arr // [ 'a', 'b', 'c', 'd' ]
join()
// join()方法以指定参数作为分隔符,将所有数组成员连接为一个字符串返回如果不提供参数,默认用逗号分隔var a = [1, 2, 3, 4];a.join(' ') // '1 2 3 4'a.join(' | ') // "1 | 2 | 3 | 4"a.join() // "1,2,3,4"// 如果数组成员是undefined或null或空位,会被转成空字符串[undefined, null].join('#')// '#'['a',, 'b'].join('-')// 'a--b'// 通过call方法,这个方法也可以用于字符串或类似数组的对象Array.prototype.join.call('hello', '-')// "h-e-l-l-o"var obj = { 0: 'a', 1: 'b', length: 2 };Array.prototype.join.call(obj, '-')// 'a-b'
concat()
// concat方法用于多个数组的合并// 它将新数组的成员,添加到原数组成员的后部,然后返回一个新数组,原数组不变['hello'].concat(['world'])// ["hello", "world"]['hello'].concat(['world'], ['!'])// ["hello", "world", "!"][].concat({a: 1}, {b: 2})// [{ a: 1 }, { b: 2 }][2].concat({a: 1})// [2, {a: 1}]// 除了数组作为参数,concat也接受其他类型的值作为参数,添加到目标数组尾部。[1, 2, 3].concat(4, 5, 6)// [1, 2, 3, 4, 5, 6]// 如果数组成员包括对象,concat方法返回当前数组的一个浅拷贝var obj = { a: 1 };var oldArray = [obj];var newArray = oldArray.concat();obj.a = 2;newArray[0].a // 2
reverse()
// reverse方法用于颠倒排列数组元素,返回改变后的数组。注意,该方法将改变原数组var a = ['a', 'b', 'c'];a.reverse() // ["c", "b", "a"]a // ["c", "b", "a"]
slice()
// slice()方法用于提取目标数组的一部分,返回一个新数组,原数组不变arr.slice(start, end);var a = ['a', 'b', 'c'];a.slice(0) // ["a", "b", "c"]a.slice(1) // ["b", "c"]a.slice(1, 2) // ["b"]a.slice(2, 6) // ["c"]a.slice() // ["a", "b", "c"]// 最后一个例子slice()没有参数,实际上等于返回一个原数组的拷贝// 如果slice()方法的参数是负数,则表示倒数计算的位置var a = ['a', 'b', 'c'];a.slice(-2) // ["b", "c"]a.slice(-2, -1) // ["b"]// 如果第一个参数大于等于数组长度,或者第二个参数小于第一个参数,则返回空数组var a = ['a', 'b', 'c'];a.slice(4) // []a.slice(2, 1) // []// slice()方法的一个重要应用,是将类似数组的对象转为真正的数组Array.prototype.slice.call({ 0: 'a', 1: 'b', length: 2 })// ['a', 'b']Array.prototype.slice.call(document.querySelectorAll("div"));Array.prototype.slice.call(arguments);
splice()
// splice()方法用于删除原数组的一部分成员// 并可以在删除的位置添加新的数组成员,返回值是被删除的元素// 注意,该方法会改变原数组arr.splice(start, count, addElement1, addElement2, ...);var a = ['a', 'b', 'c', 'd', 'e', 'f'];a.splice(4, 2) // ["e", "f"]a // ["a", "b", "c", "d"]// 起始位置如果是负数,就表示从倒数位置开始删除var a = ['a', 'b', 'c', 'd', 'e', 'f'];a.splice(-4, 2) // ["c", "d"]// 如果只是单纯地插入元素,splice方法的第二个参数可以设为0var a = [1, 1, 1];a.splice(1, 0, 2) // []a // [1, 2, 1, 1]// 如果只提供第一个参数,等同于将原数组在指定位置拆分成两个数组var a = [1, 2, 3, 4];a.splice(2) // [3, 4]a // [1, 2]
sort()
// sort方法对数组成员进行排序,默认是按照字典顺序排序// 排序后,原数组将被改变['d', 'c', 'b', 'a'].sort()// ['a', 'b', 'c', 'd'][4, 3, 2, 1].sort()// [1, 2, 3, 4][11, 101].sort()// [101, 11][10111, 1101, 111].sort()// [10111, 1101, 111]// 上面代码的最后两个例子,需要特殊注意// sort()方法不是按照大小排序,而是按照字典顺序// 也就是说,数值会被先转成字符串,再按照字典顺序进行比较,所以101排在11的前面// 如果想让sort方法按照自定义方式排序,可以传入一个函数作为参数[10111, 1101, 111].sort(function (a, b) {return a - b;})// [111, 1101, 10111]// 上面代码中,sort的参数函数本身接受两个参数,表示进行比较的两个数组成员[{ name: "张三", age: 30 },{ name: "李四", age: 24 },{ name: "王五", age: 28 }].sort(function (o1, o2) {return o1.age - o2.age;})// [// { name: "李四", age: 24 },// { name: "王五", age: 28 },// { name: "张三", age: 30 }// ]// bad[1, 4, 2, 6, 0, 6, 2, 6].sort((a, b) => a > b)// good[1, 4, 2, 6, 0, 6, 2, 6].sort((a, b) => a - b)// 前一种排序算法返回的是布尔值,这是不推荐使用的。后一种是数值,才是更好的写法
map()
// map方法将数组的所有成员依次传入参数函数// 然后把每一次的执行结果组成一个新数组返回var numbers = [1, 2, 3];numbers.map(function (n) {return n + 1;});// [2, 3, 4]numbers// [1, 2, 3]// map方法接受一个函数作为参数// 该函数调用时,map方法向它传入三个参数:当前成员、当前位置和数组本身[1, 2, 3].map(function(elem, index, arr) {return elem * index;});// [0, 2, 6]// map方法还可以接受第二个参数,用来绑定回调函数内部的this变量var arr = ['a', 'b', 'c'];[1, 2].map(function (e) {return this[e];}, arr)// ['b', 'c']// 如果数组有空位,map方法的回调函数在这个位置不会执行,会跳过数组的空位var f = function (n) { return 'a' };[1, undefined, 2].map(f) // ["a", "a", "a"][1, null, 2].map(f) // ["a", "a", "a"][1, , 2].map(f) // ["a", , "a"]
forEach()
// forEach方法与map方法很相似,也是对数组的所有成员依次执行参数函数// 但是,forEach方法不返回值,只用来操作数据function log(element, index, array) {console.log('[' + index + '] = ' + element);}[2, 5, 9].forEach(log);// [0] = 2// [1] = 5// [2] = 9// forEach方法也可以接受第二个参数,绑定参数函数的this变量var out = [];[1, 2, 3].forEach(function(elem) {this.push(elem * elem);}, out);out // [1, 4, 9]// 注意,forEach方法无法中断执行,总是会将所有成员遍历完// 如果希望符合某种条件时,就中断遍历,要使用for循环var arr = [1, 2, 3];for (var i = 0; i < arr.length; i++) {if (arr[i] === 2) break;console.log(arr[i]);}// 1// forEach方法也会跳过数组的空位var log = function (n) {console.log(n + 1);};[1, undefined, 2].forEach(log)// 2// NaN// 3[1, null, 2].forEach(log)// 2// 1// 3[1, , 2].forEach(log)// 2// 3
filter()
// filter方法用于过滤数组成员,满足条件的成员组成一个新数组返回[1, 2, 3, 4, 5].filter(function (elem) {return (elem > 3);})// [4, 5]var arr = [0, 1, 'a', false];arr.filter(Boolean)// [1, "a"]// filter方法的参数函数可以接受三个参数:当前成员,当前位置和整个数组[1, 2, 3, 4, 5].filter(function (elem, index, arr) {return index % 2 === 0;});// [1, 3, 5]// filter方法还可以接受第二个参数,用来绑定参数函数内部的this变量var obj = { MAX: 3 };var myFilter = function (item) {if (item > this.MAX) return true;};var arr = [2, 8, 3, 4, 1, 3, 2, 9];arr.filter(myFilter, obj) // [8, 4, 9]
some(),every()
//这两个方法类似“断言”(assert),返回一个布尔值// 表示判断数组成员是否符合某种条件// 它们接受一个函数作为参数,所有数组成员依次执行该函数// 该函数接受三个参数:当前成员、当前位置和整个数组,然后返回一个布尔值// some方法是只要一个成员的返回值是true,则整个some方法的返回值就是true// 否则返回falsevar arr = [1, 2, 3, 4, 5];arr.some(function (elem, index, arr) {return elem >= 3;});// true// every方法是所有成员的返回值都是true,整个every方法才返回true// 否则返回falsevar arr = [1, 2, 3, 4, 5];arr.every(function (elem, index, arr) {return elem >= 3;});// false// 注意,对于空数组,some方法返回false// every方法返回true// 回调函数都不会执行function isEven(x) { return x % 2 === 0 }[].some(isEven) // false[].every(isEven) // true// some和every方法还可以接受第二个参数,用来绑定参数函数内部的this变量
reduce(),reduceRight()
// reduce方法和reduceRight方法依次处理数组的每个成员,最终累计为一个值// 它们的差别是,reduce是从左到右处理(从第一个成员到最后一个成员)// reduceRight则是从右到左(从最后一个成员到第一个成员),其他完全一样[1, 2, 3, 4, 5].reduce(function (a, b) {console.log(a, b);return a + b;})// 1 2// 3 3// 6 4// 10 5//最后结果:15// reduce方法和reduceRight方法的第一个参数都是一个函数// 该函数接受以下四个参数。累积变量,默认为数组的第一个成员当前变量,默认为数组的第二个成员当前位置(从0开始)原数组// 这四个参数之中,只有前两个是必须的,后两个则是可选的// 如果要对累积变量指定初值,可以把它放在reduce方法和reduceRight方法的第二个参数[1, 2, 3, 4, 5].reduce(function (a, b) {return a + b;}, 10);// 25// 上面的第二个参数相当于设定了默认值,处理空数组时尤其有用function add(prev, cur) {return prev + cur;}[].reduce(add)// TypeError: Reduce of empty array with no initial value[].reduce(add, 1)// 1// reduceRight方法的例子function subtract(prev, cur) {return prev - cur;}[3, 2, 1].reduce(subtract) // 0[3, 2, 1].reduceRight(subtract) // -4// 上面代码中,reduce方法相当于3减去2再减去1// reduceRight方法相当于1减去2再减去3// 由于这两个方法会遍历数组,所以实际上还可以用来做一些遍历相关的操作// 比如,找出字符长度最长的数组成员function findLongest(entries) {return entries.reduce(function (longest, entry) {return entry.length > longest.length ? entry : longest;}, '');}findLongest(['aaa', 'bb', 'c']) // "aaa"// reduce的参数函数会将字符长度较长的那个数组成员,作为累积值// 这导致遍历所有成员之后,累积值就是字符长度最长的那个成员
indexOf(),lastIndexOf()
// indexOf方法返回给定元素在数组中第一次出现的位置,如果没有出现则返回-1var a = ['a', 'b', 'c'];a.indexOf('b') // 1a.indexOf('y') // -1// indexOf方法还可以接受第二个参数,表示搜索的开始位置['a', 'b', 'c'].indexOf('a', 1) // -1// lastIndexOf方法返回给定元素在数组中最后一次出现的位置,如果没有出现则返回-1var a = [2, 5, 9, 2];a.lastIndexOf(2) // 3a.lastIndexOf(7) // -1// 注意,这两个方法不能用来搜索NaN的位置,即它们无法确定数组成员是否包含NaN[NaN].indexOf(NaN) // -1[NaN].lastIndexOf(NaN) // -1// 这是因为这两个方法内部,使用严格相等运算符(===)进行比较// 而NaN是唯一一个不等于自身的值
链式使用
var users = [{name: 'tom', email: 'tom@example.com'},{name: 'peter', email: 'peter@example.com'}];users.map(function (user) {return user.email;}).filter(function (email) {return /^t/.test(email);}).forEach(function (email) {console.log(email);});// "tom@example.com"
4. 包装对象
- 所谓“包装对象”,指的是与数值、字符串、布尔值分别相对应的Number、String、Boolean三个原生对象
- 这三个原生对象可以把原始类型的值变成(包装成)对象
- 包装对象的设计目的
- 首先是使得“对象”这种类型可以覆盖 JavaScript 所有的值,整门语言有一个通用的数据模型
- 其次是使得原始类型的值也有办法调用自己的方法
var v1 = new Number(123);var v2 = new String('abc');var v3 = new Boolean(true);typeof v1 // "object"typeof v2 // "object"typeof v3 // "object"v1 === 123 // falsev2 === 'abc' // falsev3 === true // false// Number、String和Boolean这三个原生对象// 如果不作为构造函数调用(即调用时不加new),而是作为普通函数调用// 常常用于将任意类型的值转为数值、字符串和布尔值// 字符串转为数值Number('123') // 123// 数值转为字符串String(123) // "123"// 数值转为布尔值Boolean(123) // true// 实例方法valueOf()方法返回包装对象实例对应的原始类型的值new Number(123).valueOf() // 123new String('abc').valueOf() // "abc"new Boolean(true).valueOf() // true// toString()方法返回对应的字符串形式new Number(123).toString() // "123"new String('abc').toString() // "abc"new Boolean(true).toString() // "true"
原始类型与实例对象的自动转换
- 某些场合,原始类型的值会自动当作包装对象调用,即调用包装对象的属性和方法
- 这时,JavaScript 引擎会自动将原始类型的值转为包装对象实例,并在使用后立刻销毁实例
'abc'.length // 3var str = 'abc';str.length // 3// 等同于var strObj = new String(str)// String {// 0: "a", 1: "b", 2: "c", length: 3, [[PrimitiveValue]]: "abc"// }strObj.length // 3// 上面代码中,字符串abc的包装对象提供了多个属性,length只是其中之一// 自动转换生成的包装对象是只读的,无法修改。所以,字符串无法添加新属性var s = 'Hello World';s.x = 123;s.x // undefined
自定义方法
String.prototype.double = function () {return this.valueOf() + this.valueOf();};'abc'.double()// abcabcNumber.prototype.double = function () {return this.valueOf() + this.valueOf();};(123).double() // 246// 注意,最后一行的123外面必须要加上圆括号// 否则后面的点运算符(.)会被解释成小数点
5. Boolean 对象
- Boolean对象是 JavaScript 的三个包装对象之一
- 作为构造函数,它主要用于生成布尔值的包装对象实例
var b = new Boolean(true);typeof b // "object"b.valueOf() // true// 注意,false对应的包装对象实例,布尔运算结果也是trueif (new Boolean(false)) {console.log('true');} // trueif (new Boolean(false).valueOf()) {console.log('true');} // 无输出
Boolean 函数的类型转换作用
Boolean(undefined) // falseBoolean(null) // falseBoolean(0) // falseBoolean('') // falseBoolean(NaN) // falseBoolean(1) // trueBoolean('false') // trueBoolean([]) // trueBoolean({}) // trueBoolean(function () {}) // trueBoolean(/foo/) // true// 使用双重的否运算符(!)也可以将任意值转为对应的布尔值!!undefined // false!!null // false!!0 // false!!'' // false!!NaN // false!!1 // true!!'false' // true!![] // true!!{} // true!!function(){} // true!!/foo/ // true// 对于一些特殊值,Boolean对象前面加不加new,会得到完全相反的结果,必须小心if (Boolean(false)) {console.log('true');} // 无输出if (new Boolean(false)) {console.log('true');} // trueif (Boolean(null)) {console.log('true');} // 无输出if (new Boolean(null)) {console.log('true');} // true
6. Number 对象
- Number对象是数值对应的包装对象
- 可以作为构造函数使用
- 也可以作为工具函数使用
var n = new Number(1);typeof n // "object"Number(true) // 1
静态属性
Number.POSITIVE_INFINITY // InfinityNumber.NEGATIVE_INFINITY // -InfinityNumber.NaN // NaNNumber.MAX_VALUE// 1.7976931348623157e+308Number.MAX_VALUE < Infinity// trueNumber.MIN_VALUE// 5e-324Number.MIN_VALUE > 0// trueNumber.MAX_SAFE_INTEGER // 9007199254740991Number.MIN_SAFE_INTEGER // -9007199254740991
实例方法
// Number.prototype.toString()// Number对象部署了自己的toString方法,用来将一个数值转为字符串形式(10).toString() // "10"// toString方法可以接受一个参数,表示输出的进制// 如果省略这个参数,默认将数值先转为十进制(10).toString(2) // "1010"(10).toString(8) // "12"(10).toString(16) // "a"// toString方法只能将十进制的数,转为其他进制的字符串// 如果要将其他进制的数,转回十进制,需要使用parseInt方法// Number.prototype.toFixed()// toFixed()方法先将一个数转为指定位数的小数,然后返回这个小数对应的字符串(10).toFixed(2) // "10.00"10.005.toFixed(2) // "10.01"// 由于浮点数的原因,小数5的四舍五入是不确定的,使用的时候必须小心// Number.prototype.toExponential()// toExponential方法用于将一个数转为科学计数法形式(10).toExponential() // "1e+1"(10).toExponential(1) // "1.0e+1"(10).toExponential(2) // "1.00e+1"(1234).toExponential() // "1.234e+3"(1234).toExponential(1) // "1.2e+3"(1234).toExponential(2) // "1.23e+3"// toExponential方法的参数是小数点后有效数字的位数,范围为0到100// 超出这个范围,会抛出一个 RangeError 错误// Number.prototype.toPrecision()// Number.prototype.toPrecision()方法用于将一个数转为指定位数的有效数字(12.34).toPrecision(1) // "1e+1"(12.34).toPrecision(2) // "12"(12.34).toPrecision(3) // "12.3"(12.34).toPrecision(4) // "12.34"(12.34).toPrecision(5) // "12.340"// 该方法用于四舍五入时不太可靠,跟浮点数不是精确储存有(12.35).toPrecision(3) // "12.3"(12.25).toPrecision(3) // "12.3"(12.15).toPrecision(3) // "12.2"(12.45).toPrecision(3) // "12.4"// Number.prototype.toLocaleString()// Number.prototype.toLocaleString()方法接受一个地区码作为参数,返回一个字符串// 表示当前数字在该地区的当地书写形式(123).toLocaleString('zh-Hans-CN-u-nu-hanidec')// "一二三"// 该方法还可以接受第二个参数配置对象,用来定制指定用途的返回字符串// 该对象的style属性指定输出样式,默认值是decimal,表示输出十进制形式// 如果值为percent,表示输出百分数(123).toLocaleString('zh-Hans-CN', { style: 'percent' })// "12,300%"// 如果style属性的值为currency,则可以搭配currency属性,输出指定格式的货币字符串形式(123).toLocaleString('zh-Hans-CN', { style: 'currency', currency: 'CNY' })// "¥123.00"(123).toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })// "123,00 €"(123).toLocaleString('en-US', { style: 'currency', currency: 'USD' })// "$123.00"// 如果Number.prototype.toLocaleString()省略了参数,则由浏览器自行决定如何处理// 注意,该方法如果使用浏览器不认识的地区码,会抛出一个错误(123).toLocaleString('123') // 出错
自定义方法
Number.prototype.add = function (x) {return this + x;};8['add'](2) // 10// 链式运算Number.prototype.subtract = function (x) {return this - x;};(8).add(2).subtract(4)// 6// 部署更复杂的运算Number.prototype.iterate = function () {var result = [];for (var i = 0; i <= this; i++) {result.push(i);}return result;};(8).iterate()// [0, 1, 2, 3, 4, 5, 6, 7, 8]// 注意,数值的自定义方法,只能定义在它的原型对象Number.prototype上面// 数值本身是无法自定义属性的var n = 1;n.x = 1;n.x // undefined
7. String 对象
- String对象是 JavaScript 原生提供的三个包装对象之一
- 用来生成字符串对象
var s1 = 'abc';var s2 = new String('abc');typeof s1 // "string"typeof s2 // "object"s2.valueOf() // "abc"// 字符串对象是一个类似数组的对象(很像数组,但不是数组)new String('abc')// String {0: "a", 1: "b", 2: "c", length: 3}(new String('abc'))[1] // "b"// String对象还可以当作工具方法使用,将任意类型的值转为字符串String(true) // "true"String(5) // "5"// 静态方法// String.fromCharCode()// 该方法的参数是一个或多个数值,代表 Unicode 码点,返回值是这些码点组成的字符串String.fromCharCode() // ""String.fromCharCode(97) // "a"String.fromCharCode(104, 101, 108, 108, 111)// "hello"// 注意,该方法不支持 Unicode 码点大于0xFFFF的字符// 即传入的参数不能大于0xFFFF(即十进制的 65535)// JavaScript 默认支持两个字节的字符,这种情况下,必须把0x20BB7拆成两个字符表示String.fromCharCode(0xD842, 0xDFB7)// "𠮷"
实例属性和方法
// length'abc'.length // 3// charAt方法返回指定位置的字符,参数是从0开始编号的位置var s = new String('abc');s.charAt(1) // "b"s.charAt(s.length - 1) // "c"// 这个方法完全可以用数组下标替代'abc'.charAt(1) // "b"'abc'[1] // "b"// charCodeAt()方法返回字符串指定位置的 Unicode 码点(十进制表示)// 相当于String.fromCharCode()的逆操作'abc'.charCodeAt(1) // 98// 如果没有任何参数,charCodeAt返回首字符的 Unicode 码点'abc'.charCodeAt() // 97// 如果参数为负数,或大于等于字符串的长度,charCodeAt返回NaN'abc'.charCodeAt(-1) // NaN'abc'.charCodeAt(4) // NaN// 注意,charCodeAt方法返回的 Unicode 码点不会大于65536(0xFFFF)// concat方法用于连接两个字符串,返回一个新字符串,不改变原字符串var s1 = 'abc';var s2 = 'def';s1.concat(s2) // "abcdef"s1 // "abc// 该方法可以接受多个参数'a'.concat('b', 'c') // "abc"// 如果参数不是字符串,concat方法会将其先转为字符串,然后再连接var one = 1;var two = 2;var three = '3';''.concat(one, two, three) // "123"one + two + three // "33"// 作为对比,加号运算符在两个运算数都是数值时,不会转换类型// 所以返回的是一个两个字符的字符串// slice()方法用于从原字符串取出子字符串并返回,不改变原字符串'JavaScript'.slice(0, 4) // "Java"'JavaScript'.slice(4) // "Script"'JavaScript'.slice(-6) // "Script"'JavaScript'.slice(0, -6) // "Java"'JavaScript'.slice(-2, -1) // "p"'JavaScript'.slice(2, 1) // ""// substring方法用于从原字符串取出子字符串并返回,不改变原字符串// 跟slice方法很相像'JavaScript'.substring(0, 4) // "Java"'JavaScript'.substring(4) // "Script"// 如果第一个参数大于第二个参数,substring方法会自动更换两个参数的位置'JavaScript'.substring(10, 4) // "Script"// 等同于'JavaScript'.substring(4, 10) // "Script"// 如果参数是负数,substring方法会自动将负数转为0'JavaScript'.substring(-3) // "JavaScript"'JavaScript'.substring(4, -3) // "Java"// 由于这些规则违反直觉,因此不建议使用substring方法,应该优先使用slice// substr方法用于从原字符串取出子字符串并返回,不改变原字符串// 跟slice和substring方法的作用相同'JavaScript'.substr(4, 6) // "Script"'JavaScript'.substr(4) // "Script"'JavaScript'.substr(-6) // "Script"'JavaScript'.substr(4, -1) // ""// indexOf方法用于确定一个字符串在另一个字符串中第一次出现的位置// 返回结果是匹配开始的位置// 如果返回-1,就表示不匹配'hello world'.indexOf('o') // 4'JavaScript'.indexOf('script') // -1'hello world'.indexOf('o', 6) // 7// lastIndexOf方法的用法跟indexOf方法一致// 主要的区别是lastIndexOf从尾部开始匹配,indexOf则是从头部开始匹配'hello world'.lastIndexOf('o') // 7lastIndexOf的第二个参数表示从该位置起向前匹配'hello world'.lastIndexOf('o', 6) // 4// trim方法用于去除字符串两端的空格,返回一个新字符串,不改变原字符串' hello world '.trim()// "hello world"// 该方法去除的不仅是空格,还包括制表符(\t、\v)、换行符(\n)和回车符(\r)// toLowerCase方法用于将一个字符串全部转为小写// toUpperCase则是全部转为大写// 它们都返回一个新字符串,不改变原字符串'Hello World'.toLowerCase()// "hello world"'Hello World'.toUpperCase()// "HELLO WORLD"match方法用于确定原字符串是否匹配某个子字符串,返回一个数组// 成员为匹配的第一个字符串'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 // 1matches.input // "cat, bat, sat, fat"// match方法还可以使用正则表达式作为参数// search方法的用法基本等同于match,但是返回值为匹配的第一个位置// 如果没有找到匹配,则返回-1'cat, bat, sat, fat'.search('at') // 1// search方法还可以使用正则表达式作为参数// replace方法用于替换匹配的子字符串//一般情况下只替换第一个匹配(除非使用带有g修饰符的正则表达式)'aaa'.replace('a', 'b') // "baa"// replace方法还可以使用正则表达式作为参数split方法按照给定规则分割字符串,返回一个由分割出来的子字符串组成的数组'a|b|c'.split('|') // ["a", "b", "c"]'a|b|c'.split('') // ["a", "|", "b", "|", "c"]'a|b|c'.split() // ["a|b|c"]'a||c'.split('|') // ['a', '', 'c']'|b|c'.split('|') // ["", "b", "c"]'a|b|'.split('|') // ["a", "b", ""]// split方法还可以接受第二个参数,限定返回数组的最大成员数'a|b|c'.split('|', 0) // []'a|b|c'.split('|', 1) // ["a"]'a|b|c'.split('|', 2) // ["a", "b"]'a|b|c'.split('|', 3) // ["a", "b", "c"]'a|b|c'.split('|', 4) // ["a", "b", "c"]// split方法还可以使用正则表达式作为参数// localeCompare方法用于比较两个字符串,它返回一个整数// 如果小于0,表示第一个字符串小于第二个字符串// 如果等于0,表示两者相等// 如果大于0,表示第一个字符串大于第二个字符串'apple'.localeCompare('banana') // -1'apple'.localeCompare('apple') // 0// 该方法的最大特点,就是会考虑自然语言的顺序// 举例来说,正常情况下,大写的英文字母小于小写字母'B' > 'a' // false
8. Math 对象
Math对象的静态属性,提供以下一些数学常数
Math.E:常数eMath.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.718281828459045Math.LN2 // 0.6931471805599453Math.LN10 // 2.302585092994046Math.LOG2E // 1.4426950408889634Math.LOG10E // 0.4342944819032518Math.PI // 3.141592653589793Math.SQRT1_2 // 0.7071067811865476Math.SQRT2 // 1.4142135623730951// 这些属性都是只读的,不能修改。
Math对象提供以下一些静态方法
Math.abs():绝对值Math.ceil():向上取整Math.floor():向下取整Math.max():最大值Math.min():最小值Math.pow():幂运算Math.sqrt():平方根Math.log():自然对数Math.exp():e的指数Math.round():四舍五入Math.random():随机数// Math.abs方法返回参数值的绝对值Math.abs(1) // 1Math.abs(-1) // 1// Math.max方法返回参数之中最大的那个值// Math.min返回最小的那个值Math.max(2, -1, 5) // 5Math.min(2, -1, 5) // -1Math.min() // InfinityMath.max() // -Infinity// Math.floor方法返回小于参数值的最大整数(地板值)// Math.ceil方法返回大于参数值的最小整数(天花板值)Math.floor(3.2) // 3Math.floor(-3.2) // -4Math.ceil(3.2) // 4Math.ceil(-3.2) // -3// 这两个方法可以结合起来,实现一个总是返回数值的整数部分的函数function ToInteger(x) {x = Number(x);return x < 0 ? Math.ceil(x) : Math.floor(x);}ToInteger(3.2) // 3ToInteger(3.5) // 3ToInteger(3.8) // 3ToInteger(-3.2) // -3ToInteger(-3.5) // -3ToInteger(-3.8) // -3// Math.round方法用于四舍五入Math.round(0.1) // 0Math.round(0.5) // 1Math.round(0.6) // 1// 等同于Math.floor(x + 0.5)// 注意,它对负数的处理(主要是对0.5的处理)Math.round(-1.1) // -1Math.round(-1.5) // -1Math.round(-1.6) // -2Math.pow方法返回以第一个参数为底数、第二个参数为指数的幂运算值// 等同于 2 ** 2Math.pow(2, 2) // 4// 等同于 2 ** 3Math.pow(2, 3) // 8// 下面是计算圆面积的方法var radius = 20;var area = Math.PI * Math.pow(radius, 2);// Math.sqrt方法返回参数值的平方根。如果参数是一个负值,则返回NaNMath.sqrt(4) // 2Math.sqrt(-4) // NaN// Math.log方法返回以e为底的自然对数值Math.log(Math.E) // 1Math.log(10) // 2.302585092994046// 如果要计算以10为底的对数,可以先用Math.log求出自然对数// 然后除以Math.LN10;求以2为底的对数,可以除以Math.LN2Math.log(100)/Math.LN10 // 2Math.log(8)/Math.LN2 // 3// Math.exp方法返回常数e的参数次方Math.exp(1) // 2.718281828459045Math.exp(3) // 20.085536923187668Math.random()返回0到1之间的一个伪随机数,可能等于0,但是一定小于1Math.random() // 0.7151307314634323// 任意范围的随机数生成函数如下function getRandomArbitrary(min, max) {return Math.random() * (max - min) + min;}getRandomArbitrary(1.5, 6.5)// 2.4942810038223864// 任意范围的随机整数生成函数如下function getRandomInt(min, max) {return Math.floor(Math.random() * (max - min + 1)) + min;}getRandomInt(1, 6) // 5// 返回随机字符的例子如下function random_str(length) {var ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';ALPHABET += 'abcdefghijklmnopqrstuvwxyz';ALPHABET += '0123456789-_';var str = '';for (var i = 0; i < length; ++i) {var rand = Math.floor(Math.random() * ALPHABET.length);str += ALPHABET.substring(rand, rand + 1);}return str;}random_str(6) // "NdQKOr"
Math对象还提供一系列三角函数方法
Math.sin():返回参数的正弦(参数为弧度值)Math.cos():返回参数的余弦(参数为弧度值)Math.tan():返回参数的正切(参数为弧度值)Math.asin():返回参数的反正弦(返回值为弧度值)Math.acos():返回参数的反余弦(返回值为弧度值)Math.atan():返回参数的反正切(返回值为弧度值)Math.sin(0) // 0Math.cos(0) // 1Math.tan(0) // 0Math.sin(Math.PI / 2) // 1Math.asin(1) // 1.5707963267948966Math.acos(1) // 0Math.atan(1) // 0.7853981633974483
9. Date 对象
- Date对象是 JavaScript 原生的时间库
- 它以国际标准时间(UTC)1970年1月1日00:00:00作为时间的零点
- 可以表示的时间范围是前后各1亿天(单位为毫秒)
普通函数的用法
// Date对象可以作为普通函数直接调用,返回一个代表当前时间的字符串Date()// "Wed Jun 17 2020 22:19:51 GMT+0800 (中国标准时间)"Date(2020, 1, 1)// "Wed Jun 17 2020 22:20:44 GMT+0800 (中国标准时间)"// 无论有没有参数,直接调用Date总是返回当前时间
构造函数的用法
var today = new Date();// Date实例有一个独特的地方// 其他对象求值的时候,都是默认调用.valueOf()方法// 但是Date实例求值的时候,默认调用的是toString()方法。这导致对Date实例求值,返回的是一个字符串// 代表该实例对应的时间var today = new Date();today// "Tue Dec 01 2015 09:34:43 GMT+0800 (CST)"// 等同于today.toString()// "Tue Dec 01 2015 09:34:43 GMT+0800 (CST)"// today是Date的实例,直接求值等同于调用toString方法// 作为构造函数时,Date对象可以接受多种格式的参数,返回一个该参数对应的时间实例// 参数为时间零点开始计算的毫秒数new Date(1378218728000)// Tue Sep 03 2013 22:32:08 GMT+0800 (CST)// 参数为日期字符串new Date('January 6, 2013');// Sun Jan 06 2013 00:00:00 GMT+0800 (CST)// 参数为多个整数,// 代表年、月、日、小时、分钟、秒、毫秒new Date(2013, 0, 1, 0, 0, 0, 0)// Tue Jan 01 2013 00:00:00 GMT+0800 (CST)// 关于Date构造函数的参数,有几点说明// 第一点,参数可以是负整数,代表1970年元旦之前的时间new Date(-1378218728000)// Fri Apr 30 1926 17:27:52 GMT+0800 (CST)// 第二点,只要是能被Date.parse()方法解析的字符串,都可以当作参数new Date('2013-2-15')new Date('2013/2/15')new Date('02/15/2013')new Date('2013-FEB-15')new Date('FEB, 15, 2013')new Date('FEB 15, 2013')new Date('February, 15, 2013')new Date('February 15, 2013')new Date('15 Feb 2013')new Date('15, February, 2013')// Fri Feb 15 2013 00:00:00 GMT+0800 (CST)// 第三,参数为年、月、日等多个整数时,年和月是不能省略的// 其他参数都可以省略的,也就是说,这时至少需要两个参数// 因为如果只使用“年”这一个参数,Date会将其解释为毫秒数new Date(2013)// Thu Jan 01 1970 08:00:02 GMT+0800 (CST)new Date(2013, 0)// Tue Jan 01 2013 00:00:00 GMT+0800 (CST)new Date(2013, 0, 1)// Tue Jan 01 2013 00:00:00 GMT+0800 (CST)new Date(2013, 0, 1, 0)// Tue Jan 01 2013 00:00:00 GMT+0800 (CST)new Date(2013, 0, 1, 0, 0, 0, 0)// Tue Jan 01 2013 00:00:00 GMT+0800 (CST)// 上面代码中,不管有几个参数,返回的都是2013年1月1日零点// 各个参数的取值范围如下年:使用四位数年份,比如2000。如果写成两位数或个位数,则加上1900,即10代表1910年,如果是负数,表示公元前。月:0表示一月,依次类推,11表示12月日:1到31小时:0到23分钟:0到59秒:0到59毫秒:0到999// 注意,月份从0开始计算,但是,天数从1开始计算// 另外,除了日期的默认值为1,小时、分钟、秒钟和毫秒的默认值都是0// 这些参数如果超出了正常范围,会被自动折算。比如,如果月设为15,就折算为下一年的4月new Date(2013, 15)// Tue Apr 01 2014 00:00:00 GMT+0800 (CST)new Date(2013, 0, 0)// Mon Dec 31 2012 00:00:00 GMT+0800 (CST)// 参数还可以使用负数,表示扣去的时间new Date(2013, -1)// Sat Dec 01 2012 00:00:00 GMT+0800 (CST)new Date(2013, 0, -1)// Sun Dec 30 2012 00:00:00 GMT+0800 (CST)// 上面代码中,分别对月和日使用了负数,表示从基准日扣去相应的时间
日期的运算
// 类型自动转换时,Date实例如果转为数值,则等于对应的毫秒数// 如果转为字符串,则等于对应的日期字符串var d1 = new Date(2000, 2, 1);var d2 = new Date(2000, 3, 1);d2 - d1// 2678400000d2 + d1// "Sat Apr 01 2000 00:00:00 GMT+0800 (CST)Wed Mar 01 2000 00:00:00 GMT+0800 (CST)"
静态方法
// Date.now方法返回当前时间距离时间零点(1970年1月1日 00:00:00 UTC)的毫秒数// 相当于 Unix 时间戳乘以1000Date.now() // 1364026285194// Date.parse方法用来解析日期字符串// 返回该时间距离时间零点(1970年1月1日 00:00:00)的毫秒数// 日期字符串应该符合 RFC 2822 和 ISO 8061 这两个标准// YYYY-MM-DDTHH:mm:ss.sssZ格式,其中最后的Z表示时区// 但是,其他格式也可以被解析Date.parse('Aug 9, 1995')Date.parse('January 26, 2011 13:51:50')Date.parse('Mon, 25 Dec 1995 13:30:00 GMT')Date.parse('Mon, 25 Dec 1995 13:30:00 +0430')Date.parse('2011-10-10')Date.parse('2011-10-10T14:48:00')// 如果解析失败,返回NaNDate.parse('xxx') // NaN// 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的实例对象,有几十个自己的方法,除了valueOf和toString,可以分为以下三类
- to类:从Date对象返回一个字符串,表示指定的时间
- get类:获取Date对象的日期和时间
- set类:设置Date对象的日期和时间
// valueOf方法返回实例对象距离时间零点(1970年1月1日00:00:00 UTC)对应的毫秒数// 该方法等同于getTime方法var d = new Date();d.valueOf() // 1362790014817d.getTime() // 1362790014817// 预期为数值的场合,Date实例会自动调用该方法,所以可以用下面的方法计算时间的间隔ar start = new Date();// ...var end = new Date();var elapsed = end - start;
to 类方法
// toString方法返回一个完整的日期字符串var d = new Date(2013, 0, 1);d.toString()// "Tue Jan 01 2013 00:00:00 GMT+0800 (CST)"d// "Tue Jan 01 2013 00:00:00 GMT+0800 (CST)"// toUTCString方法返回对应的 UTC 时间,也就是比北京时间晚8个小时var d = new Date(2013, 0, 1);d.toUTCString()// "Mon, 31 Dec 2012 16:00:00 GMT"// toISOString方法返回对应时间的 ISO8601 写法var d = new Date(2013, 0, 1);d.toISOString()// "2012-12-31T16:00:00.000Z"// toJSON方法返回一个符合 JSON 格式的 ISO 日期字符串// 与toISOString方法的返回结果完全相同var d = new Date(2013, 0, 1);d.toJSON()// "2012-12-31T16:00:00.000Z"// toDateString方法返回日期字符串(不含小时、分和秒)var d = new Date(2013, 0, 1);d.toDateString() // "Tue Jan 01 2013"// toTimeString方法返回时间字符串(不含年月日)var d = new Date(2013, 0, 1);d.toTimeString() // "00:00:00 GMT+0800 (CST)"// 本地时间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是一个配置对象dateObj.toLocaleString([locales[, options]])dateObj.toLocaleDateString([locales[, options]])dateObj.toLocaleTimeString([locales[, options]])var d = new Date(2013, 0, 1);d.toLocaleString('en-US') // "1/1/2013, 12:00:00 AM"d.toLocaleString('zh-CN') // "2013/1/1 上午12:00:00"d.toLocaleDateString('en-US') // "1/1/2013"d.toLocaleDateString('zh-CN') // "2013/1/1"d.toLocaleTimeString('en-US') // "12:00:00 AM"d.toLocaleTimeString('zh-CN') // "上午12:00:00"// options配置对象有以下属性dateStyle:可能的值为full、long、medium、short。timeStyle:可能的值为full、long、medium、short。month:可能的值为numeric、2-digit、long、short、narrow。year:可能的值为numeric、2-digit。weekday:可能的值为long、short、narrow。day、hour、minute、second:可能的值为numeric、2-digit。timeZone:可能的值为 IANA 的时区数据库。timeZooneName:可能的值为long、short。hour12:24小时周期还是12小时周期,可能的值为true、false// 用法如下var d = new Date(2013, 0, 1);d.toLocaleDateString('en-US', {weekday: 'long',year: 'numeric',month: 'long',day: 'numeric'})// "Tuesday, January 1, 2013"d.toLocaleDateString('en-US', {day: "2-digit",month: "long",year: "2-digit"});// "January 01, 13"d.toLocaleTimeString('en-US', {timeZone: 'UTC',timeZoneName: 'short'})// "4:00:00 PM UTC"d.toLocaleTimeString('en-US', {timeZone: 'Asia/Shanghai',timeZoneName: 'long'})// "12:00:00 AM China Standard Time"d.toLocaleTimeString('en-US', {hour12: false})// "00:00:00"d.toLocaleTimeString('en-US', {hour12: true})// "12:00:00 AM"
get 类方法
// Date对象提供了一系列get*方法,用来获取实例对象某个方面的值getTime():返回实例距离1970年1月1日00:00:00的毫秒数,等同于valueOf方法。getDate():返回实例对象对应每个月的几号(从1开始)。getDay():返回星期几,星期日为0,星期一为1,以此类推。getFullYear():返回四位的年份。getMonth():返回月份(0表示1月,11表示12月)。getHours():返回小时(0-23)。getMilliseconds():返回毫秒(0-999)。getMinutes():返回分钟(0-59)。getSeconds():返回秒(0-59)。getTimezoneOffset():返回当前时间与 UTC 的时区差异,以分钟表示,返回结果考虑到了夏令时因素// 所有这些get*方法返回的都是整数,不同方法返回值的范围不一样。分钟和秒:0 到 59小时:0 到 23星期:0(星期天)到 6(星期六)日期:1 到 31月份:0(一月)到 11(十二月)var d = new Date('January 6, 2013');d.getDate() // 6d.getMonth() // 0d.getFullYear() // 2013d.getTimezoneOffset() // -480下面是一个例子,计算本年度还剩下多少天// function leftDays() {var today = new Date();var endYear = new Date(today.getFullYear(), 11, 31, 23, 59, 59, 999);var msPerDay = 24 * 60 * 60 * 1000;return Math.round((endYear.getTime() - today.getTime()) / msPerDay);}// 上面这些get*方法返回的都是当前时区的时间// Date对象还提供了这些方法对应的 UTC 版本,用来返回 UTC 时间getUTCDate()getUTCFullYear()getUTCMonth()getUTCDay()getUTCHours()getUTCMinutes()getUTCSeconds()getUTCMilliseconds()var d = new Date('January 6, 2013');d.getDate() // 6d.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月var d = new Date ('January 6, 2013');d // Sun Jan 06 2013 00:00:00 GMT+0800 (CST)d.setDate(9) // 1357660800000d // Wed Jan 09 2013 00:00:00 GMT+0800 (CST)// set*方法的参数都会自动折算// 以setDate()为例,如果参数超过当月的最大天数,则向下一个月顺延// 如果参数是负数,表示从上个月的最后一天开始减去的天数var d1 = new Date('January 6, 2013');d1.setDate(32) // 1359648000000d1 // Fri Feb 01 2013 00:00:00 GMT+0800 (CST)var d2 = new Date ('January 6, 2013');d2.setDate(-1) // 1356796800000d2 // Sun Dec 30 2012 00:00:00 GMT+0800 (CST)// set类方法和get类方法,可以结合使用,得到相对时间var d = new Date();// 将日期向后推1000天d.setDate(d.getDate() + 1000);// 将时间设为6小时后d.setHours(d.getHours() + 6);// 将年份设为去年d.setFullYear(d.getFullYear() - 1);// set*系列方法除了setTime(),都有对应的 UTC 版本,即设置 UTC 时区的时间setUTCDate()setUTCFullYear()setUTCHours()setUTCMilliseconds()setUTCMinutes()setUTCMonth()setUTCSeconds()var d = new Date('January 6, 2013');d.getUTCHours() // 16d.setUTCHours(22) // 1357423200000d // Sun Jan 06 2013 06:00:00 GMT+0800 (CST)
10. RegExp 对象
- 正则表达式(regular expression)是一种表达文本模式(即字符串结构)的方法
- 有点像字符串的模板,常常用来按照“给定模式”匹配文本
- 新建正则表达式有两种方法
- 一种是使用字面量,以斜杠表示开始和结束
- 另一种是使用RegExp构造函数
var regex = /xyz/;var regex = new RegExp('xyz');// 前者的效率较高// 而且,前者比较便利和直观,所以实际应用中,基本上都采用字面量定义正则表达式// RegExp构造函数还可以接受第二个参数,表示修饰符(详细解释见下文)var regex = new RegExp('xyz', 'i');// 等价于var regex = /xyz/i;
实例属性
- 正则对象的实例属性分成两类
- 一类是修饰符相关,用于了解设置了什么修饰符
- 另一类是与修饰符无关的属性
RegExp.prototype.ignoreCase:返回一个布尔值,表示是否设置了i修饰符RegExp.prototype.global:返回一个布尔值,表示是否设置了g修饰符RegExp.prototype.multiline:返回一个布尔值,表示是否设置了m修饰符RegExp.prototype.flags:返回一个字符串,包含了已经设置的所有修饰符,按字母排序// 上面四个属性都是只读的var r = /abc/igm;r.ignoreCase // truer.global // truer.multiline // truer.flags // 'gim'RegExp.prototype.lastIndex:返回一个整数,表示下一次开始搜索的位置// 该属性可读写,但是只在进行连续搜索时有意义RegExp.prototype.source:返回正则表达式的字符串形式(不包括反斜杠),该属性只读var r = /abc/igm;r.lastIndex // 0r.source // "abc"
正则实例对象的test方法返回一个布尔值
- 表示当前模式是否能匹配参数字符串
/cat/.test('cats and dogs') // true// 如果正则表达式带有g修饰符,则每一次test方法都从上一次结束的位置开始向后匹配var r = /x/g;var s = '_x_x';r.lastIndex // 0r.test(s) // truer.lastIndex // 2r.test(s) // truer.lastIndex // 4r.test(s) // false// 上面代码的正则表达式使用了g修饰符,表示是全局搜索,会有多个结果// 接着,三次使用test方法,每一次开始搜索的位置都是上一次匹配的后一个位置// 带有g修饰符时,可以通过正则对象的lastIndex属性指定开始搜索的位置var r = /x/g;var s = '_x_x';r.lastIndex = 4;r.test(s) // falser.lastIndex // 0r.test(s)// 注意,带有g修饰符时,正则表达式内部会记住上一次的lastIndex属性// 这时不应该更换所要匹配的字符串,否则会有一些难以察觉的错误var r = /bb/g;r.test('bb') // truer.test('-bb-') // false// 上面代码中,由于正则表达式r是从上一次的lastIndex位置开始匹配// 导致第二次执行test方法时出现预期以外的结果// 如果正则模式是一个空字符串,则匹配所有字符串new RegExp('').test('abc')// true
正则实例对象的exec()方法,用来返回匹配结果
- 如果发现匹配,就返回一个数组,成员是匹配成功的子字符串
- 否则返回null
var s = '_x_x';var r1 = /x/;var r2 = /y/;r1.exec(s) // ["x"]r2.exec(s) // null// 组匹配var s = '_x_x';var r = /_(x)/;r.exec(s) // ["_x", "x"]// exec()方法的返回数组还包含以下两个属性:input:整个原字符串index:模式匹配成功的开始位置(从0开始计数)var r = /a(b+)a/;var arr = r.exec('_abbba_aba_');arr // ["abbba", "bbb"]arr.index // 1arr.input // "_abbba_aba_"// 上面代码中的index属性等于1,是因为从原字符串的第二个位置开始匹配成功。// 如果正则表达式加上g修饰符,则可以使用多次exec()方法// 下一次搜索的位置从上一次匹配成功结束的位置开始var reg = /a/g;var str = 'abc_abc_abc'var r1 = reg.exec(str);r1 // ["a"]r1.index // 0reg.lastIndex // 1var r2 = reg.exec(str);r2 // ["a"]r2.index // 4reg.lastIndex // 5var r3 = reg.exec(str);r3 // ["a"]r3.index // 8reg.lastIndex // 9var r4 = reg.exec(str);r4 // nullreg.lastIndex // 0// 利用g修饰符允许多次匹配的特点,可以用一个循环完成全部匹配var reg = /a/g;var str = 'abc_abc_abc'while(true) {var match = reg.exec(str);if (!match) break;console.log('#' + match.index + ':' + match[0]);}// #0:a// #4:a// #8:a
字符串的实例方法
- String.prototype.match():返回一个数组,成员是所有匹配的子字符串
- String.prototype.search():按照给定的正则表达式进行搜索,返回一个整数,表示匹配开始的位置
- String.prototype.replace():按照给定的正则表达式进行替换,返回替换后的字符串
- String.prototype.split():按照给定规则进行字符串分割,返回一个数组,包含分割后的各个成员
字符串实例对象的match方法对字符串进行正则匹配,返回匹配结果
var s = '_x_x';var r1 = /x/;var r2 = /y/;s.match(r1) // ["x"]s.match(r2) // null// 如果正则表达式带有g修饰符,则该方法与正则对象的exec方法行为不同// 会一次性返回所有匹配成功的结果var s = 'abba';var r = /a/g;s.match(r) // ["a", "a"]r.exec(s) // ["a"]// 设置正则表达式的lastIndex属性,对match方法无效// 匹配总是从字符串的第一个字符开始var r = /a|b/g;r.lastIndex = 7;'xaxb'.match(r) // ['a', 'b']r.lastIndex // 0
字符串对象的search方法,返回第一个满足条件的匹配结果在整个字符串中的位置
- 如果没有任何匹配,则返回-1
'_x_x'.search(/x/)// 1
字符串对象的replace方法可以替换匹配的值,它接受两个参数
- 第一个是正则表达式,表示搜索模式
- 第二个是替换的内容
str.replace(search, replacement)// 正则表达式如果不加g修饰符,就替换第一个匹配成功的值,否则替换所有匹配成功的值'aaa'.replace('a', 'b') // "baa"'aaa'.replace(/a/, 'b') // "baa"'aaa'.replace(/a/g, 'b') // "bbb"// replace方法的一个应用,就是消除字符串首尾两端的空格var str = ' #id div.class ';str.replace(/^\s+|\s+$/g, '')// "#id div.class"// replace方法的第二个参数可以使用美元符号$,用来指代所替换的内容$&:匹配的子字符串。$`:匹配结果前面的文本$':匹配结果后面的文本$n:匹配成功的第n组内容,n是从1开始的自然数$$:指代美元符号$'hello world'.replace(/(\w+)\s(\w+)/, '$2 $1')// "world hello"'abc'.replace('b', '[$`-$&-$\']')// "a[a-b-c]c"// replace方法的第二个参数还可以是一个函数,将每一个匹配内容替换为函数返回值'3 and 5'.replace(/[0-9]+/g, function (match) {return 2 * match;})// "6 and 10"var a = 'The quick brown fox jumped over the lazy dog.';var pattern = /quick|brown|lazy/ig;a.replace(pattern, function replacer(match) {return match.toUpperCase();});// The QUICK BROWN fox jumped over the LAZY dog.// 作为replace方法第二个参数的替换函数,可以接受多个参数// 第一个参数是捕捉到的内容// 第二个参数是捕捉到的组匹配(有多少个组匹配,就有多少个对应的参数)// 此外,最后还可以添加两个参数// 倒数第二个参数是捕捉到的内容在整个字符串中的位置(比如从第五个位置开始)// 最后一个参数是原字符串var prices = {'p1': '$1.99','p2': '$9.99','p3': '$5.00'};var template = '<span id="p1"></span>'+ '<span id="p2"></span>'+ '<span id="p3"></span>';template.replace(/(<span id=")(.*?)(">)(<\/span>)/g,function(match, $1, $2, $3, $4){return $1 + $2 + $3 + prices[$2] + $4;});// "<span id="p1">$1.99</span><span id="p2">$9.99</span><span id="p3">$5.00</span>"
字符串对象的split方法按照正则规则分割字符串,返回一个由分割后的各个部分组成的数组
str.split(separator, [limit])// 该方法接受两个参数,第一个参数是正则表达式,表示分隔规则,第二个参数是返回数组的最大成员数// 非正则分隔'a, b,c, d'.split(',')// [ 'a', ' b', 'c', ' d' ]// 正则分隔,去除多余的空格'a, b,c, d'.split(/, */)// [ 'a', 'b', 'c', 'd' ]// 指定返回数组的最大成员'a, b,c, d'.split(/, */, 2)[ 'a', 'b' ]// 例一'aaa*a*'.split(/a*/)// [ '', '*', '*' ]// 例二'aaa**a*'.split(/a*/)// ["", "*", "*", "*"]// 上面代码的分割规则是0次或多次的a,由于正则默认是贪婪匹配// 所以例一的第一个分隔符是aaa,第二个分割符是a// 将字符串分成三个部分,包含开始处的空字符串// 例二的第一个分隔符是aaa,第二个分隔符是0个a(即空字符)// 第三个分隔符是a,所以将字符串分成四个部分// 如果正则表达式带有括号,则括号匹配的部分也会作为数组成员返回'aaa*a*'.split(/(a*)/)// [ '', 'aaa', '*', 'a', '*' ]
正则表达式的规则:https://wangdoc.com/javascript/stdlib/regexp.html
- 看阮一峰老师写的吧,后面我没看完,下次再来看
字面量字符和元字符
// 字面量字符/dog/.test('old dog') // true// 点字符/c.t/// 位置字符// test必须出现在开始位置/^test/.test('test123') // true// test必须出现在结束位置/test$/.test('new test') // true// 从开始位置到结束位置只有test/^test$/.test('test') // true/^test$/.test('test test') // false// 选择符(|)/11|22/.test('911') // true// 匹配fred、barney、betty之中的一个/fred|barney|betty//a( |\t)b/.test('a\tb') // true
转义符
/1+1/.test('1+1')// false/1\+1/.test('1+1')// true// 正则表达式中,需要反斜杠转义的,一共有12个字符^ . [ $ ( ) | * + ? { \(new RegExp('1\+1')).test('1+1')// false(new RegExp('1\\+1')).test('1+1')// true
特殊字符
\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 字符。
字符类
/[abc]/.test('hello world') // false/[abc]/.test('apple') // true// 脱字符(^)/[^abc]/.test('bbc news') // true/[^abc]/.test('bbc') // falsevar s = 'Please yes\nmake my day!';s.match(/yes.*day/) // nulls.match(/yes[^]*day/) // [ 'yes\nmake my day']// 连字符(-)/a-z/.test('b') // false/[a-z]/.test('b') // true[0-9.,][0-9a-fA-F][a-zA-Z0-9-][1-31]var str = "\u0130\u0131\u0132";/[\u0128-\uFFFF]/.test(str)// true/[A-z]/.test('\\') // true
预定义模式
\d 匹配0-9之间的任一数字,相当于[0-9]。\D 匹配所有0-9以外的字符,相当于[^0-9]。\w 匹配任意的字母、数字和下划线,相当于[A-Za-z0-9_]。\W 除所有字母、数字和下划线以外的字符,相当于[^A-Za-z0-9_]。\s 匹配空格(包括换行符、制表符、空格符等),相等于[ \t\r\n\v\f]。\S 匹配非空格的字符,相当于[^ \t\r\n\v\f]。\b 匹配词的边界。\B 匹配非词边界,即在词的内部// 一些例子// \s 的例子/\s\w*/.exec('hello world') // [" world"]// \b 的例子/\bworld/.test('hello world') // true/\bworld/.test('hello-world') // true/\bworld/.test('helloworld') // false// \B 的例子/\Bworld/.test('hello-world') // false/\Bworld/.test('helloworld') // true// 通常,正则表达式遇到换行符(\n)就会停止匹配var html = "<b>Hello</b>\n<i>world!</i>";/.*/.exec(html)[0]// "<b>Hello</b>"var html = "<b>Hello</b>\n<i>world!</i>";/[\S\s]*/.exec(html)[0]// "<b>Hello</b>\n<i>world!</i>"
重复类
/lo{2}k/.test('look') // true/lo{2,5}k/.test('looook') // true
量词符
// t 出现0次或1次/t?est/.test('test') // true/t?est/.test('est') // true// t 出现1次或多次/t+est/.test('test') // true/t+est/.test('ttest') // true/t+est/.test('est') // false// t 出现0次或多次/t*est/.test('test') // true/t*est/.test('ttest') // true/t*est/.test('tttest') // true/t*est/.test('est') // true
贪婪模式和非贪婪模式
// 贪婪模式var s = 'aaa';s.match(/a+/) // ["aaa"]// 非贪婪模式var s = 'aaa';s.match(/a+?/) // ["a"]'abb'.match(/ab*/) // ["abb"]'abb'.match(/ab*?/) // ["a"]'abb'.match(/ab?/) // ["ab"]'abb'.match(/ab??/) // ["a"]
修饰符
// 单个修饰符var regex = /test/i;// 多个修饰符var regex = /test/ig;// g 修饰符var regex = /b/;var str = 'abba';regex.test(str); // trueregex.test(str); // trueregex.test(str); // truevar regex = /b/g;var str = 'abba';regex.test(str); // trueregex.test(str); // trueregex.test(str); // false// i 修饰符/abc/.test('ABC') // false/abc/i.test('ABC') // true// m 修饰符/world$/.test('hello world\n') // false/world$/m.test('hello world\n') // true/^b/m.test('a\nb') // true
11. JSON 对象
JSON 格式
// 合法的 JSON 对象["one", "two", "three"]{ "one": 1, "two": 2, "three": 3 }{"names": ["张三", "李四"] }[ { "name": "张三"}, {"name": "李四"} ]// 不合法的 JSON{ name: "张三", 'age': 32 } // 属性名必须使用双引号[32, 64, 128, 0xFFF] // 不能使用十六进制值{ "name": "张三", "age": undefined } // 不能使用 undefined{ "name": "张三","birthday": new Date('Fri, 26 Aug 2011 07:13:10 GMT'),"getName": function () {return this.name;}} // 属性值不能使用函数和日期对象
JSON.stringify()
JSON.stringify('abc') // ""abc""JSON.stringify(1) // "1"JSON.stringify(false) // "false"JSON.stringify([]) // "[]"JSON.stringify({}) // "{}"JSON.stringify([1, "false", false])// '[1,"false",false]'JSON.stringify({ name: "张三" })// '{"name":"张三"}'// 注意,对于原始类型的字符串,转换结果会带双引号JSON.stringify('foo') === "foo" // falseJSON.stringify('foo') === "\"foo\"" // true// 如果不是内层的双引号,将来还原的时候,引擎就无法知道原始值是布尔值还是字符串JSON.stringify(false) // "false"JSON.stringify('false') // "\"false\""// 如果对象的属性是undefined、函数或 XML 对象,该属性会被JSON.stringify过滤var obj = {a: undefined,b: function () {}};JSON.stringify(obj) // "{}"// 如果数组的成员是undefined、函数或 XML 对象,则这些值被转成nullvar arr = [undefined, function () {}];JSON.stringify(arr) // "[null,null]"// 正则对象会被转成空对象JSON.stringify(/foo/) // "{}"JSON.stringify方法会忽略对象的不可遍历的属性var obj = {};Object.defineProperties(obj, {'foo': {value: 1,enumerable: true},'bar': {value: 2,enumerable: false}});JSON.stringify(obj); // "{"foo":1}"
JSON.stringify方法还可以接受一个数组,作为第二个参数,指定需要转成字符串的属性
var obj = {'prop1': 'value1','prop2': 'value2','prop3': 'value3'};var selectedProperties = ['prop1', 'prop2'];JSON.stringify(obj, selectedProperties)// "{"prop1":"value1","prop2":"value2"}"// 这个类似白名单的数组,只对对象的属性有效,对数组无效JSON.stringify(['a', 'b'], ['0'])// "["a","b"]"JSON.stringify({0: 'a', 1: 'b'}, ['0'])// "{"0":"a"}"// 第二个参数还可以是一个函数,用来更改JSON.stringify的返回值function f(key, value) {if (typeof value === "number") {value = 2 * value;}return value;}JSON.stringify({ a: 1, b: 2 }, f)// '{"a": 2,"b": 4}'// 注意,这个处理函数是递归处理所有的键var o = {a: {b: 1}};function f(key, value) {console.log("["+ key +"]:" + value);return value;}JSON.stringify(o, f)// []:[object Object]// [a]:[object Object]// [b]:1// '{"a":{"b":1}}'// 递归处理中,每一次处理的对象,都是前一次返回的值var o = {a: 1};function f(key, value) {if (typeof value === 'object') {return {b: 2};}return value * 2;}JSON.stringify(o, f)// "{"b": 4}"// 如果处理函数返回undefined或没有返回值,则该属性会被忽略function f(key, value) {if (typeof(value) === "string") {return undefined;}return value;}JSON.stringify({ a: "abc", b: 123 }, f)// '{"b": 123}'
JSON.stringify还可以接受第三个参数,用于增加返回的 JSON 字符串的可读性。如果是数字,表示每个属性前面添加的空格(最多不超过10个);如果是字符串(不超过10个字符),则该字符串会添加在每行前面
JSON.stringify({ p1: 1, p2: 2 }, null, 2);/*"{"p1": 1,"p2": 2}"*/JSON.stringify({ p1:1, p2:2 }, null, '|-');/*"{|-"p1": 1,|-"p2": 2}"*/// 参数对象的 toJSON 方法// 如果参数对象有自定义的toJSON方法// 那么JSON.stringify会使用这个方法的返回值作为参数// 而忽略原对象的其他属性
JSON.parse()
JSON.parse('{}') // {}JSON.parse('true') // trueJSON.parse('"foo"') // "foo"JSON.parse('[1, 5, "false"]') // [1, 5, "false"]JSON.parse('null') // nullvar o = JSON.parse('{"name": "张三"}');o.name // 张三// 如果传入的字符串不是有效的 JSON 格式,JSON.parse方法将报错JSON.parse("'String'") // illegal single quotes// SyntaxError: Unexpected token ILLEGAL// 为了处理解析错误,可以将JSON.parse方法放在try...catch代码块中try {JSON.parse("'String'");} catch(e) {console.log('parsing error');}// JSON.parse方法可以接受一个处理函数,作为第二个参数,用法与JSON.stringify方法类似function f(key, value) {if (key === 'a') {return value + 10;}return value;}JSON.parse('{"a": 1, "b": 2}', f)// {a: 11, b: 2}
二、面向对象编程
1. 实例对象与 new 命令
var Vehicle = function (p) {this.price = p;};var v = new Vehicle(500);// 为了与普通函数区别,构造函数名字的第一个字母通常大写
构造函数的特点有两个。
- 函数体内部使用了this关键字,代表了所要生成的对象实例。
- 生成对象的时候,必须使用new命令。
使用new命令时,它后面的函数依次执行下面的步骤。
- 创建一个空对象,作为将要返回的对象实例。
- 将这个空对象的原型,指向构造函数的prototype属性。
- 将这个空对象赋值给函数内部的this关键字。
- 开始执行构造函数内部的代码。
// 如果构造函数内部有return语句,而且return后面跟着一个对象,new命令会返回return语句指定的对象// 否则,就会不管return语句,返回this对象var Vehicle = function () {this.price = 1000;return 1000;};(new Vehicle()) === 1000// falsevar Vehicle = function (){this.price = 1000;return { price: 2000 };};(new Vehicle()).price// 2000
new.target
function f() {console.log(new.target === f);}f() // falsenew f() // true// 使用这个属性,可以判断函数调用的时候,是否使用new命令。function f() {if (!new.target) {throw new Error('请使用 new 命令调用!');}// ...}f() // Uncaught Error: 请使用 new 命令调用!
Object.create() 创建实例对象
// 以这个现有的对象作为模板,生成新的实例对象,这时就可以使用Object.create()方法var person1 = {name: '张三',age: 38,greeting: function() {console.log('Hi! I\'m ' + this.name + '.');}};var person2 = Object.create(person1);person2.name // 张三person2.greeting() // Hi! I'm 张三.
2. this 关键字
它总是返回一个对象
- 简单说,this就是属性或方法“当前”所在的对象
var person = {name: '张三',describe: function () {return '姓名:'+ this.name;}};person.describe()// "姓名:张三"// 网页编程的例子<input type="text" name="age" size=3 onChange="validate(this, 18, 99);"><script>function validate(obj, lowval, hival){if ((obj.value < lowval) || (obj.value > hival))console.log('Invalid Value!');}</script>// 上面代码是一个文本输入框,每当用户输入一个值,就会调用onChange回调函数,验证这个值是否在指定范围。// 浏览器会向回调函数传入当前对象,因此this就代表传入当前对象(即文本框)// 然后就可以从this.value上面读到用户的输入值
全局环境使用this,它指的就是顶层对象window。
this === window // truefunction f() {console.log(this === window);}f() // true
构造函数中的this,指的是实例对象。
var Obj = function (p) {this.p = p;};
如果对象的方法里面包含this,this的指向就是方法运行时所在的对象。该方法赋值给另一个对象,就会改变this的指向。
var obj ={foo: function () {console.log(this);}};obj.foo() // obj
但是,下面这几种用法,都会改变this的指向。
// 情况一(obj.foo = obj.foo)() // window// 情况二(false || obj.foo)() // window// 情况三(1, obj.foo)() // window
如果this所在的方法不在对象的第一层,这时this只是指向当前一层的对象,而不会继承更上面的层。
var a = {p: 'Hello',b: {m: function() {console.log(this.p);}}};a.b.m() // undefinedvar a = {b: {m: function() {console.log(this.p);},p: 'Hello'}};var hello = a.b.m;hello() // undefined
使用注意点
// 避免多层 this// 由于this的指向是不确定的,所以切勿在函数中包含多层的this。var o = {f1: function () {console.log(this);var f2 = function () {console.log(this);}();}}o.f1()// Object// Window// 避免数组处理方法中的 this// 数组的map和foreach方法,允许提供一个函数作为参数。这个函数内部不应该使用this。var o = {v: 'hello',p: [ 'a1', 'a2' ],f: function f() {this.p.forEach(function (item) {console.log(this.v + ' ' + item);});}}o.f()// undefined a1// undefined a2// 避免回调函数中的 this// 回调函数中的this往往会改变指向,最好避免使用。var o = new Object();o.f = function () {console.log(this === o);}// jQuery 的写法$('#button').on('click', o.f);// 上面代码中,点击按钮以后,控制台会显示false。原因是此时this不再指向o对象// 而是指向按钮的 DOM 对象,因为f方法是在按钮对象的环境中被调用的。// 这种细微的差别,很容易在编程中忽视,导致难以察觉的错误。
绑定 this 的方法
- this的动态切换,固然为 JavaScript 创造了巨大的灵活性,但也使得编程变得困难和模糊。
- 有时,需要把this固定下来,避免出现意想不到的情况。JavaScript 提供了call、apply、bind这三个方法
- 来切换/固定this的指向。
// Function.prototype.call()var obj = {};var f = function () {return this;};f() === window // truef.call(obj) === obj // true// call方法的参数,应该是一个对象。如果参数为空、null和undefined,则默认传入全局对象。var n = 123;var obj = { n: 456 };function a() {console.log(this.n);}a.call() // 123a.call(null) // 123a.call(undefined) // 123a.call(window) // 123a.call(obj) // 456// 如果call方法的参数是一个原始值,那么这个原始值会自动转成对应的包装对象,然后传入call方法。var f = function () {return this;};f.call(5)// Number {[[PrimitiveValue]]: 5}// call方法的一个应用是调用对象的原生方法。var obj = {};obj.hasOwnProperty('toString') // false// 覆盖掉继承的 hasOwnProperty 方法obj.hasOwnProperty = function () {return true;};obj.hasOwnProperty('toString') // trueObject.prototype.hasOwnProperty.call(obj, 'toString') // false// Function.prototype.apply()// apply方法的作用与call方法类似,也是改变this指向,然后再调用该函数。// 唯一的区别就是,它接收一个数组作为函数执行时的参数,使用格式如下。// func.apply(thisValue, [arg1, arg2, ...])// 找出数组最大元素var a = [10, 2, 4, 15, 9];Math.max.apply(null, a) // 15// 将数组的空元素变为undefinedArray.apply(null, ['a', ,'b'])// [ 'a', undefined, 'b' ]// 转换类似数组的对象Array.prototype.slice.apply({0: 1, length: 1}) // [1]Array.prototype.slice.apply({0: 1}) // []Array.prototype.slice.apply({0: 1, length: 2}) // [1, undefined]Array.prototype.slice.apply({length: 1}) // [undefined]// 绑定回调函数的对象var o = new Object();o.f = function () {console.log(this === o);}var f = function (){o.f.apply(o);// 或者 o.f.call(o);};// jQuery 的写法$('#button').on('click', f);// Function.prototype.bind()// bind()方法用于将函数体内的this绑定到某个对象,然后返回一个新函数。var d = new Date();d.getTime() // 1481869925657var print = d.getTime;print() // Uncaught TypeError: this is not a Date object.var print = d.getTime.bind(d);print() // 1481869925657// bind方法的参数就是所要绑定this的对象,下面是一个更清晰的例子。var counter = {count: 0,inc: function () {this.count++;}};var func = counter.inc.bind(counter);func();counter.count // 1// 如果bind()方法的第一个参数是null或undefined,等于将this绑定到全局对象// 函数运行时this指向顶层对象(浏览器为window)。function add(x, y) {return x + y;}var plus5 = add.bind(null, 5);plus5(10) // 15
bind() 方法注意点
// bind()方法每运行一次,就返回一个新函数,这会产生一些问题。// 比如,监听事件的时候,不能写成下面这样。element.addEventListener('click', o.m.bind(o));// 上面代码中,click事件绑定bind()方法生成的一个匿名函数。这样会导致无法取消绑定// 所以下面的代码是无效的。element.removeEventListener('click', o.m.bind(o));// 正确的方法是写成下面这样:var listener = o.m.bind(o);element.addEventListener('click', listener);// ...element.removeEventListener('click', listener);// 结合回调函数使用var counter = {count: 0,inc: function () {'use strict';this.count++;}};function callIt(callback) {callback();}callIt(counter.inc.bind(counter));counter.count // 1// 结合call()方法使用[1, 2, 3].slice(0, 1) // [1]// 等同于Array.prototype.slice.call([1, 2, 3], 0, 1) // [1]// 用bind()方法改写var slice = Function.prototype.call.bind(Array.prototype.slice);slice([1, 2, 3], 0, 1) // [1]
三、章节链接
「@浪里淘沙的小法师」
