一、标准库
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 // true
obj instanceof Number // true
var obj = Object('foo');
obj instanceof Object // true
obj instanceof String // true
var obj = Object(true);
obj instanceof Object // true
obj instanceof Boolean // true
// 参数为一个对象
var arr = [];
var obj = Object(arr); // 返回原数组
obj === arr // true
var value = {};
var obj = Object(value) // 返回原对象
obj === value // true
var fn = function () {};
var obj = Object(fn); // 返回原函数
obj === fn // true
// 写一个判断变量是否为对象的函数
function isObject(value) {
return value === Object(value);
}
isObject([]) // true
isObject(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 // true
var 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 // 2
Object.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 // true
var 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({}) // true
type.isNumber(NaN) // true
type.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 Bethune
person.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') // true
obj.hasOwnProperty('toString') // false
// 上面代码中,对象obj自身具有p属性,所以返回true
toString属性是继承的,所以返回false
2. 属性描述对象
- JavaScript 提供了一个内部数据结构,用来描述对象的属性,控制它的行为
- 比如该属性是否可写、可遍历等等
- 这个内部数据结构称为“属性描述对象”(attributes object)
- 每个属性都有自己对应的属性描述对象,保存该属性的一些元信息
// 属性描述对象的一个例子
{
value: 123, // 该属性的属性值,默认为undefined
writable: false, // 表示属性值(value)是否可改变(即是否可写),默认为true
enumerable: true, // 表示该属性是否可遍历
configurable: false, // 表示可配置性,默认为true
get: undefined, // get是一个函数,表示该属性的取值函数(getter),默认为undefined
set: 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 // 123
obj.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 // 123
obj.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 value
Object.defineProperty(obj, 'p', {
writable: true,
get: function() { return 456; }
});
// TypeError: Invalid property descriptor.
// Cannot both specify accessors and a value or writable attribute
// 上面代码中,同时定义了get属性和value属性,以及将writable属性设为true,就会报错。
// Object.defineProperty()和Object.defineProperties()参数里面的属性描述对象中,
// writable、configurable、enumerable这三个属性的默认值都为false
var obj = {};
Object.defineProperty(obj, 'foo', {});
Object.getOwnPropertyDescriptor(obj, 'foo')
// {
// value: undefined,
// writable: false,
// enumerable: false,
// configurable: false
// }
// 实例对象的propertyIsEnumerable()方法返回一个布尔值,用来判断某个属性是否可遍历
// 注意,这个方法只能用于判断对象自身的属性,对于继承的属性一律返回false
var obj = {};
obj.p = 123;
obj.propertyIsEnumerable('p') // true
obj.propertyIsEnumerable('toString') // false
// 上面代码中,obj.p是可遍历的,而obj.toString是继承的属性
元属性
- 属性描述对象的各个属性称为“元属性”,因为它们可以看作是控制属性的属性
// value属性是目标属性的值
var obj = {};
obj.p = 123;
Object.getOwnPropertyDescriptor(obj, 'p').value
// 123
Object.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 // 37
obj.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 // 123
for (var key in obj) {
console.log(key);
}
// undefined
Object.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: p
Object.defineProperty(obj, 'p', {writable: true})
// TypeError: Cannot redefine property: p
Object.defineProperty(obj, 'p', {enumerable: true})
// TypeError: Cannot redefine property: p
Object.defineProperty(obj, 'p', {configurable: true})
// TypeError: Cannot redefine property: p
// 注意,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 // 5
obj.next = 10;
obj.next // 10
obj.next = 5;
// Uncaught Error: 新的值必须大于当前值
// 上面代码中,next属性的存值函数和取值函数,都依赖于内部属性$n
对象的拷贝
// 将一个对象的所有属性,拷贝到另一个对象,可以用下面的方法实现
var extend = function (to, from) {
for (var property in from) {
to[property] = from[property];
}
return to;
}
extend({}, {
a: 1
})
// {a: 1}
// 上面这个方法的问题在于,如果遇到存取器定义的属性,会只拷贝值
extend({}, {
get a() { return 1 }
})
// {a: 1}
// 为了解决这个问题,我们可以通过Object.defineProperty方法来拷贝属性
var extend = function (to, from) {
for (var property in from) {
if (!from.hasOwnProperty(property)) continue;
Object.defineProperty(
to,
property,
Object.getOwnPropertyDescriptor(from, property)
);
}
return to;
}
extend({}, { get a(){ return 1 } })
// { get a(){ return 1 } })
冻结对象的读写状态,防止对象被改变,有三种冻结方式
- 最弱的一种是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) // true
Object.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方法也返回false
var 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 // undefined
delete obj.p // false
obj.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返回false
var obj = {
p: 'hello'
};
Object.freeze(obj);
Object.isSealed(obj) // true
Object.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 // 2
arr // [ empty x 2 ]
// 生成一个两个成员的数组,每个位置都是空值
var arr = new Array(2);
// 等同于
var arr = Array(2);
// 考虑到语义性,以及与其他构造函数用户保持一致,建议总是加上new
// rray()作为构造函数,行为很不一致
因此,不建议使用它生成新数组,直接使用数组字面量是更好的做法
// bad
var arr = new Array(1, 2);
// good
var arr = [1, 2];
var a = new Array(3);
var b = [undefined, undefined, undefined];
a.length // 3
b.length // 3
a[0] // undefined
b[0] // undefined
0 in a // false
0 in b // true
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) // 1
arr.push('a') // 2
arr.push(true, {}) // 4
arr // [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'); // 4
a // ['x', 'a', 'b', 'c']
// unshift()方法可以接受多个参数,这些参数都会添加到目标数组头部
var arr = [ 'c', 'd' ];
arr.unshift('a', 'b') // 4
arr // [ 'a', 'b', 'c', 'd' ]
join()
// join()方法以指定参数作为分隔符,将所有数组成员连接为一个字符串返回
如果不提供参数,默认用逗号分隔
var a = [1, 2, 3, 4];
a.join(' ') // '1 2 3 4'
a.join(' | ') // "1 | 2 | 3 | 4"
a.join() // "1,2,3,4"
// 如果数组成员是undefined或null或空位,会被转成空字符串
[undefined, null].join('#')
// '#'
['a',, 'b'].join('-')
// 'a--b'
// 通过call方法,这个方法也可以用于字符串或类似数组的对象
Array.prototype.join.call('hello', '-')
// "h-e-l-l-o"
var obj = { 0: 'a', 1: 'b', length: 2 };
Array.prototype.join.call(obj, '-')
// 'a-b'
concat()
// concat方法用于多个数组的合并
// 它将新数组的成员,添加到原数组成员的后部,然后返回一个新数组,原数组不变
['hello'].concat(['world'])
// ["hello", "world"]
['hello'].concat(['world'], ['!'])
// ["hello", "world", "!"]
[].concat({a: 1}, {b: 2})
// [{ a: 1 }, { b: 2 }]
[2].concat({a: 1})
// [2, {a: 1}]
// 除了数组作为参数,concat也接受其他类型的值作为参数,添加到目标数组尾部。
[1, 2, 3].concat(4, 5, 6)
// [1, 2, 3, 4, 5, 6]
// 如果数组成员包括对象,concat方法返回当前数组的一个浅拷贝
var obj = { a: 1 };
var oldArray = [obj];
var newArray = oldArray.concat();
obj.a = 2;
newArray[0].a // 2
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方法的第二个参数可以设为0
var a = [1, 1, 1];
a.splice(1, 0, 2) // []
a // [1, 2, 1, 1]
// 如果只提供第一个参数,等同于将原数组在指定位置拆分成两个数组
var a = [1, 2, 3, 4];
a.splice(2) // [3, 4]
a // [1, 2]
sort()
// sort方法对数组成员进行排序,默认是按照字典顺序排序
// 排序后,原数组将被改变
['d', 'c', 'b', 'a'].sort()
// ['a', 'b', 'c', 'd']
[4, 3, 2, 1].sort()
// [1, 2, 3, 4]
[11, 101].sort()
// [101, 11]
[10111, 1101, 111].sort()
// [10111, 1101, 111]
// 上面代码的最后两个例子,需要特殊注意
// sort()方法不是按照大小排序,而是按照字典顺序
// 也就是说,数值会被先转成字符串,再按照字典顺序进行比较,所以101排在11的前面
// 如果想让sort方法按照自定义方式排序,可以传入一个函数作为参数
[10111, 1101, 111].sort(function (a, b) {
return a - b;
})
// [111, 1101, 10111]
// 上面代码中,sort的参数函数本身接受两个参数,表示进行比较的两个数组成员
[
{ 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
// 否则返回false
var arr = [1, 2, 3, 4, 5];
arr.some(function (elem, index, arr) {
return elem >= 3;
});
// true
// every方法是所有成员的返回值都是true,整个every方法才返回true
// 否则返回false
var 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方法返回给定元素在数组中第一次出现的位置,如果没有出现则返回-1
var a = ['a', 'b', 'c'];
a.indexOf('b') // 1
a.indexOf('y') // -1
// indexOf方法还可以接受第二个参数,表示搜索的开始位置
['a', 'b', 'c'].indexOf('a', 1) // -1
// lastIndexOf方法返回给定元素在数组中最后一次出现的位置,如果没有出现则返回-1
var a = [2, 5, 9, 2];
a.lastIndexOf(2) // 3
a.lastIndexOf(7) // -1
// 注意,这两个方法不能用来搜索NaN的位置,即它们无法确定数组成员是否包含NaN
[NaN].indexOf(NaN) // -1
[NaN].lastIndexOf(NaN) // -1
// 这是因为这两个方法内部,使用严格相等运算符(===)进行比较
// 而NaN是唯一一个不等于自身的值
链式使用
var users = [
{name: 'tom', email: 'tom@example.com'},
{name: 'peter', email: 'peter@example.com'}
];
users
.map(function (user) {
return user.email;
})
.filter(function (email) {
return /^t/.test(email);
})
.forEach(function (email) {
console.log(email);
});
// "tom@example.com"
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 // false
v2 === 'abc' // false
v3 === true // false
// Number、String和Boolean这三个原生对象
// 如果不作为构造函数调用(即调用时不加new),而是作为普通函数调用
// 常常用于将任意类型的值转为数值、字符串和布尔值
// 字符串转为数值
Number('123') // 123
// 数值转为字符串
String(123) // "123"
// 数值转为布尔值
Boolean(123) // true
// 实例方法
valueOf()方法返回包装对象实例对应的原始类型的值
new Number(123).valueOf() // 123
new 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 // 3
var str = 'abc';
str.length // 3
// 等同于
var strObj = new String(str)
// String {
// 0: "a", 1: "b", 2: "c", length: 3, [[PrimitiveValue]]: "abc"
// }
strObj.length // 3
// 上面代码中,字符串abc的包装对象提供了多个属性,length只是其中之一
// 自动转换生成的包装对象是只读的,无法修改。所以,字符串无法添加新属性
var s = 'Hello World';
s.x = 123;
s.x // undefined
自定义方法
String.prototype.double = function () {
return this.valueOf() + this.valueOf();
};
'abc'.double()
// abcabc
Number.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对应的包装对象实例,布尔运算结果也是true
if (new Boolean(false)) {
console.log('true');
} // true
if (new Boolean(false).valueOf()) {
console.log('true');
} // 无输出
Boolean 函数的类型转换作用
Boolean(undefined) // false
Boolean(null) // false
Boolean(0) // false
Boolean('') // false
Boolean(NaN) // false
Boolean(1) // true
Boolean('false') // true
Boolean([]) // true
Boolean({}) // true
Boolean(function () {}) // true
Boolean(/foo/) // true
// 使用双重的否运算符(!)也可以将任意值转为对应的布尔值
!!undefined // false
!!null // false
!!0 // false
!!'' // false
!!NaN // false
!!1 // true
!!'false' // true
!![] // true
!!{} // true
!!function(){} // true
!!/foo/ // true
// 对于一些特殊值,Boolean对象前面加不加new,会得到完全相反的结果,必须小心
if (Boolean(false)) {
console.log('true');
} // 无输出
if (new Boolean(false)) {
console.log('true');
} // true
if (Boolean(null)) {
console.log('true');
} // 无输出
if (new Boolean(null)) {
console.log('true');
} // true
6. Number 对象
- Number对象是数值对应的包装对象
- 可以作为构造函数使用
- 也可以作为工具函数使用
var n = new Number(1);
typeof n // "object"
Number(true) // 1
静态属性
Number.POSITIVE_INFINITY // Infinity
Number.NEGATIVE_INFINITY // -Infinity
Number.NaN // NaN
Number.MAX_VALUE
// 1.7976931348623157e+308
Number.MAX_VALUE < Infinity
// true
Number.MIN_VALUE
// 5e-324
Number.MIN_VALUE > 0
// true
Number.MAX_SAFE_INTEGER // 9007199254740991
Number.MIN_SAFE_INTEGER // -9007199254740991
实例方法
// Number.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') // 7
lastIndexOf的第二个参数表示从该位置起向前匹配
'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 // 1
matches.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:常数e
Math.LN2:2 的自然对数
Math.LN10:10 的自然对数
Math.LOG2E:以 2 为底的e的对数
Math.LOG10E:以 10 为底的e的对数
Math.PI:常数π
Math.SQRT1_2:0.5 的平方根
Math.SQRT2:2 的平方根
Math.E // 2.718281828459045
Math.LN2 // 0.6931471805599453
Math.LN10 // 2.302585092994046
Math.LOG2E // 1.4426950408889634
Math.LOG10E // 0.4342944819032518
Math.PI // 3.141592653589793
Math.SQRT1_2 // 0.7071067811865476
Math.SQRT2 // 1.4142135623730951
// 这些属性都是只读的,不能修改。
Math对象提供以下一些静态方法
Math.abs():绝对值
Math.ceil():向上取整
Math.floor():向下取整
Math.max():最大值
Math.min():最小值
Math.pow():幂运算
Math.sqrt():平方根
Math.log():自然对数
Math.exp():e的指数
Math.round():四舍五入
Math.random():随机数
// Math.abs方法返回参数值的绝对值
Math.abs(1) // 1
Math.abs(-1) // 1
// Math.max方法返回参数之中最大的那个值
// Math.min返回最小的那个值
Math.max(2, -1, 5) // 5
Math.min(2, -1, 5) // -1
Math.min() // Infinity
Math.max() // -Infinity
// Math.floor方法返回小于参数值的最大整数(地板值)
// Math.ceil方法返回大于参数值的最小整数(天花板值)
Math.floor(3.2) // 3
Math.floor(-3.2) // -4
Math.ceil(3.2) // 4
Math.ceil(-3.2) // -3
// 这两个方法可以结合起来,实现一个总是返回数值的整数部分的函数
function ToInteger(x) {
x = Number(x);
return x < 0 ? Math.ceil(x) : Math.floor(x);
}
ToInteger(3.2) // 3
ToInteger(3.5) // 3
ToInteger(3.8) // 3
ToInteger(-3.2) // -3
ToInteger(-3.5) // -3
ToInteger(-3.8) // -3
// Math.round方法用于四舍五入
Math.round(0.1) // 0
Math.round(0.5) // 1
Math.round(0.6) // 1
// 等同于
Math.floor(x + 0.5)
// 注意,它对负数的处理(主要是对0.5的处理)
Math.round(-1.1) // -1
Math.round(-1.5) // -1
Math.round(-1.6) // -2
Math.pow方法返回以第一个参数为底数、第二个参数为指数的幂运算值
// 等同于 2 ** 2
Math.pow(2, 2) // 4
// 等同于 2 ** 3
Math.pow(2, 3) // 8
// 下面是计算圆面积的方法
var radius = 20;
var area = Math.PI * Math.pow(radius, 2);
// Math.sqrt方法返回参数值的平方根。如果参数是一个负值,则返回NaN
Math.sqrt(4) // 2
Math.sqrt(-4) // NaN
// 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
// Math.exp方法返回常数e的参数次方
Math.exp(1) // 2.718281828459045
Math.exp(3) // 20.085536923187668
Math.random()返回0到1之间的一个伪随机数,可能等于0,但是一定小于1
Math.random() // 0.7151307314634323
// 任意范围的随机数生成函数如下
function getRandomArbitrary(min, max) {
return Math.random() * (max - min) + min;
}
getRandomArbitrary(1.5, 6.5)
// 2.4942810038223864
// 任意范围的随机整数生成函数如下
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
getRandomInt(1, 6) // 5
// 返回随机字符的例子如下
function random_str(length) {
var ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
ALPHABET += 'abcdefghijklmnopqrstuvwxyz';
ALPHABET += '0123456789-_';
var str = '';
for (var i = 0; i < length; ++i) {
var rand = Math.floor(Math.random() * ALPHABET.length);
str += ALPHABET.substring(rand, rand + 1);
}
return str;
}
random_str(6) // "NdQKOr"
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
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
// 2678400000
d2 + d1
// "Sat Apr 01 2000 00:00:00 GMT+0800 (CST)Wed Mar 01 2000 00:00:00 GMT+0800 (CST)"
静态方法
// Date.now方法返回当前时间距离时间零点(1970年1月1日 00:00:00 UTC)的毫秒数
// 相当于 Unix 时间戳乘以1000
Date.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')
// 如果解析失败,返回NaN
Date.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() // 1362790014817
d.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() // 6
d.getMonth() // 0
d.getFullYear() // 2013
d.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() // 6
d.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) // 1357660800000
d // Wed Jan 09 2013 00:00:00 GMT+0800 (CST)
// set*方法的参数都会自动折算
// 以setDate()为例,如果参数超过当月的最大天数,则向下一个月顺延
// 如果参数是负数,表示从上个月的最后一天开始减去的天数
var d1 = new Date('January 6, 2013');
d1.setDate(32) // 1359648000000
d1 // Fri Feb 01 2013 00:00:00 GMT+0800 (CST)
var d2 = new Date ('January 6, 2013');
d2.setDate(-1) // 1356796800000
d2 // Sun Dec 30 2012 00:00:00 GMT+0800 (CST)
// set类方法和get类方法,可以结合使用,得到相对时间
var d = new Date();
// 将日期向后推1000天
d.setDate(d.getDate() + 1000);
// 将时间设为6小时后
d.setHours(d.getHours() + 6);
// 将年份设为去年
d.setFullYear(d.getFullYear() - 1);
// set*系列方法除了setTime(),都有对应的 UTC 版本,即设置 UTC 时区的时间
setUTCDate()
setUTCFullYear()
setUTCHours()
setUTCMilliseconds()
setUTCMinutes()
setUTCMonth()
setUTCSeconds()
var d = new Date('January 6, 2013');
d.getUTCHours() // 16
d.setUTCHours(22) // 1357423200000
d // Sun Jan 06 2013 06:00:00 GMT+0800 (CST)
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 // true
r.global // true
r.multiline // true
r.flags // 'gim'
RegExp.prototype.lastIndex:返回一个整数,表示下一次开始搜索的位置
// 该属性可读写,但是只在进行连续搜索时有意义
RegExp.prototype.source:返回正则表达式的字符串形式(不包括反斜杠),该属性只读
var r = /abc/igm;
r.lastIndex // 0
r.source // "abc"
正则实例对象的test方法返回一个布尔值
- 表示当前模式是否能匹配参数字符串
/cat/.test('cats and dogs') // true
// 如果正则表达式带有g修饰符,则每一次test方法都从上一次结束的位置开始向后匹配
var r = /x/g;
var s = '_x_x';
r.lastIndex // 0
r.test(s) // true
r.lastIndex // 2
r.test(s) // true
r.lastIndex // 4
r.test(s) // false
// 上面代码的正则表达式使用了g修饰符,表示是全局搜索,会有多个结果
// 接着,三次使用test方法,每一次开始搜索的位置都是上一次匹配的后一个位置
// 带有g修饰符时,可以通过正则对象的lastIndex属性指定开始搜索的位置
var r = /x/g;
var s = '_x_x';
r.lastIndex = 4;
r.test(s) // false
r.lastIndex // 0
r.test(s)
// 注意,带有g修饰符时,正则表达式内部会记住上一次的lastIndex属性
// 这时不应该更换所要匹配的字符串,否则会有一些难以察觉的错误
var r = /bb/g;
r.test('bb') // true
r.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 // 1
arr.input // "_abbba_aba_"
// 上面代码中的index属性等于1,是因为从原字符串的第二个位置开始匹配成功。
// 如果正则表达式加上g修饰符,则可以使用多次exec()方法
// 下一次搜索的位置从上一次匹配成功结束的位置开始
var reg = /a/g;
var str = 'abc_abc_abc'
var r1 = reg.exec(str);
r1 // ["a"]
r1.index // 0
reg.lastIndex // 1
var r2 = reg.exec(str);
r2 // ["a"]
r2.index // 4
reg.lastIndex // 5
var r3 = reg.exec(str);
r3 // ["a"]
r3.index // 8
reg.lastIndex // 9
var r4 = reg.exec(str);
r4 // null
reg.lastIndex // 0
// 利用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') // false
var s = 'Please yes\nmake my day!';
s.match(/yes.*day/) // null
s.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); // true
regex.test(str); // true
regex.test(str); // true
var regex = /b/g;
var str = 'abba';
regex.test(str); // true
regex.test(str); // true
regex.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" // false
JSON.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 对象,则这些值被转成null
var 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') // true
JSON.parse('"foo"') // "foo"
JSON.parse('[1, 5, "false"]') // [1, 5, "false"]
JSON.parse('null') // null
var o = JSON.parse('{"name": "张三"}');
o.name // 张三
// 如果传入的字符串不是有效的 JSON 格式,JSON.parse方法将报错
JSON.parse("'String'") // illegal single quotes
// SyntaxError: Unexpected token ILLEGAL
// 为了处理解析错误,可以将JSON.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
// false
var Vehicle = function (){
this.price = 1000;
return { price: 2000 };
};
(new Vehicle()).price
// 2000
new.target
function f() {
console.log(new.target === f);
}
f() // false
new 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 // true
function 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() // undefined
var 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 // true
f.call(obj) === obj // true
// call方法的参数,应该是一个对象。如果参数为空、null和undefined,则默认传入全局对象。
var n = 123;
var obj = { n: 456 };
function a() {
console.log(this.n);
}
a.call() // 123
a.call(null) // 123
a.call(undefined) // 123
a.call(window) // 123
a.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') // true
Object.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
// 将数组的空元素变为undefined
Array.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() // 1481869925657
var 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]
三、章节链接
「@浪里淘沙的小法师」