0%

JavaScript String、Array、Object、Date 常用方法小结

  反正闲着也是闲着,稍微整理总结了一下 JavaScript 字符串、数组、对象、时间的常用方法,阿彪出品,必属精品/滑稽。

字符串(String)

charAt

1
2
// 返回在指定位置的字符
"12345".charAt(0); // 1

charCodeAt

1
2
// 返回在指定的位置的字符的 Unicode 编码
"12345".charCodeAt(0); // 49

concat

1
2
// 连接字符串
"123".concat("456"); // "123456"

indexOf/lastIndexOf

1
2
3
// 查看字符出现的位置
"123451".indexOf("1"); // 0
"123451".indexOf("1"); // 5

match

1
2
// match() 方法可在字符串内检索指定的值,或找到一个或多个正则表达式的匹配。该方法类似 indexOf() 和 lastIndexOf(),但是它返回指定的值,而不是字符串的位置。
"doubleam 666 123".match(/\d+/g); // ["666", "123"]
1
2
3
// search() 方法用于检索字符串中指定的子字符串,或检索与正则表达式相匹配的子字符串,如果没有找到任何匹配的子串,则返回 -1。
"doubleam 666 123".search("6"); // 9
"doubleam 666 123".search("9"); // -1

replace

1
2
3
4
5
6
7
// replace() 方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。
"doubleam 666".replace("666", "999"); // "doubleam 999"
"doubleam 666".replace(/(doubleam)\s(666)/g, "$2 $1"); // 666 doubleam
"doubleam 666".replace(/(doubleam)\s(666)/g, function($1, $2, $3){
console.log($1, $2, $3);//doubleam 666, doubleam, 666
return $1 + $2 + $3;
}); // doubleam 666doubleam666

slice

1
2
3
4
5
6
7
// 提取字符串的片断,并在新的字符串中返回被提取的部分。
// str.slice(start, end);
// start: 要抽取的片断的起始下标。如果是负数,则该参数规定的是从字符串的尾部开始算起的位置。也就是说,-1 指字符串的最后一个字符,-2 指倒数第二个字符,以此类推。
// end:紧接着要抽取的片段的结尾的下标。若未指定此参数,则要提取的子串包括 start 到原字符串结尾的字符串。如果该参数是负数,那么它规定的是从字符串的尾部开始算起的位置。
"i love oqm".slice(0, 1); // 'i'
"i love oqm".slice(2); // 'love oqm'
"i love oqm".slice(-3); // 'oqm'

substr

1
2
3
4
// 同上
"i love oqm".substr(0, 1); // 'i'
"i love oqm".substr(2); // 'love oqm'
"i love oqm".substr(-3); // 'oqm'

substring

1
2
3
4
// 同上,但不支持负数,尾部开区间 [start, end)。
"i love oqm".substring(0, 1); // 'i'
"i love oqm".substring(2); // 'love oqm'
"i love oqm".substring(0, 4); // 'i lo'

split

1
2
3
4
// 把字符串分割为字符串数组
"1,2,3,4,5".split(); // ["1,2,3,4,5"]
"1,2,3,4,5".split(""); // ["1", ",", "2", ",", "3", ",", "4", ",", "5"]
"1,2,3,4,5".split(","); // ["1", "2", "3", "4", "5"]

toLocaleLowerCase/toLocaleUpperCase

1
2
3
// 把字符串转换为小/大写
'aBc'.toLocaleLowerCase(); // 'abc'
'aBc'.toLocaleUpperCase(); // 'ABC'

toLowerCase/toUpperCase

1
2
3
// 把字符串转换为小/大写
'aBc'.toLowerCase(); // 'abc'
'aBc'.toUpperCase(); // 'ABC'

for-of

for-of 可以遍历字符串,除了遍历字符串,这个遍历器最大的优点是可以识别大于0xFFFF的码点,传统的for循环无法识别这样的码点。

at

1
2
// at方法可以识别 Unicode 编号大于 0xFFFF 的字符,返回正确的字符。
'我爱你'.at(0); // "我"

includes

1
2
// 查看指定字符是否存在与字符串中
'doubleam'.includes('d'); // true

startsWith/endsWith

1
2
3
// 查看指定字符是否存在与字符串开头/结尾
'doubleam'.startsWith('a'); // false
'doubleam'.endsWith('m'); // true

repeat

1
2
3
// repeat 方法返回一个新字符串,表示将原字符串重复n次。
'doubleam'.repeat(3); // "doubleamdoubleamdoubleam"
'doubleam'.repeat(0); // ""

padStart/padEnd

