JS 回顾 2
约 11627 字大约 39 分钟
2026-01-25
Object
在 JS 里,大多数“普通对象”(包括数组、函数、日期对象、正则对象、包装对象等)在原型链顶端都会走到 Object.prototype,也就是说那些对象都是 Object 的实例。
Array instanceof Object
// trueinstanceof 判断的是:右侧构造函数的 prototype 是否出现在左侧对象的原型链上。
数组的原型链大概是:arr → Array.prototype → Object.prototype → null,所以 Object.prototype 在链上
但有两个非常重要的例外,null / undefined 不是对象,而且 null 的原型链终点就是它自己(也可以理解为“没有对象身份”)。
Object.create(null) 创建的是“无原型对象”,它的原型直接是 null,根本不经过 Object.prototype,这类对象常用于做“纯字典”(避免 toString、constructor 之类的原型属性干扰)。
还有一点就是 原始值本身不是对象,但当你用到属性/方法时,JS 会临时“装箱”(boxing)成包装对象(比如 new String("hi"))去帮你调用方法,所以你会觉得它“像对象”。
这种 const o = Object.create(null) 就是刻意造出来的“裸对象 / 纯字典”:它 没有原型,所以不会从 Object.prototype 继承任何东西——也就意味着你“. 点不出来”那些常见方法(toString、hasOwnProperty、constructor……全没了)。
它的主要用途基本就两类:当 Map 的轻量替代、以及 安全地当 key-value 字典。
普通 {} 天生带一堆原型属性,如果你把对象当哈希表用,最烦的是:你的 key 可能刚好叫 toString / constructor / __proto__,会产生奇怪行为甚至安全问题(原型污染)。
Object.create(null) 就不会:
const dict = Object.create(null);
dict.toString // undefined
"toString" in dict // false所以它特别适合:外部输入的 key(比如用户提交的字段名、URL 参数、JSON 里的键、HTTP header 名等)直接塞进字典里统计/映射。
因为没原型链,枚举时不会被继承属性搅局(虽然 Object.keys({}) 本来也只拿自有属性,但 for...in 会枚举继承属性):
const dict = Object.create(null);
dict.a = 1;
for (const k in dict) console.log(k); // 只会 a在 key 全是字符串/符号、操作就是 get/set/has、而且对象规模不夸张时,裸对象当字典可能更轻量(但现代引擎里 Map 也很快;别为了“传说中的性能”提前优化,更多是为了“语义干净/安全”)。
代价就是它啥都没有,在它的身上用一些对象的方法就要换思路了:
dict.hasOwnProperty("a") // TypeError: dict.hasOwnProperty is not a functionObject.hasOwn(dict, "a") // 新版
// 或者
Object.prototype.hasOwnProperty.call(dict, "a")Object 对象的原生方法分成两类:Object 本身的方法与 Object 的实例方法
所谓“本身方法”就是直接定义在 Object 对象的方法。
所谓 实例方法 就是定义在 Object 原型对象 Object.prototype 上的方法,它可以被 Object 实例直接使用。
通过 const obj = new Object() 的写法生成新对象,与字面量的写法 var obj = {} 是等价的
静态方法
Object.keys 方法的参数是一个对象,返回一个数组,该数组的成员都是该对象自身的(而不是继承的)所有属性名,而 Object.getOwnPropertyNames 方法还返回不可枚举的属性名。
(1)对象属性模型的相关方法
Object.getOwnPropertyDescriptor():获取某个属性的描述对象。Object.defineProperty():通过描述对象,定义某个属性。Object.defineProperties():通过描述对象,定义多个属性。
(2)控制对象状态的方法
Object.preventExtensions():防止对象扩展,使得一个对象无法再添加新的属性。Object.isExtensible():判断对象是否可扩展,用于检查一个对象是否使用了Object.preventExtensions方法。Object.seal():禁止对象配置,使得一个对象既无法添加新属性,也无法删除旧属性,实质是把属性描述对象的configurable属性设为falseObject.isSealed():判断一个对象是否可配置,用于检查一个对象是否使用了Object.seal方法。Object.freeze():冻结一个对象,使得它无法添加新属性、无法删除旧属性、也无法改变属性的值,使得这个对象实际上变成了常量。Object.isFrozen():判断一个对象是否被冻结,用于检查一个对象是否使用了Object.freeze方法。
(3)原型链相关方法
Object.create():该方法可以指定原型对象和属性,返回一个新的对象。Object.getPrototypeOf():获取对象的Prototype对象。
控制对象状态的方法的注意点
只影响对象本身(shallow),不影响内部引用(non-transitive)
约束的是 这个对象自己的自有属性(own properties),不递归、不传递:
freeze(obj)只会让obj这一层:不能新增/删除/改值(对数据属性而言)- 但如果
obj.a指向另一个对象:obj.a.x = ...仍然可能成功(除非obj.a也被冻住)
一句话:冻的是壳,不是壳里所有层级。
原型链仍然是“旁路”(prototype as a bypass path)
即使把 obj 冻住了,仍然可能通过:
- 改
obj.__proto__指向的对象 - 或再往上改
Object.prototype
从效果上看: obj 自己动不了 ≠ obj 的行为永远不变,因为它的“继承来源”可能变,当然这也不是永远都能绕过:原型对象本身也可能被 seal/freeze 或属性描述符(writable/configurable)卡死。
总结:preventExtensions/seal/freeze 提供的是 对象自身的浅层完整性约束;要防“内部可变”和“原型链旁路”,得用 deep freeze / 不可变数据结构 / Map 或无原型对象 / Proxy /(极端情况下)冻结原型 等组合方案。
实例方法
Object 实例对象的方法,主要有以下六个。
Object.prototype.valueOf():返回当前对象对应的值。Object.prototype.toString():返回当前对象对应的字符串形式。Object.prototype.toLocaleString():返回当前对象对应的本地字符串形式。Object.prototype.hasOwnProperty():判断某个属性是否为当前对象自身的属性,还是继承自原型对象的属性。Object.prototype.isPrototypeOf():判断当前对象是否为另一个对象的原型。Object.prototype.propertyIsEnumerable():判断某个属性是否可枚举。
isPrototypeOf 是 原型对象的方法,用来判断:“我(左边这个对象)是否出现在右边对象的原型链上”
const arr = [];
Array.prototype.isPrototypeOf(arr); // true
Object.prototype.isPrototypeOf(arr); // true
arr instanceof Array; // true
arr instanceof Object; // truearr.__proto__ === Array.prototype,并且再往上还有 Object.prototype。
A.prototype.isPrototypeOf(obj):问 A.prototype 是否在 obj 的原型链上obj instanceof A:问 A.prototype 是否在 obj 的原型链上
自定义原型可以更直观:
const proto = { tag: "P" };
const o = Object.create(proto);
proto.isPrototypeOf(o); // true
Object.prototype.isPrototypeOf(o); // truevalueOf 方法的主要用途是,JavaScript 自动类型转换时会默认调用这个方法
Object.prototype.toString 方法返回对象的类型字符串,因此可以用来判断一个值的类型,对象的 toString 方法返回一个字符串 [object Object],其中第二个 Object 表示该值的构造函数
不同数据类型的 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 可以看出一个值到底是什么类型,利用这个特性,可以写出一个比 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"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.getOwnPropertyDescriptor() 方法可以获取属性描述对象,它的第一个参数是目标对象,第二个参数是一个字符串,对应目标对象的某个属性名。
注意:Object.getOwnPropertyDescriptor() 方法只能用于对象自身的属性,不能用于继承的属性。
注意:一旦定义了取值函数 get(或存值函数 set),就不能将 writable 属性设为 true,或者同时定义 value 属性,否则会报错。
属性描述对象的各个属性称为“元属性”,因为它们可以看作是控制属性的属性。
value 属性是目标属性的值。
writable 属性是一个布尔值,决定了目标属性的值(value)是否可以被改变。
注意:正常模式下,对 writable 为 false 的属性赋值不会报错,只会默默失败,但是,严格模式下会报错,即使对 a 属性重新赋予一个同样的值。
如果原型对象的某个属性的 writable 为 false,那么子对象将无法自定义这个属性。
var proto = Object.defineProperty({}, 'foo', {
value: 'a',
writable: false
});
var obj = Object.create(proto);
obj.foo = 'b';
obj.foo // 'a'原型链上 proto.foo 的属性描述符参与了对 obj.foo 赋值的判定(gatekeeper),但赋值的接收者仍是 obj,不是 proto。
因为“赋值”这件事在 JS 里 不是“自己没有就一定创建到自己身上”,它还要尊重原型链上同名属性的“规则”。
- 读:自己没有就去原型拿。
- 写:自己没有会尝试在自己身上创建,但如果原型链上已经有同名属性且它是“不可写/不可设”的,那么它会阻止你创建同名遮蔽属性。
obj.foo = ... 走的是“普通赋值语义”(会受原型链上同名属性影响),而 Object.defineProperty(obj, 'foo', ...) 是 元编程级别的定义/重定义,它在很多情况下可以 直接把 foo 定义成 obj 的自有属性,从而“绕开”普通赋值那套 [[Set]] 规则。
但也有个边界:
- 目标对象是否可扩展(
Object.preventExtensions / seal / freeze) - 目标对象上同名自有属性是否 configurable(不可配置就改不了属性形态)
上述例子可以用 defineProperty 直接在 obj 上定义一个自有属性 foo,就能遮蔽原型:
Object.defineProperty(obj, 'foo', {
value: 'b',
writable: true,
configurable: true,
enumerable: true
});
obj.foo; // 'b'
proto.foo; // 'a'enumerable(可遍历性)返回一个布尔值,表示目标属性是否可遍历。
如果一个属性的 enumerable 为 false,下面三个操作不会取到该属性。
for..in循环Object.keys方法JSON.stringify方法
因此,enumerable 可以用来设置“秘密”属性。
for...in 循环包括继承的属性,Object.keys 方法不包括继承的属性,如果需要获取对象自身的所有属性,不管是否可遍历,可以使用 Object.getOwnPropertyNames 方法。
另外,JSON.stringify 方法会排除 enumerable 为 false 的属性,有时可以利用这一点,如果对象的 JSON 格式输出要排除某些属性,就可以把这些属性的 enumerable 设为 false。
configurable(可配置性)返回一个布尔值,决定了是否可以修改属性描述对象。也就是说,configurable 为 false 时,writable、enumerable 和 configurable 都不能被修改了。
注意:该属性为 false 的时候,writable 属性只有在 false 改为 true 时会报错,true 改为 false 是允许的。
var obj = Object.defineProperty({}, 'p', {
writable: true,
configurable: false
});
Object.defineProperty(obj, 'p', {writable: false})
// 修改成功value 属性的情况比较特殊。只要 writable 和 configurable 有一个为 true,就允许改动 value。
除了直接定义以外,属性还可以用存取器(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"// 写法二
var obj = {
get p() {
return 'getter';
},
set p(value) {
console.log('setter: ' + value);
}
};第一种写法,属性 p 的 configurable 和 enumerable 都为 false,从而导致属性 p 是不可遍历的;
第二种写法,属性 p 的 configurable 和 enumerable 都为 true,因此属性 p 是可遍历的。
对象拷贝
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 } })hasOwnProperty 那一行用来过滤掉继承的属性,否则可能会报错,因为 Object.getOwnPropertyDescriptor 读不到继承属性的属性描述对象。
这个方法解决了如果遇到存取器定义的属性,会只拷贝值的问题
但用 for...in 会漏掉 不可枚举的属性(因为 for...in 只遍历 enumerable),如果想“完整拷贝所有自有属性(含 symbol/不可枚举)”,得换成 Reflect.ownKeys(from) 来遍历。
数组
静态方法
Array.isArray 方法返回一个布尔值,表示参数是否为数组,它可以弥补 typeof 运算符的不足。
实例方法
valueOf 方法是一个所有对象都拥有的方法,表示对该对象求值,不同对象的 valueOf 方法不尽一致,数组的 valueOf 方法返回数组本身。
toString 方法也是对象的通用方法,数组的 toString 方法返回数组的字符串形式。
push 方法用于在数组的末端添加一个或多个元素,并返回添加新元素后的数组长度,注意,该方法会改变原数组。
pop 方法用于删除数组的最后一个元素,并返回该元素,注意,该方法会改变原数组。
shift() 方法用于删除数组的第一个元素,并返回该元素,注意,该方法会改变原数组。
unshift() 方法用于在数组的第一个位置添加元素,并返回添加新元素后的数组长度,注意,该方法会改变原数组。
join() 方法以指定参数作为分隔符,将所有数组成员连接为一个字符串返回,如果不提供参数,默认用逗号分隔,通过 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 方法用于多个数组的合并。它将新数组的成员,添加到原数组成员的后部,然后返回一个新数组,原数组不变。
['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}]
[1, 2, 3].concat(4, 5, 6)
// [1, 2, 3, 4, 5, 6]reverse 方法用于颠倒排列数组元素,返回改变后的数组,注意,该方法将改变原数组。
slice() 方法用于提取目标数组的一部分,返回一个新数组,原数组不变:
arr.slice(start, end);它的第一个参数为起始位置(从 0 开始,会包括在返回的新数组之中),第二个参数为终止位置(但该位置的元素本身不包括在内)。
如果省略第二个参数,则一直返回到原数组的最后一个成员,如果 slice() 方法的参数是负数,则表示倒数计算的位置。
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() 方法用于删除原数组的一部分成员,并可以在删除的位置添加新的数组成员,返回值是被删除的元素,注意,该方法会改变原数组。
arr.splice(start, count, addElement1, addElement2, ...);splice 的第一个参数是删除的起始位置(从 0 开始),第二个参数是被删除的元素个数,如果后面还有更多的参数,则表示这些就是要被插入数组的新元素。
sort 方法对数组成员进行排序,默认是按照字典顺序排序,排序后,原数组将被改变。
sort() 方法不是按照大小排序,而是按照字典顺序,也就是说,数值会被先转成字符串,再按照字典顺序进行比较,如果想让 sort 方法按照自定义方式排序,可以传入一个函数作为参数:
[10111, 1101, 111].sort(function (a, b) {
return a - b;
})
// [111, 1101, 10111]sort 的参数函数本身接受两个参数,表示进行比较的两个数组成员,如果该函数的返回值大于 0,表示第一个成员排在第二个成员后面;
其他情况下,都是第一个元素排在第二个元素前面。
注意:自定义的排序函数应该返回数值,否则不同的浏览器可能有不同的实现,不能保证结果都一致。
map() 方法将数组的所有成员依次传入参数函数,然后把每一次的执行结果组成一个新数组返回。
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() 方法的回调函数在这个位置不会执行,会跳过数组的空位,map() 方法不会跳过 undefined 和 null
forEach() 方法与 map() 方法很相似,也是对数组的所有成员依次执行参数函数,但是,forEach() 方法不返回值,只用来操作数据,这就是说,如果数组遍历的目的是为了得到返回值,那么使用 map() 方法,否则使用 forEach() 方法。
forEach() 的用法与 map() 方法一致,参数是一个函数,该函数同样接受三个参数:当前值、当前位置、整个数组。
forEach() 方法也可以接受第二个参数,绑定参数函数的 this 变量。
注意:forEach() 方法无法中断执行,总是会将所有成员遍历完,如果希望符合某种条件时,就中断遍历,要使用 for 循环。
forEach() 方法不会跳过 undefined 和 null,但会跳过空位。
filter() 方法用于过滤数组成员,满足条件的成员组成一个新数组返回,它的参数是一个函数,所有数组成员依次执行该函数,返回结果为 true 的成员组成一个新数组返回,该方法不会改变原数组。
filter() 方法的参数函数可以接受三个参数:当前成员,当前位置和整个数组。
filter() 方法还可以接受第二个参数,用来绑定参数函数内部的 this 变量。
[1, 2, 3, 4, 5].filter(function (elem, index, arr) {
return index % 2 === 0;
});
// [1, 3, 5]some(),every() 返回一个布尔值,表示判断数组成员是否符合某种条件。
它们接受一个函数作为参数,所有数组成员依次执行该函数,该函数接受三个参数:当前成员、当前位置和整个数组,然后返回一个布尔值。
some 方法是只要一个成员的返回值是 true,则整个 some 方法的返回值就是 true,否则返回 false。
every 方法是所有成员的返回值都是 true,整个 every 方法才返回 true,否则返回 false。
注意:对于空数组,some 方法返回 false,every 方法返回 true,回调函数都不会执行。
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 5reduce() 的参数是一个函数,数组每个成员都会依次执行这个函数。如果数组有 n 个成员,这个参数函数就会执行 n - 1 次。
reduce() 方法和 reduceRight() 方法的第一个参数都是一个函数。该函数接受以下四个参数。
- 累积变量。第一次执行时,默认为数组的第一个成员;以后每次执行时,都是上一轮的返回值。
- 当前变量。第一次执行时,默认为数组的第二个成员;以后每次执行时,都是下一个成员。
- 当前位置。一个整数,表示第二个参数(当前变量)的位置,默认为
1。 - 原数组。
这四个参数之中,只有前两个是必须的,后两个则是可选的。
如果要对累积变量指定初值,可以把它放在 reduce() 方法和 reduceRight() 方法的第二个参数。
indexOf 方法返回给定元素在数组中第一次出现的位置,如果没有出现则返回 -1。
indexOf 方法还可以接受第二个参数,表示搜索的开始位置。
['a', 'b', 'c'].indexOf('a', 1) // -1lastIndexOf 方法返回给定元素在数组中最后一次出现的位置,如果没有出现则返回 -1。
注意:这两个方法不能用来搜索 NaN 的位置,即它们无法确定数组成员是否包含 NaN。
包装对象
JS 里有三种 原始类型:number / string / boolean,在某些条件下,它们会被 自动转成对象 来使用,对应的“包装对象”分别是:Number,String,Boolean
带 new:把原始值“包装成对象”,new Number(123)、new String('abc')、new Boolean(true) 的 typeof 都是 "object",并且与原始值全等比较为 false。
不带 new:做类型转换,返回 原始类型值,Number('123') -> 123,String(123) -> "123",Boolean(123) -> true
设计目的:让“对象”这种数据模型能覆盖 JS 的所有值(统一的数据模型),让原始类型也能“像对象一样”调用方法(比如字符串的 length、各种方法)
共同实例方法(从 Object 继承):
valueOf()返回包装对象对应的 原始值toString()返回对应的 字符串形式
原始类型 ↔ 包装对象的自动转换
自动装箱(临时对象):某些场景下,原始值会被当作对象用:JS 引擎会
- 临时创建一个包装对象
- 调用属性/方法
- 立刻销毁该临时对象
例:'abc'.length // 3 本质等价于临时 new String('abc') 再取 length。
注意:临时包装对象是“只读的”,不能给字符串这种原始值“加属性”:s = 'Hello' ; s.x = 123 ; s.x // undefined
因为每次访问时生成的是 新的临时对象,上次塞进去的属性早就跟着对象销毁了。
真要让所有字符串都有某个属性/方法:应该加到 String.prototype 上(原型链)。
自定义方法,给原型加“全局可用”的能力,可以扩展:
String.prototype.double = function(){ ... }Number.prototype.double = function(){ ... }
这样原始字符串/数字也能直接调用:'abc'.double()、(123).double()。
细节坑:123.double() 会被解析成小数点;需要写 (123).double()。
Number
静态属性
Number.POSITIVE_INFINITY:正的无限,指向Infinity。Number.NEGATIVE_INFINITY:负的无限,指向-Infinity。Number.NaN:表示非数值,指向NaN。Number.MIN_VALUE:表示最小的正数(即最接近 0 的正数,在 64 位浮点数体系中为5e-324),相应的,最接近 0 的负数为-Number.MIN_VALUE。Number.MAX_SAFE_INTEGER:表示能够精确表示的最大整数,即9007199254740991。Number.MIN_SAFE_INTEGER:表示能够精确表示的最小整数,即-9007199254740991。
实例方法
toString 方法用来将一个数值转为字符串形式,可以接受一个参数,表示输出的进制,如果省略这个参数,默认将数值先转为十进制,再输出字符串
(10).toString(2) // "1010"
(10).toString(8) // "12"
(10).toString(16) // "a"toString 方法只能将十进制的数,转为其他进制的字符串,如果要将其他进制的数,转回十进制,需要使用 parseInt 方法
toFixed() 方法先将一个数转为指定位数的小数,然后返回这个小数对应的字符串,参数为小数位数,有效范围为 0 到 100,超出这个范围将抛出 RangeError 错误。
(10).toFixed(2) // "10.00"
10.005.toFixed(2) // "10.01"由于浮点数的原因,小数 5 的四舍五入是不确定的,使用的时候必须小心
(10.055).toFixed(2) // 10.05
(10.005).toFixed(2) // 10.01toExponential 方法用于将一个数转为科学计数法形式,参数是小数点后有效数字的位数,范围为 0 到 100,超出这个范围,会抛出一个 RangeError 错误
(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"toPrecision() 方法用于将一个数转为指定位数的有效数字
该方法的参数为有效数字的位数,范围是 1 到 100,超出这个范围会抛出 RangeError 错误
该方法用于四舍五入时不太可靠,跟浮点数不是精确储存有关
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 "如果省略了参数,则由浏览器自行决定如何处理,通常会使用操作系统的地区设定
注意:该方法如果使用浏览器不认识的地区码,会抛出一个错误
String
静态方法
String.fromCharCode() 是 String 构造函数的静态方法,用于把一组 UTF-16 码元(0 ~ 0xFFFF)转换成字符串,参数为多个数值 → 按顺序拼成字符串
String.fromCharCode() // ""
String.fromCharCode(97) // "a"
String.fromCharCode(104,101,108,108,111) // "hello"它的参数 不是 Unicode 码点(code point),而是 UTF-16 码元(code unit)
也就是说:每个参数都会被当成 16 位整数(Uint16),超过 0xFFFF 的部分会被直接丢弃
不支持大于 0xFFFF 的字符,根本原因是 JS 字符串底层使用 UTF-16,默认一个“字符”占 2 字节(16 位),码点 > 0xFFFF 的字符(emoji、扩展汉字)需要 4 字节,UTF-16 用 代理对(surrogate pair) 来表示它们
String.fromCharCode(0xD842, 0xDFB7)0x20BB7 在 UTF-16 中会被拆成:高代理:0xD842,低代理:0xDFB7,两个 16 位码元合在一起,才能表示一个真实字符,这个拆分规则是 UTF-16 规范决定的,一般不手算
或者直接使用 String.fromCodePoint()(ES6) ,区别:
fromCharCode→ 吃码元(16 位)fromCodePoint→ 吃码点(真正的 Unicode 字符)
String.fromCodePoint() 是 ES6(ES2015)引入的,主要是因为 JS 对 Unicode 的支持 非常不完整
- JS 从一开始就不是“没引入 Unicode”:它一直用 Unicode 的字符集概念。
- 但 JS 内部字符串的存储单位,一直是 UTF-16 的 16 位码元(code unit)。
- 早期(ES5 及之前)的问题是:很多字符串 API 只按“码元”工作,对 > 0xFFFF 的字符(需要两个码元的代理对)就显得“不懂 Unicode”。
Unicode 给每个字符一个编号(码点 code point),比如 😀 是 U+1F600,JS 选择用 UTF-16 来表示这些码点:
- BMP 范围(≤ U+FFFF):1 个 16 位码元就够
- 非 BMP(> U+FFFF):需要 2 个码元(代理对)
实例属性
charAt 方法返回指定位置的字符,参数是从 0 开始编号的位置。
charCodeAt() 方法返回字符串指定位置的 Unicode 码点(十进制表示),相当于 String.fromCharCode() 的逆操作。
注意:charCodeAt 方法返回的 Unicode 码点不会大于 65536(0xFFFF),也就是说,只返回两个字节的字符的码点,如果遇到码点大于 65536 的字符(四个字节的字符),必须连续使用两次 charCodeAt,不仅读入 charCodeAt(i),还要读入 charCodeAt(i+1),将两个值放在一起,才能得到准确的字符。
concat 方法用于连接两个字符串,返回一个新字符串,不改变原字符串,该方法可以接受多个参数。
slice() 方法用于从原字符串取出子字符串并返回,不改变原字符串,它的第一个参数是子字符串的开始位置,第二个参数是子字符串的结束位置(不含该位置),如果省略第二个参数,则表示子字符串一直到原字符串结束。
substring 方法用于从原字符串取出子字符串并返回,不改变原字符串,跟 slice 方法很相像,它的第一个参数表示子字符串的开始位置,第二个位置表示结束位置(返回结果不含该位置),如果省略第二个参数,则表示子字符串一直到原字符串的结束,如果第一个参数大于第二个参数,substring 方法会自动更换两个参数的位置,如果参数是负数,substring 方法会自动将负数转为 0。
substr 方法用于从原字符串取出子字符串并返回,不改变原字符串,跟 slice 和 substring 方法的作用相同。
substr 方法的第一个参数是子字符串的开始位置(从 0 开始计算),第二个参数是子字符串的长度,如果省略第二个参数,则表示子字符串一直到原字符串的结束,如果第一个参数是负数,表示倒数计算的字符位置。如果第二个参数是负数,将被自动转为 0,因此会返回空字符串。
indexOf 方法用于确定一个字符串在另一个字符串中第一次出现的位置,返回结果是匹配开始的位置。如果返回 -1,就表示不匹配,还可以接受第二个参数,表示从该位置开始向后匹配。
'hello world'.indexOf('o') // 4
'JavaScript'.indexOf('script') // -1lastIndexOf 方法的用法跟 indexOf 方法一致,主要的区别是 lastIndexOf 从尾部开始匹配,indexOf 则是从头部开始匹配,lastIndexOf 的第二个参数表示从该位置起向前匹配。
trim 方法用于去除字符串两端的空格,返回一个新字符串,不改变原字符串,该方法去除的不仅是空格,还包括制表符(\t、\v)、换行符(\n)和回车符(\r)。
toLowerCase 方法用于将一个字符串全部转为小写,toUpperCase 则是全部转为大写,它们都返回一个新字符串,不改变原字符串。
match 方法用于确定原字符串是否匹配某个子字符串,返回一个数组,成员为匹配的第一个字符串,如果没有找到匹配,则返回 null,返回的数组还有 index 属性和 input 属性,分别表示匹配字符串开始的位置和原始字符串,还可以使用正则表达式作为参数。
search 方法的用法基本等同于 match,但是返回值为匹配的第一个位置,如果没有找到匹配,则返回 -1,search 方法还可以使用正则表达式作为参数。
replace 方法用于替换匹配的子字符串,一般情况下只替换第一个匹配(除非使用带有 g 修饰符的正则表达式),可以使用正则表达式作为参数。
split 方法按照给定规则分割字符串,返回一个由分割出来的子字符串组成的数组,如果省略参数,则返回数组的唯一成员就是原字符串,还可以接受第二个参数,限定返回数组的最大成员数,可以使用正则表达式作为参数。
Math
Math.abs 方法返回参数值的绝对值。
Math.floor 方法返回小于或等于参数值的最大整数(地板值)。
Math.floor(3.2) // 3
Math.floor(-3.2) // -4Math.ceil 方法返回大于或等于参数值的最小整数(天花板值)。
Math.ceil(3.2) // 4
Math.ceil(-3.2) // -3Math.round 方法用于四舍五入
Math.pow 方法返回以第一个参数为底数、第二个参数为指数的幂运算值。
Math.sqrt 方法返回参数值的平方根,如果参数是一个负值,则返回 NaN。
Math.random() 返回 0 到 1 之间的一个伪随机数,可能等于 0,但是一定小于 1。
任意范围的随机数生成函数如下
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"Date
Date 对象可以作为普通函数直接调用,返回一个代表当前时间的字符串。
Date()
// 'Wed Jan 28 2026 10:07:00 GMT+0800 (中国标准时间)'Date 还可以当作构造函数使用,对它使用 new 命令,会返回一个 Date 对象的实例,如果不加参数,实例代表的就是当前时间,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 实例有一个独特的地方。其他对象求值的时候,都是默认调用 .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)"参数可以是负整数,代表 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 会将其解释为毫秒数。
各个参数的取值范围如下:
- 年:使用四位数年份,比如
2000,如果写成两位数或个位数,则加上1900,即10代表 1910 年。如果是负数,表示公元前。 - 月:
0表示一月,依次类推,11表示 12 月。 - 日:
1到31。 - 小时:
0到23。 - 分钟:
0到59。 - 秒:
0到59 - 毫秒:
0到999。
注意:月份从 0 开始计算,但是,天数从 1 开始计算。另外,除了日期的默认值为 1,小时、分钟、秒钟和毫秒的默认值都是 0。
类型自动转换时,Date 实例如果转为数值,则等于对应的毫秒数;
如果转为字符串,则等于对应的日期字符串,所以,两个日期实例对象进行减法运算时,返回的是它们间隔的毫秒数;
进行加法运算时,返回的是两个字符串连接而成的新字符串。
静态方法
Date.now 方法返回当前时间距离时间零点(1970 年 1 月 1 日 00:00:00 UTC)的毫秒数,相当于 Unix 时间戳乘以 1000。
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.UTC 方法接受年、月、日等变量作为参数,返回该时间距离时间零点(1970 年 1 月 1 日 00:00:00 UTC)的毫秒数。
// 格式
Date.UTC(year, month[, date[, hrs[, min[, sec[, ms]]]]])
// 用法
Date.UTC(2011, 0, 1, 2, 3, 4, 567)
// 1293847384567该方法的参数用法与 Date 构造函数完全一致,比如月从 0 开始计算,日期从 1 开始计算,区别在于 Date.UTC 方法的参数,会被解释为 UTC 时间(世界标准时间),Date 构造函数的参数会被解释为当前时区的时间。
实例方法
valueOf 方法返回实例对象距离时间零点(1970 年 1 月 1 日 00:00:00 UTC)对应的毫秒数,该方法等同于 getTime 方法。
var d = new Date();
d.valueOf() // 1362790014817
d.getTime() // 1362790014817toString 方法返回一个完整的日期字符串,因为 toString 是默认的调用方法,所以如果直接读取 Date 实例,就相当于调用这个方法。
toUTCString 方法返回对应的 UTC 时间,也就是比北京时间晚 8 个小时。
toISOString 方法返回对应时间的 ISO8601 写法,该方法返回的总是 UTC 时区的时间。
toJSON 方法返回一个符合 JSON 格式的 ISO 日期字符串,与 toISOString 方法的返回结果完全相同。
toDateString 方法返回日期字符串(不含小时、分和秒)。
toTimeString 方法返回时间字符串(不含年月日)。
toLocaleString():完整的本地时间。
toLocaleDateString():本地日期(不含小时、分和秒)。
toLocaleTimeString():本地时间(不含年月日)。
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 的时区差异,以分钟表示,返回结果考虑到了夏令时因素。
RegExp
JSON
JSON 格式(JavaScript Object Notation 的缩写)是一种用于数据交换的文本格式,2001 年由 Douglas Crockford 提出,目的是取代繁琐笨重的 XML 格式。
相比 XML 格式,JSON 格式有两个显著的优点:书写简单,一目了然;符合 JavaScript 原生语法,可以由解释引擎直接处理,不用另外添加解析代码。
所以,JSON 迅速被接受,已经成为各大网站交换数据的标准格式,并被写入标准。
每个 JSON 对象就是一个值,可能是一个数组或对象,也可能是一个原始类型的值。总之,只能是一个值,不能是两个或更多的值。
JSON 对值的类型和格式有严格的规定。
- 复合类型的值只能是数组或对象,不能是函数、正则表达式对象、日期对象。
- 原始类型的值只有四种:字符串、数值(必须以十进制表示)、布尔值和
null(不能使用NaN,Infinity,-Infinity和undefined)。 - 字符串必须使用双引号表示,不能使用单引号。
- 对象的键名必须放在双引号里面。
- 数组或对象最后一个成员的后面,不能加逗号。
以下都是合法的 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;
}
} // 属性值不能使用函数和日期对象注意:null、空数组和空对象都是合法的 JSON 值。
JSON 对象是 JavaScript 的原生对象,用来处理 JSON 格式数据,它有两个静态方法:JSON.stringify() 和 JSON.parse()。
JSON.stringify() 方法用于将一个值转为 JSON 字符串,该字符串符合 JSON 格式,并且可以被 JSON.parse() 方法还原。
注意:对于原始类型的字符串,转换结果会带双引号,如果对象的属性是 undefined、函数或 XML 对象,该属性会被 JSON.stringify() 过滤。
如果数组的成员是 undefined、函数或 XML 对象,则这些值被转成 null,正则对象会被转成空对象。
JSON.stringify() 方法会忽略对象的不可遍历的属性。
JSON.stringify() 方法还可以接受一个数组,作为第二个参数,指定参数对象的哪些属性需要转成字符串。
var obj = {
'prop1': 'value1',
'prop2': 'value2',
'prop3': 'value3'
};
var selectedProperties = ['prop1', 'prop2'];
JSON.stringify(obj, selectedProperties)
// "{" prop1 ":" value1 "," prop2 ":" value2 "}"这个类似白名单的数组,只对对象的属性有效,对数组无效。
第二个参数还可以是一个函数,用来更改 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}'f 函数,接受两个参数,分别是被转换的对象的键名和键值。如果键值是数值,就将它乘以 2,否则就原样返回。
注意:这个处理函数是递归处理所有的键
var obj = {a: {b: 1}};
function f(key, value) {
console.log("["+ key +"]:" + value);
return value;
}
JSON.stringify(obj, f)
// []:[object Object]
// [a]:[object Object]
// [b]:1
// '{"a":{"b": 1}}'上面代码中,对象 obj 一共会被 f 函数处理三次,输出的最后那行是 JSON.stringify() 的默认输出。
第一次键名为空,键值是整个对象 obj;第二次键名为 a,键值是 {b: 1};第三次键名为 b,键值为 1。
递归处理中,每一次处理的对象,都是前一次返回的值:
var obj = {a: 1};
function f(key, value) {
if (typeof value === 'object') {
return {b: 2};
}
return value * 2;
}
JSON.stringify(obj, f)
// "{" b ": 4}"上面代码中,f 函数修改了对象 obj,接着 JSON.stringify() 方法就递归处理修改后的对象 obj。
如果处理函数返回 undefined 或没有返回值,则该属性会被忽略。
function f(key, value) {
if (typeof(value) === "string") {
return undefined;
}
return value;
}
JSON.stringify({ a: "abc", b: 123 }, f)
// '{"b": 123}'上面代码中,a 属性经过处理后,返回 undefined,于是该属性被忽略了。
JSON.stringify() 还可以接受第三个参数,用于增加返回的 JSON 字符串的可读性。
默认返回的是单行字符串,对于大型的 JSON 对象,可读性非常差,第三个参数使得每个属性单独占据一行,并且将每个属性前面添加指定的前缀(不超过 10 个字符)。
// 默认输出
JSON.stringify({ p1: 1, p2: 2 })
// JSON.stringify({ p1: 1, p2: 2 })
// 分行输出
JSON.stringify({ p1: 1, p2: 2 }, null, '\t')
// {
// "p1": 1,
// "p2": 2
// }上面例子中,第三个属性 \t 在每个属性前面添加一个制表符,然后分行显示。
第三个属性如果是一个数字,则表示每个属性前面添加的空格(最多不超过 10 个)
如果参数对象有自定义的 toJSON() 方法,那么 JSON.stringify() 会使用这个方法的返回值作为参数,而忽略原对象的其他属性。
var user = {
firstName: '三',
lastName: '张',
get fullName(){
return this.lastName + this.firstName;
}
};
JSON.stringify(user)
// "{" firstName ":" 三 "," lastName ":" 张 "," fullName ":" 张三 "}"var user = {
firstName: '三',
lastName: '张',
get fullName(){
return this.lastName + this.firstName;
},
toJSON: function () {
return {
name: this.lastName + this.firstName
};
}
};
JSON.stringify(user)
// "{" name ":" 张三 "}"Date 对象就有一个自己的 toJSON() 方法。
var date = new Date('2015-01-01');
date.toJSON() // "2015-01-01T00:00:00.000Z"
JSON.stringify(date) // "" 2015-01-01T00:00:00.000Z ""toJSON() 方法的一个应用是,将正则对象自动转为字符串,因为 JSON.stringify() 默认不能转换正则对象,但是设置了 toJSON() 方法以后,就可以转换正则对象了。
var obj = {
reg: /foo/
};
// 不设置 toJSON 方法时
JSON.stringify(obj) // "{" reg ":{}}"
// 设置 toJSON 方法时
RegExp.prototype.toJSON = RegExp.prototype.toString;
JSON.stringify(/foo/) // ""/foo/""JSON.parse() 方法用于将 JSON 字符串转换成对应的值。
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 格式,所以报错。
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}JSON.parse()和JSON.stringify()可以结合使用,像下面这样写,实现对象的深拷贝。
JSON.parse(JSON.stringify(obj))上面这种写法,可以深度克隆一个对象,但是对象内部不能有 JSON 不允许的数据类型,比如函数、正则对象、日期对象等。