1
2
3
4
5
6
// 字符串长度补全,默认使用空格补全长度。
"1.01".padEnd(5, "0"); // "1.010"
// 如果原字符串的长度,等于或大于指定的最小长度,则返回原字符串。
"1.010".padEnd(5, "0"); // "1.010"
// 如果用来补全的字符串与原字符串,两者的长度之和超过了指定的最小长度,则会截去超出位数的补全字符串。
'09-28'.padStart(10, 'yyyy-mm-dd'); // "yyyy-09-28"

trim

1
2
// 去除首尾空格
" doubleam ".trim(); // "doubleam"

trimLeft/trimRight/trimStart/trimEnd

字面意思

生成 DOM string

1
2
3
// 有趣
"baidu".link('baidu.com'); // "<a href=\"baidu.com\">baidu</a>"
"baidu".sub(); // "<sub>baidu</sub>"

数组(Array)

reduce

1
2
3
4
5
6
7
8
9
10
11
// 使用 console.time('test'); 与 console.timeEnd('test'); 测试发现,数组遍历速度 for > for-of > forEach > filter > map > for-in(可适用于对象)/reduce > $.each([1, 2, 3, 4], (index, value) => console.log(value));(JQ)
// 以下介绍遍历方法均不可跳出,且 map 为迭代严格意义上不算循环,若想实现跳出或部分判断可使用 ES6 for-of、find或some...

// reduce 方法有两个个参数,第一个参数是一个callback,用于针对数组项的操作(total, currentValue, currentIndex, selfArray);第二个参数则是传入的初始值,这个初始值用于单个数组项的操作。需要注意的是,reduce方法返回值并不是数组,而是返回经过叠加处理后的结果;不改变原数组。
[1, 2, 3, 4].reduce((x, y) => x + y); //10
let testArr = [{'name':'a', 'age':1}, {'name':'b', 'age':2}, {'name':'c', 'age':3}, {'name':'d', 'age':4}];
testArr.reduce(function (total, currentValue, currentIndex, selfArray) {
// total 为上一次调用返回的值,或者是提供的初始值 0。 // 0,1,3,6,10
console.log(selfArray[currentIndex].name); // a,b,c,d
return total + currentValue.age;
}, 0); // 10

map

1
2
// map 方法对数组的每一项都运行给定的函数,返回每次函数调用的结果组成一个新数组,不改变原数组。
[1, 2, 3, 4].map((value, index, selfArray) => value * 10); // [10,20,30,40]

forEach

1
2
3
4
// forEach 数组遍历
[1, 2, 3, 4].forEach(function(value, index, selfArray) {
//do something
});

push

1
2
3
4
// push 方法可向数组的末尾添加一个或多个元素,并返回新的长度,改变原数组。
let testArr = [1, 2, 3, 4];
testArr.push(5, 6); // 6
console.log(testArr); // [1,2,3,4,5,6]

pop

1
2
3
4
// pop 方法用于删除并返回数组的最后一个元素,改变原数组。
let testArr = [1, 2, 3, 4];
testArr.pop(); // 4
console.log(testArr); // [1,2,3]

shift

1
2
3
4
// shift 方法用于把数组的第一个元素从其中删除,并返回第一个元素的值,改变原数组。
let testArr = [1, 2, 3, 4];
testArr.shift(); // 1
console.log(testArr); // [2,3,4]

unshift

1
2
3
4
// unshift 方法可向数组的开头添加一个或更多元素,并返回新的长度,改变原数组。
let testArr = [1, 2, 3, 4];
testArr.unshift(-1, 0); // 6
console.log(testArr); // [-1,0,1,2,3,4]

concat

1
2
// concat 方法用于连接两个或多个数组,返回新数组。
[1].concat([2],[3],[4]); // [1,2,3,4]

join

1
2
// join 方法用于把数组中的所有元素放入一个字符串,元素是通过指定的分隔符进行分隔的,默认使用 ',' 号分隔,不改变原数组,返回字符串。
[1, 2, 3, 4].join('/'); // 1/2/3/4

filter

1
2
3
// filter 对数组的每一项都运行给定的函数,返回结果为 true 组成的数组,不改变原数组,返回新数组。
[null, undefined, 1, '', 0, 'biugle'].filter(Boolean); // [1,'biugle']
[null, undefined, 1, '', 0, 'biugle'].filter((value, index, selfArray) => value == 'biugle'); // [biugle]

every

1
2
// every 对数组的每一项都运行给定的函数,每一项都返回 true,则返回 true,不改变原数组,返回 bool。
[1, 2, 3, 4].every((value, index, selfArray) => value < 5); // true

some

1
2
// some 对数组的每一项都运行给定的函数,任意一项返回 true,则返回 true,不改变原数组,返回 bool。
[1, 2, 3, 4].some((value, index, selfArray) => value == 1); // true

at

1
2
// at 获取指定下标元素,同 array[index]。
[1, 2, 3, 4].at(3); // 4

slice

1
2
3
4
// slice 返回一个新的数组,包含从 start 到 end (尾部开区间,不包括该元素。)的 selfArray 中的元素,不改变原数组。
let testArr = [1, 2, 3, 4];
testArr.slice(0, 3); // [1,2,3]
console.log(testArr); // [1,2,3,4]

splice

1
2
3
4
5
// splice 方法可删除从 index 处开始的零个或多个元素,并且用参数列表中声明的一个或多个值来替换那些被删除的元素。
// 如果从 selfArray 中删除了元素,则返回的是含有被删除的元素的数组。该方法会直接对数组进行修改。
let testArr = [1, 2, 3, 4];
testArr.splice(1, 2, 5, 6); // [2, 3] ps: 在 index 为 1 开始,删除 2 个元素,添加 5, 6。
console.log(testArr); // [1, 5, 6, 4]

sort

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// sort 排序按照 Unicode Code 位置排序,默认升序,改变原数组。
// 需传入比较函数 function(a, b),a/b为相比较的值。
// 若返回一个 < 0 的值,则 a 应该在 b 前面。
// 若返回 = 0 的值,a 与 b 点位置相对不变。
// 若返回一个 > 0 的值,则 a 应该在 b 后面。
[1, 2, 4, 3, 6, 5].sort(); // [1, 2, 3, 4, 5, 6]
[1, 2, 4, 3, 16, 15].sort(); // [1, 15, 16, 2, 3, 4]
[1, 2, 4, 3, 16, 15].sort((a, b) => a - b); // [1, 2, 3, 4, 15, 16]
[1, 2, 4, 3, 16, 15].sort(function(a, b) {
console.log(`${b} - ${a} = ${b - a}`);
return b - a;
}); // [16, 15, 4, 3, 2, 1]
['aaa', 'bb', 'c'].sort(); // ["aaa", "bb", "c"]
['aaa', 'bb', 'c'].sort().sort((a, b) => a.length - b.length); // ["c", "bb", "aaa"]

reverse

1
2
3
4
5
6
7
// reverse 方法用于颠倒数组中元素的顺序,返回的是颠倒后的数组,改变原数组。
let testArr = [1, 2, 3, 4];
testArr.reverse();
console.log(testArr); // [4, 3, 2, 1]

// 我们也可以通过 sort 来进行数组颠倒,只需返回 < 0 的值即可。
[1, 2, 3, 4, 5].sort(() => -1); // [5, 4, 3, 2, 1]

indexOf

1
2
3
4
5
// indexOf 和 lastIndexOf 都接受两个参数:查找的值、查找起始位置。不存在,返回 -1,存在,返回位置。indexOf 是从前往后查找,lastIndexOf 是从后往前查找。
let testArr = [1, 2, 3, 1];
testArr.indexOf(1); // 0
testArr.lastIndexOf(1); // 3
testArr.indexOf(4); // -1

find

1
2
3
4
// find 和 findIndex 传入一个回调函数,找到数组中符合当前搜索规则的第一个元素,返回它,并且终止搜索。findIndex 返回的是下标。
let testArr = [1, 2, 3, 4];
testArr.find((item, index, selfArray) => item > 1); // 2
testArr.findIndex((item) => item > 1); // 1

fill

1
2
3
4
// fill(value, start, end) 用新元素替换掉数组内的元素,可以指定替换下标范围(尾部开区间),改变原数组。
let testArr = [1, 2, 3, 4];
testArr.fill(0, 1, 3); // [1, 0, 0, 4]
testArr.fill(0, 1, 100); // [1, 0, 0, 0]

copyWithin

1
2
3
// copyWithin(target, start, end) 选择数组的某个下标,从该位置开始复制数组元素,默认从 0 开始复制,也可以指定要复制的元素范围(尾部开区间),改变原数组。
let testArr = [1, 2, 3, 4];
testArr.copyWithin(2, 0, 2); // [1, 2, 1, 2] ps: 从 index 2 开始复制 [0,2) 之间的元素,所以原数组 3,4 被替换为 1,2。

from

1
2
3
4
5
// from 将类似数组的对象(array-like object)和可遍历(iterable)的对象转为真正的数组。
Array.from([1, 2, 3, 4]); // [1,2,3,4]
Array.from('1234'); // ['1','2','3','4']

// 数组克隆我们也可以使用此方法,或者使用 concat,亦或是使用 ... 展开运算符。

of

1
2
3
4
5
6
// of 用于将一组值,转换为数组,这个方法的主要目的,是弥补数组构造函数 Array() 的不足。
// 因为参数个数的不同,会导致 Array() 的行为有差异。
Array(1, 2, 3); // [1,2,3]
Array(4); // [empty*4]
Array.of(1, 2, 3); // [1,2,3]
Array.of(4); // [4]

entries

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// entries 返回迭代器,返回键值对。
// 数组
let testArr = ['a', 'b', 'c'];
for(let value of testArr.entries()) {
console.log(value); // [0, 'a'],[1, 'b'],[2, 'c']
}
// Set,(add,delete,size,forEach,has,keys,values,clear)Set结构不会添加重复的值,也可用于数组去重。[...new Set([1, 1, 2, 2, 3, 4, 5])]; 或 Array.from(new Set([1, 1, 2, 2, 3, 4, 5])); // [1, 2, 3, 4, 5]
// 去重还可以利用对象 key,或者 Array 的 filter 方法加上 indexOf 来完成。
new Set([NaN, NaN, {}, {}]); // [NaN, {}, {}]
let testSet = new Set(testArr);
for(let value of testSet.entries()) {
console.log(value); // ['a', 'a'],['b', 'b'],['c', 'c']
}
// Map,(set,get,delete,size,forEach,has,keys,values,clear)一般用于快速查询,性能高。
let testMap = new Map([['a', 'a'], ['b', 'b']]);
testMap.set('c', 'c');
for(let value of testMap.entries()) {
console.log(value); // ['a', 'a'],['b', 'b'],['c', 'c']
}

values

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// values 返回迭代器,返回键值对的 value。
// 数组
let testArr = ['a', 'b', 'c'];
for(let value of testArr.values()) {
console.log(value); //'a' 'b' 'c'
}
// Set
let testSet = new Set(['a', 'b', 'c']);
for(let value of testSet.values()) {
console.log(value); // 'a' 'b' 'c'
}
// Map
let testMap = new Map([['a', 'a'], ['b', 'b'], ['c', 'c']]);
for(let value of testMap.values()) {
console.log(value); // 'a' 'b' 'c'
}

keys

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// keys() 返回迭代器,返回键值对的 key。
// 数组
let testArr = ['a', 'b', 'c'];
for(let value of testArr.keys()) {
console.log(value); //'0' '1' '2'
}
// Set
let testSet = new Set(['a', 'b', 'c']);
for(let value of testSet.keys()) {
console.log(value); // 'a' 'b' 'c'
}
// Map
let testMap = new Map([['a', 'a'], ['b', 'b'], ['c', 'c']]);
for(let value of testMap.keys()) {
console.log(value); // 'a' 'b' 'c'
}

includes

1
2
3
4
5
6
// includes 判断数组中是否存在该元素,参数:查找的值、起始位置,可以替换 ES5 时代的 indexOf 判断方式。
// indexOf 判断元素是否为 NaN,会判断错误。
let testArr = [1, 2, 3, 4, NaN];
testArr.includes(1, 2); // false ps: 从 index 2 开始查。
testArr.includes(NaN); // true
testArr.indexOf(NaN); // -1

flat

1
2
3
4
// flat 数组降维打击,返回新数组,不改变原数组。
[1, 2, [3, 4], [5, [6, 7]], 8].flat(); // [1, 2, 3, 4, 5, [6, 7], 8]
[1, 2, [3, 4], [5, [6, 7, [8]]], 9].flat(2); // [1, 2, 3, 4, 5, 6, 7, [8], 9]
[1, 2, [3, 4], [5, [6, 7, [8]]], 9].flat(3); // [1, 2, 3, 4, 5, 6, 7, 8, 9]

对象(Object)

create

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// Object.create() 方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__(里面的属性属于新对象自己),其实简单来说就是改变创建对象的原型指向。
let testObj = {name: 'doubleam', age: 21};
let extendObj = Object.create(testObj, {
gender: {
value: 'man', // 属性值
writable: true, // 是否可以重写值
enumerable: true, // 是否可枚举
configurable: true // 是否可以修改以上几项配置
}
});
extendObj; // {gender: 'man'}
extendObj.__proto__; // {name: "doubleam", age: 21}
extendObj.name; // doubelam
// enumerable 是否可枚举
// for-in 遍历对象所有属性(包括原型上的属性)。
// Object.keys 只能遍历自身属性
// JSON.stringify 只能序列化自身属性
for(item in extendObj){
console.log(item); // gender,name,age
// 若 enumerable = false,那么此处打印为 name,age。
}
Object.keys(extendObj); // gender ps: 多个属性按ascii顺序排列,vue 中使用 v-for 遍历也受此影响。
// 若 enumerable = false,那么此处值为空 []。

// Object.create与 new Object() 是有区别的,比如 Object.create(null) 创建的对象是一个空对象,在该对象上没有继承 Object.prototype 原型链上的属性或者方法 (toString(),hasOwnProperty()等)。



// 利用 create 实现 new
function Dog(name) {
this.name = name;
}
Dog.prototype.speak = function () {
console.log('汪汪汪');
};
function _new(fn, ...arg) {
// let obj = {}
// obj.__proto__ = fn.prototype
let obj = Object.create(fn.prototype); // 先改变原型指向
fn.call(obj, ...arg); // 再调用构造函数
return obj;
}
let blackDog = _new(Dog, '小黑');

console.log(blackDog);
console.log(blackDog.name);
blackDog.speak();

// 继承则可以通过 prototype 构造函数 es6-extend 等方式实现 https://www.cnblogs.com/humin/p/4556820.html

assign

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// Object.assign(target, source1, source2) 方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target),若 target 与 source 存在相同属性,则会被后出现的覆盖。拷贝的属性是有限制的,只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性(enumerable: false)。
let target = {a: 0, b: 1};
let source1 = {b: 2, c: 3};
let source2 = {c: 4};
Object.assign(target, source1, source2); // {a:0, b:2, c:4}

// 深浅拷贝
// Object.assign()拷贝的是属性值。假如源对象的属性值是一个对象的引用,那么它也只指向那个引用。
// 如果对象的属性值为简单类型(如 number,string,boolean),通过 Object.assign({}, source); 得到的新对象为深拷贝。
// 但如果属性值为对象(object)或其它引用类型时,那对于这个对象而言其实是浅拷贝的。
// 总结:当被复制合并的对象中只有一级属性,没有二级属性的时候,此方法为深拷贝,但是对象中有对象的时候,此方法,在二级属性以后就是浅拷贝。
// 如果要实现深拷贝,可以使用 JSON.parse(JSON.stringify(source)); 或者使用 JQ $.extend()。
// 也可以通过 ... 展开运算符克隆对象:newObj = {...obj1, ...obj2}; 数组转对象也可以使用 ... 展开运算符: newObj = {...arr};
let testObj = {a: 1, obj: {a: 2}};
let copyObj = Object.assign({}, testObj);
copyObj.obj.a = 3;
console.log(testObj.obj.a) //3

// 特例 空类型 undefined,null
Object.assign(target, undefined) === target; // true
Object.assign(target, null) === target; // true
Object.assign([1, 2, 3, 4], [5, 6, 7]); // [5, 6, 7, 4]

// js原始数据类型: number,string,boolean,undefined,null,object 【ES6 又新增 Symbol 还有谷歌的 bigint】。
// 基本数据类型(简单类型),值类型: number,string,boolean。
// 复杂数据类型(引用类型),object。
// 空类型 undefined,null。
// 值类型的值在栈中存储,值类型传递,传递的是值。
// 引用类型的值,对象在堆上存储,地址在栈上存储。引用类型传递,传递的是地址(引用)。
let num = 10; // 值类型,值在栈上。
var obj = {}; //复杂类型,对象在堆,地址(引用)在栈。且使用 var 关键字不仅可以变量提升,在页面关闭之前一直在内存中。

// 提一下类型判断,typeof 一般只能返回如下几个结果:"number"、"string"、"boolean"、"object"、"function" 和 "undefined"。

// 如果我们要判断数组、时间等其他 new 对象,我们可以通过原生的方法,或者构造函数 constructor 来判断。
// Array.isArray(arr); 或 typeof arr === 'object' && arr.constructor === Array; 或 Array.prototype.isPrototypeOf(arr);
// typeof date === 'object' && date.constructor === Date;

// 当然也可以使用 instanceof 该运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
// ({}) instanceof Object; // true
// ([]) instanceof Object; // true 数组是对象的子类
// ([]) instanceof Array; // true
// (new Date()) instanceof Object; // true
// (new Date()) instanceof Date; // true 同上

// 【toString】还可以使用 Object.prototype.toString.call(val); 来判断类型
// Object.prototype.toString.call(1); // '[object Number]'
// Object.prototype.toString.call({}); // '[object Object]'
// Object.prototype.toString.call([]); // '[object Array]'
// Object.prototype.toString.call(function(){}); // '[object Function]'
// Object.prototype.toString.call(new Date); // '[object Date]'

// Since JavaScript 1.8.5
// Object.prototype.toString.call(null); // '[object Null]'
// Object.prototype.toString.call(); // '[object Undefined]'

toString

  • toString 方法返回一个表示该对象的字符串。
对象返回值
Array以逗号分割的字符串,如 [1, 2] 的toString返回值为 1,2
Booleantrue
Date可读的时间字符串,如 Tue Aug 25 2020 19:30:17 GMT+0800 (中国标准时间)
Function声明函数的 JS 源代码字符串
Number数字值
Object[object Object]
String字符串值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
let num = 3;
let str = '3';
let bool = true;
let obj = { test: '123', example: 123 };
let func = function () {
console.log('example');
};
var arr = ['test', 'example'];

num.toString(); // "3"
str.toString(); // "3"
bool.toString(); // "true"
obj.toString(); // "[object Object]"
func.toString(); // "function () { console.log('example'); }"
arr.toString(); // "test,example"

// 每个对象都有一个 toString() 方法,当该对象被表示为一个文本值时,或者一个对象以预期的字符串方式引用时自动调用。默认情况下,toString() 方法被每个 Object 对象继承。如果此方法在自定义对象中未被覆盖,toString() 返回 "[object type]",其中 type 是对象的类型。
// 上面【toString】我们也提到可以用他来判断类型
// 还可以自定义一个方法,来取代默认的 toString() 方法。该 toString() 方法不能传入参数,并且必须返回一个字符串。自定义的 toString() 方法可以是任何我们需要的值,但如果它附带有关对象的信息,它将变得非常有用。
function Dog(name, breed, color, sex) {
this.name = name;
this.breed = breed;
this.color = color;
this.sex = sex;
}
Dog.prototype.toString = function dogToString() {
return `Dog ${this.name} is a ${this.sex} ${this.color} ${this.breed}`;
};

var theDog = new Dog('Hellen', 'Lab', 'red', 'female');
theDog.toString(); // 'Dog Hellen is a female red Lab'

// 用于进制转换
Number(123).toString(2); // 1111011
Number(9).toString(5); // 14

// 转回 10 进制
parseInt(14, 5); // 9
parseInt(1111011, 2); // 123

valueOf

  • 如果对象存在任意原始值,它就默认将对象转换为表示它的原始值,如果对象是复合值,而且大多数对象无法真正表示为一个原始值,因此默认的 valueOf() 方法简单地返回对象本身,而不是返回一个原始值。
对象返回值
Array数组本身
Boolean布尔值
Date返回毫秒形式的时间戳
Function函数本身
Number数字值
Object对象本身
String字符串值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 数组、函数、和正则表达式简单的继承了这个默认方法,调用这些类型的实例的 valueOf() 方法只是简单返回对象本身。
// 所有的对象都继承有 toString() 和 valueOf() 方法,对象到字符串,对象到数字的转换,(隐式转换等)会通过调用待转换对象的这两个方法中的一个来完成。
// valueOf: 返回对象的原始值表示; toString: 返回对象的字符串表示;
let num = 3;
let str = '3';
let bool = true;
let obj = { test: '123', example: 123 };
let func = function () {
console.log('example');
};
var arr = ['test', 'example'];

num.valueOf(); // 3
str.valueOf(); // "3"
bool.valueOf(); // true
obj.valueOf(); // {test:'123', example:123}
func.valueOf(); // function () { console.log('example'); }
arr.valueOf(); // ['test','example']
  • 原始值:不可变更的值,包括 undefined、null、boolean、number、string

一般的转换过程

  • 栗子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
// 保存原始的 valueOf
const valueOf = Object.prototype.valueOf;
const toString = Object.prototype.toString;
// 添加 valueOf 日志
Object.prototype.valueOf = function () {
console.log('调用 valueOf');
return valueOf.call(this);
};
// 添加toString日志
Object.prototype.toString = function () {
console.log('调用 toString');
return toString.call(this);
};

/* ---------- 分割线 ---------- */

let obj = {};
let boolObj = new Boolean(false);
if (obj) {
console.log(1);
}
if (boolObj) {
console.log(2);
}
// 未调用 valueOf 和 toString,符合 [对象到布尔值] 的转换规则。

/* ---------- 分割线 ---------- */

console.log(++obj);
// 调用 valueOf (先调用了此方法返回的是对象本身,不是原始值,继续执行。)
// 调用 toString (再调用 toString 方法返回的是 "[object Object]",是原始值(字符串)。)
// NaN (将字符串转换为数字,结果变成了 NaN。)

/* ---------- 分割线 ---------- */

let objNum = {};
Object.prototype.valueOf = function () {
console.log('调用 valueOf');
return 1; // 强制返回原始值
};
console.log(++objNum);
// 调用 valueOf
// 2

/* ---------- 分割线 ---------- */

let objTest = {};
alert(objTest);
// 调用 toString
// 弹出 [object Object]
// 对象转字符串,调用 toString 方法,直接返回了字符串 "[object Object]",对象最终转换为该字符串。

/* ---------- 分割线 ---------- */

Object.prototype.toString = function () {
console.log('调用 toString');
return { noOriginVal: false };
};
Object.prototype.valueOf = function () {
// 还原前面的定义
console.log('调用 valueOf');
return valueOf.call(this);
};
alert(objTest);
// 调用 toString (调用 toString方法,返回的不是原始值,继续执行。)
// 调用 valueOf (调用 valueOf 方法,返回的不是原始值,继续执行。)
// Uncaught TypeError: Cannot convert object to primitive value

/* ---------- 分割线 ---------- */

Object.prototype.toString = function () {
// 还原前面的定义
console.log('调用 toString');
return toString.call(this);
};
let newObj = {};
console.log('hello ' + newObj);
// 调用 valueOf
// 调用 toString
// 'hello [object Object]'
  • 最后一处本应该是预期把 newObj 当做字符串使用,先调用 toString 方法的,实际情况其实却不是这样。
  1. 【基础步骤】如果有一个是对象,则遵循对象对原始值的转换过程 (Date对象直接调用 toString 完成转换,其他对象通过 valueOf 转化,如果转换不成功则调用 toString。)。
  2. 如果两个都是对象,两个对象都遵循【基础步骤】转换到字符串。
  3. 两个数字,进行算数运算。
  4. 两个字符串,直接拼接。
  5. 一个字符串一个数字,直接拼接为字符串。

keys

1
2
3
4
// 遍历可枚举的属性,只包含对象本身可枚举属性,不包含原型链可枚举属性。
// getOwnPropertyNames 与 keys 相似,但遍历包含不可枚举属性。
let testObj = {name: 'doubleam', age: 21};
Object.keys(testObj); // ["name", "age"]

values

1
2
3
// 遍历可枚举的属性值,只包含对象本身可枚举属性值,不包含原型链可枚举属性值。
let testObj = {name: 'doubleam', age: 21};
Object.values(testObj); // ["doubleam", 21]

entries

1
2
3
4
5
// entries 分割对象,获取键值对。
let testObj = {name: 'doubleam', age: 21};
Object.entries(testObj); // [["name", "doubleam", ["age", 21]]

new Map(Object.entries(testObj)); // Map(2) {"name" => "doubleam", "age" => 21}

is

1
2
3
4
5
6
7
8
// Object.is用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致,但也有差别。
Object.is('test', 'test'); // true
Object.is({}, {}); // false

+0 === -0; //true
NaN === NaN; // false
Object.is(+0, -0); // false
Object.is(NaN, NaN); // true

hasOwnProperty

1
2
3
// Object.hasOwnProperty 方法会返回一个布尔值,表示对象自身属性中是否具有指定的属性,不包含原型上的属性。
let testObj = {name: 'doubleam', age: 21};
testObj.hasOwnProperty('name'); // true

isPrototypeOf

1
2
3
4
5
6
7
8
// 用于测试一个对象是否存在于另一个对象的原型链上
let testObj = {name: 'doubleam', age: 21};
let extendObj = Object.create(testObj, {
gender: {
value: 'man'
}
});
testObj.isPrototypeOf(extendObj); // true

propertyIsEnumerable

1
2
3
4
5
// 指定的属性是否可枚举
let testObj = {name: 'doubleam', age: 21};
testObj.propertyIsEnumerable('name'); // true
testObj.propertyIsEnumerable('length'); // false 不可枚举属性
testObj.propertyIsEnumerable('toString'); // false 原型属性

defineProperty

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 定义对象属性,Object.defineProperty(object, prop, do)
// 添加属性
let testObj = Object.defineProperty({}, "newProp", {
value: 1, // 不能与 get set 同时存在
writable: true, // 不能与 get set 同时存在
enumerable: true,
configurable: true // false,不能重新修改装饰器。
});
testObj.newProp; // 1

// 修改属性
Object.defineProperty(testObj, "newProp", {
writable: false
});

// 添加 get set(Vue2 通过此方式进行数据劫持,实现数据监听,但有一些缺陷,Vue3 改为 Proxy,后续详细介绍。)
let testObj = {otherProp: '1'};
Object.defineProperty(testObj, "newProp", {
set: function (value) {
this.otherProp = value;
},
get: function () {
return this.otherProp;
},
enumerable: true,
configurable: true
});
testObj.newProp = 3;
testObj.otherProp; // 3

defineProperties

1
2
3
4
5
6
7
8
9
10
11
12
// 同上,但可定义多个。
let testObj = {};
Object.defineProperties(testObj, {
'prop1': {
value: 'Hello',
writable: true
},
'prop2': {
value: 'World',
writable: false
}
});

时间(Date)

万能 Date 原型扩展

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/* 时间格式参考 php */
Date.prototype.format = function (fmt = "yyyy-mm-dd hh:ii:ss") {
let o = {
'm+': this.getMonth() + 1, // 获取月份 month 需 + 1
'd+': this.getDate(), // 获取日
'h+': this.getHours(), // 获取小时
'i+': this.getMinutes(), // 获取分钟
's+': this.getSeconds(), // 获取秒
'Q+': Math.floor((this.getMonth() + 3) / 3), // 获取季度
'S': this.getMilliseconds() // 获取毫秒
};
if (/(y+)/.test(fmt)) {
fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
}
for (let k in o) {
if (new RegExp('(' + k + ')').test(fmt)) {
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
}
}
return fmt;
};

new Date().format(); // "2020-08-25 23:15:35"
new Date().format('mm-dd hh:ii:ss'); // "08-25 23:15:51"
new Date().format('hh:ii'); // "23:16"
new Date().format('yyyy-mm-01 00:00:00'); // "2020-08-01 00:00:00"

人性化时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
* 人性化时间
* @param {date|string} date 时间/string
* @param {boolean} longago 是否显示周月甚至更久
* @param {String} formater 正常显示时的时间显示格式,依赖于前一个原型方法。
* @return {String} 转换结果
*/
function timeSince(date, longago = false, formater = "yyyy-mm-dd hh:ii:ss") {
if (!date) {
return;
}
date.replace(/-/g, '/'); // 虽然 win 浏览器两种符号都可以,但是需兼容 ios。
let dateTS = new Date(date);
let seconds = Math.floor((new Date() - dateTS) / 1000);
let interval = Math.floor(seconds / (24 * 3600));
if (longago) {
interval = Math.floor(seconds / (30 * 24 * 3600));
if (interval >= 4) {
return dateTS.format(formater);
}
if (interval >= 1) {
return interval + " 月前";
}
interval = Math.floor(seconds / (7 * 24 * 3600));
if (interval >= 1) {
return interval + " 周前";
}
}
if (interval >= 8) {
return dateTS.format(formater);
}
interval = Math.floor(seconds / (24 * 3600));
if (interval >= 1) {
return interval + " 天前";
}
interval = Math.floor(seconds / 3600);
if (interval >= 1) {
return interval + " 小时前";
}
interval = Math.floor(seconds / 60);
if (interval >= 1) {
return interval + " 分钟前";
}
return "刚刚";
};

timeSince(new Date()); // "刚刚"

时间设置与计算

时间获取可以参考第一部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
let myDate = new Date(); 
myDate.setFullYear(myDate.getFullYear() + 1); // 设置日期,获取 1 年后。 [2021/8/25 下午11:20:35]
myDate.setMonth(myDate.getMonth() - 3); // 设置日期,获取 3 月前。 [2021/5/25 下午11:20:35]
myDate.setDate(myDate.getDate() + 7); // 设置日期,获取 7 天后。 [2021/6/1 下午11:20:35]
myDate.setHours(myDate.getHours() + 10); // 设置日期,获取 10 小时后。 [2021/6/2 上午9:20:35]
myDate.setMinutes(myDate.getMinutes() + 10); // 设置日期,获取 10 分钟后。 [2021/6/2 上午9:30:35]
myDate.setSeconds(myDate.getSeconds() + 60); // 设置日期,获取 60 秒后。 [2021/6/2 上午9:31:35]

+ new Date(); // 1598370615000 快速获取时间戳
new Date().getTime(); // 1598370615000

let now = new Date();
let old = new Date("2020-07-25 21:50:15");
let seconds = Math.floor((now - old) / 1000); // 计算时间相差秒 2685600
let minutes = Math.floor(seconds / 60); // 计算时间相差分钟 44760
let hours = Math.floor(minutes / 60); // 计算时间相差小时 746
let days = Math.floor(hours / 24); // 计算时间相差天数 31

/* 计算实际差距天数、小时、分钟、秒 */
let runTime = now - old;
let differenceObj = {
days: Math.floor(runTime / 1000 / 3600 / 24),
hours: Math.floor(runTime / 1000 / 60 / 60 % 24),
minutes: Math.floor(runTime / 1000 / 60 % 60),
seconds: Math.floor(runTime / 1000 % 60)
}; // {days: 31, hours: 2, minutes: 0, seconds: 0}

Others

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let weeks = ["星期天", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"];
weeks[new Date().getDay()]; // 获取星期 0-6

new Date().toLocaleTimeString(); // 获取当前时间 "下午5:49:50"

new Date().toLocaleDateString(); // 获取当前日期 "2020/8/25"

// 其他时间字符串转化
// toJSON() 方法可以将 Date 对象转换为字符串,并格式化为 JSON 数据格式。
// JSON 数据用同样的格式就像x ISO-8601 标准: YYYY-MM-DDThh:ii:ss.sssZ
new Date().toJSON(); // "2020/8/25T23:51:13.411Z"
new Date().toGMTString(); // "Tue, 25 Aug 2020 23:51:20 GMT"
new Date().toISOString(); // "2020/8/25T23:51:13.423Z"
new Date().toUTCString(); // "Tue, 25 Aug 2020 23:51:19 GMT"
new Date().toString(); // "Tue Aug 25 2020 23:51:16 GMT+0800 (中国标准时间)"
new Date().toTimeString(); // "23:51:13 GMT+0800 (中国标准时间)"
new Date().toDateString(); // "Tue Aug 25 2020"

new Date().toLocaleString(); // 获取当前日期时间 "2020/8/25 下午11:52:23"
new Date().toLocaleString('chinese', { hour12: false }); // 24小时制 "2020/8/25 23:52:57"
bulb