资料来源各前端大佬和自己整理,仅作为个人学习参考用,转载传递请注明来源
联系E-mail:[email protected]
:star: :star: :star: :star: :star: JS 学习记录和遇到的坑、常用功能代码收集整理
资料来源各前端大佬和自己整理,仅作为个人学习参考用,转载传递请注明来源
联系E-mail:[email protected]
年日时分秒
/**
* 获取时分秒格式化后的字符串
*
* @param {number} totalSeconds 相差的秒(单位:秒)
*
* @example
* ```
* getYearToSeconds(17240);
* => '4小时47分20秒'
*
* getYearToSconds(1527684151664);
* =>
* ```
*/
function getYearToSeconds(totalSeconds) {
/** 格式化的字符串 */
let timeStr = '';
// 年
const year = Math.floor(totalSeconds / (365 * 24 * 60 * 60));
if (year > 0) {
timeStr += `${year}年`;
}
// 天
const lastDay = Math.floor(totalSeconds % (365 * 24 * 60 * 60));
const day = Math.floor(lastDay / (24 * 60 * 60));
if (day > 0) {
timeStr += `${day}天`;
}
// 小时
const lastHour = Math.floor(totalSeconds % (24 * 60 * 60));
const hours = Math.floor(lastHour / (60 * 60));
if (hours > 0) {
timeStr += `${hours}时`;
}
// 分
const lastMinutes = Math.floor(lastHour % (60 * 60));
const minutes = Math.floor(lastMinutes / 60);
if (minutes > 0) {
timeStr += `${minutes}分`;
}
// 秒
const lastSeconds = Math.floor(lastMinutes % 60);
const seconds = lastSeconds;
if (seconds > 0) {
timeStr += `${seconds}秒`;
}
console.log(timeStr);
}
getYearToSeconds(17240);
// new Date().getTime() 返回值的单位是毫秒数
const initToNowSeconds = Math.floor(1527684151664 / 1000);
getYearToSeconds(initToNowSeconds);
// 5月30天
// 某一天距离现在的时间搓
const date1 = new Date().getTime() - new Date('2015/05/30 21:00:00');
// console.log(date1);
getYearToSeconds( Math.floor(94694453666 / 1000) );
// 指定某两天的间隔
const g1 = new Date('2019/05/30 21:00:00') - new Date('2017/05/30 21:00:00');
getYearToSeconds( Math.floor( g1 / 1000 ) )
// 1527685963162 2018/05/30 21:13
// 1527686110907 2018/05/30 21:15
getYearToSeconds( Math.floor((1527686110907 - 1527685963162) / 1000) );
/**
* 参考资料
* + [闰年-百度百科](https://baike.baidu.com/item/%E9%97%B0%E5%B9%B4/27098?fr=aladdin)
* + [MDN-Date](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Date)
*/
JS 是包含 DOM BOM ECMAScript。ECMAScript 定义了 JS 的基本语法和用法,但是不包括DOM、BOM
DOM 和 BOM 并不属于 JavaScript(ECMAScript ) 语言的一部分。DOM 和 BOM 是 JavaScript 的运行平台(浏览器)提供的,比如在 nodejs 中就没有 DOM 和 BOM。
JavaScript 类型分为 2 大类:原生类型和对象类型。而 DOM 和 BOM 都是对象类型。
比如 html 中的段落 p 映射为 JavaScript 对象是 HTMLParagraphElement,顾名思义 Paragraph 就是英语“段落”的意思。
我们看看 HTMLParagraphElement 的继承关系:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<button id="btn1">btn1</button>
<button id="btn2">btn2</button>
<button id="btn3">btn3</button>
<button id="btn4">btn4</button>
<script>
function autoRun() {
const oBtn1 = document.getElementById('btn1');
oBtn1.addEventListener('click', function() {
console.log('oBtn1 this:', this); // => Dom #btn1
});
}
autoRun();
const oBtn2 = document.getElementById('btn2');
oBtn2.addEventListener('click', function() {
console.log('oBtn2 this:', this); // => Dom #btn2
});
const obj1 = {x: 1, y: 2};
obj1.onClick = function() {
console.log('oBtn3 this:', this); // => Dom #btn3
}
const oBtn3 = document.getElementById('btn3');
oBtn3.addEventListener('click', obj1.onClick);
function autoGo() {
const a = 111;
const obj2 = {x: 2};
obj2.onClick = function() {
console.log('oBtn4 this:', this);
}
const oBtn4 = document.getElementById('btn4');
oBtn4.addEventListener('click', obj2.onClick); // => Dom #btn4
}
autoGo();
</script>
</body>
</html>
更新时间 | 是否原创 |
---|---|
2019/01/10 | 原创 |
构造函数方式
创建数组语法// method1
new Array()
// method2
new Array(size)
// method3
new Array(elem0, elem1, ..., elem)
参数
参数 | 说明 | 类型 | 可选值 | 默认值 |
---|---|---|---|---|
无参数 | 创建一个长度为0的空数组 | - | - | - |
只一个参数 | 创建数组的长度, Array.length = size | number | - | - |
多参数: 参数 | 返回的数组填充的项 | any | - | - |
示例
const arr1 = new Array();
const arr2 = new Array(1);
const arr3 = new Array(0, 1, 2, 3, 4);
const arr4 = new Array(0, [1, 3], [4, 5]);
console.log(arr1); // []
console.log(arr2); // [empty]
console.log(arr3); // [0, 1, 2, 3, 4];
console.log(arr4); // [0, [1, 3], [4, 5]];
上面代码分析:不传参数,返回的是一个空数组;传了一个参数,且为数字,返回一个指定长度的空数组。空数组每一项被 undefined
填充;传了多个参数,且每项都为数字,返回一个包含所有参数的数组; 传了多个参数且某项为数组时,会将数组作为一个整体,放入返回的新数组中。
const arr = new Array(5).fill(1);
console.log(arr); // [1, 1, 1, 1, 1]
上面代码分析: 通过上面代码,可以快速创建指定长度和指定每项内容的数组。
直接量
语法创建数组ES3规定了数组的直接量语法
let a = [1, true, 'abc'];
let b = [a[0], a[0]*2, f(x)];
返回值
创建后的数组
异常
当size为负值,或者大于
2^32 - 1
时会抛出RangeError
异常
属性
length
一个可读/写的整数,用来指明数组中的元素个数。数组不连续时,length = 数组中最后一个元素的序号加一。改变length值会裁减或扩充数组
描述
读: 返回数组的长度。写(赋值): 改变数组的长度
示例
let a = new Array(); // a.length 初始化为 0
let b = new Array(3); // b.length 初始化为 3
let c = new Array('one', 'two', 'three'); // c.length 初始化为 3
c[4] = 'four'; // c.length 更新为 5
c[5] = 'blastoff'; // c.length 变成 11 // c.length 更新为 6
console.log(c); // ["one", "two", "three", empty, "four", "blastoff"]
console.log(c[3]); // undefined 空的项被 undefined 填充
方法列表
// @desc 通过给Function.prototype增加方法来使得该方法对所有函数都可用
// 通过给Function.prototype增加一个method方法,我们下次给对象增加
// 方法的时候就不必键入prototype这一个字符,省去了麻烦,
// 这样添加以后所有的构造函数(这里是给函数添加的方法)由于构造函数
// 也是函数,所以所有的函数都可以用
// @example
// ```
// Array.method('eReduce', func)
// ```
if (Function.prototype.method !== 'function') {
Function.prototype.method = function(name, func) {
// TODO: 扩展函数的功能
}
}
把元素衔接到数组中
语法
array.concat(value, ...)
参数
参数 | 说明 | 类型 | 可选值 | 默认值 |
---|---|---|---|---|
value | 衔接的数组, 数字类型就表示也表示数组,非必需 | `number | array` | - |
... | 若干需要衔接的数组,非必需 | `number | array` | - |
返回值
一个
新数组
,不改变源
数组,包含array
的元素,以及衔接的新元素
描述
产生一个新数组,包含一份浅复制(shadow copy)并把一个或多个参数item附加在其后
示例
// 字面量数组方式
let a = [1, 2, 3];
let a1 = a.concat(4, 5); // => a1 = [1, 2, 3, 4, 5]; a不变
let a2 = a.concat([4, 5]); // => a2 = [1, 2, 3, 4, 5]; a不变
let a3 = a.concat([4, 5], [6, 7]); // => a3 = [1, 2 ,3 ,4, 5, 6, 7]; a不变
let a4 = a.concat(4, [5, [6, 7]]); // => a4 = [1, 2, 3, 4, 5, [6, 7]]; a不变
通过以上代码发现:concat的返回结果为一个新的数组,不改变源数组
// 数组变量方式
const a = [1, 2, 3],
b = [4, 5],
c = [6, [0, 0]];
let d = [],
e = [];
d = a.concat(a, b);
console.log(d); // [ 1, 2, 3, 1, 2, 3, 4, 5 ]
console.log(a); // [ 1, 2, 3 ]
console.log(b); // [ 4, 5 ]
以上代码发现:d为 [ 1, 2, 3, 1, 2, 3, 4, 5 ]
。 a, b数组并未发生改变,d中由两个a
和一个b
构成
const a = [1, 2, 3],
b = [4, 5],
c = [6, [0, 0]];
let d = [],
e = [];
e = a.concat(b, c);
console.log(e); // [ 1, 2, 3, 4, 5, 6, [ 0, 0 ] ]
console.log(c); // [ 6, [ 0, 0 ] ]
以上代码发现:concat 方法里面的参数如果存在 多维数组
,将多维数组作为整体组合到新数组中.
功能类似的其他方法
Array.join() Array.push() Array.splice()
ES5
测试
断言函数:predicateFn
是否对每个元素为真
语法
参数
参数 | 是否必需 | 描述 |
---|---|---|
predicateFn | 非必需 | 用来测试数组元素的断言函数 predicateFn(array[i], i, array) |
o | 非必需 | 调用断言函数predicateFn时可选的 this 值 |
返回值
全部满足断言函数返回true,否则返回false;不改变当前数组
文字待整理
let result1 = [1, 2, 3].every( x => x < 5 ); // => result1 = true
let result2 = [1, 2, 3].every( x => x < 3 ); // => result2 = false
let result3 = [].every( x => false ); // true: []总返回true
Array.filter() Array.forEach() Array.some()
ES5
返回通过断言的数组元素
predicate
判断array中每项是否需要包含在返回数组中的调用函数 predicate(array[i], i, array)
o
调用断言函数predicate时可选的this值
一个新数组,只包含那些让predicate返回真值的数组元素
文字待整理
let result = [1, 2, 3].filter( x => x > 1 ) // => result = [2, 3];
Array.every() Array.forEach() Array.indexOf() Array.map() Array.reduce()
ES5
为每个数组元素调用一个函数
f
为array的每项调用的函数 array.forEach(array[i], i, array)
o
调用f时的可选this值
该方法无返回值
文字待整理
let a = [1, 2, 3];
a.forEach( (x, i, a) => { a[i]++; }); // a = [1, 4, 9];
Array.every() Array.filter() Array.indexOf() Array.map() Array.reduce()
ES5
查找数组
value
要在array中查找的值
start
开始查找的可选数组序号。如果省略则为0
从start开始最小序号值,该序号值处的array元素与value全等。如果不存在匹配的元素,返回-1
文字待整理
let result1 = ['a', 'b', 'c'].indexOf('b'); // => 1 即 result1 == 1
let result2 = ['a', 'b', 'c'].indexOf('d'); // => -1 即 result2 == -1
let result3 = ['a', 'b', 'c'].indexOf('a', 1); // => -1 即 result3 == -1
Array.lastIndexOf() String.indexOf()
将数组元素衔接为字符串
spearator
在返回的字符串中,用来分割数组某个元素与下一个元素的可选字符或字符串。如果省略,默认是英文逗号(,)
一个字符串。将array的每个元素转换为字符串。然后用separator字符串分隔开。最后衔为返回的字符串
文字待整理
let a = new Array(1, 2, 3, 'testing');
s = a.join('+'); // => s == '1+2+3+testing'
String.split()
ES5
反向查找数组
value
要在array中查找的值
start
开始查找的可选数组序号。如果省略,则从最后一个元素开始查找
一个小于等于start的最大序号值,该序号值处的array元素与value相等。如果不存在匹配的元素时,返回-1。
文字待整理
Array.indexOf() String.lastIndexOf()
ES5
从数值元素中计算新值
f
为array的每个元素调用的函数。它的返回值会成为数组的元素
o
f调用时可选的this值
一个新数组,由函数f计算出的元素组成
文字待整理
let result = [1, 2, 3].map( x => x*x ); // => result == [1, 4, 9]
Array.every() Array.filter() Array.forEach() Array.indexOf() Array.reduce()
移除并返回数组的最后一个元素
array的最后一元素
文字待整理
// pop()与伴随的push()方法。可以提供先进后出(FILO)的栈功能
let stack = []; // stack: []
let s1 = stack.push(1, 2); // stack: [1, 2] s1 == 2
let s2 = stack.pop(); // stack: [1] s2 == 2
let s3 = stack.push([4, 5]); // stack: [1, [4, 5]] s3 == 2
let s4 = stack.pop(); // stack: [1] s4 == [4, 5]
let s5 = stack.pop(); // stack: [] s5 == 1
Array.push()
给数组追加元素
array.push(value, ...);
vlaue, ...
追加到array尾部的一个或多个值
把指定值追加到数组后数组的新长度
文字待整理
Array.pop()
ES5
从数组元素中计算出一个值
f
一个函数,可以合并两个值(比如两个数组元素),并返回一个‘缩减’的新值
inital
用来缩减数组的可选初始值。如果指定该参数, reduce()的行为会像是把该参数插入array的头部一样
数组的简化值,该值是最后一次调用f时的返回值
文字待整理
let r = [1, 2, 3, 4].reduce((x, y) => x*y); // => r == 24
Array.forEach() Array.map() Array.reduceRight()
ES5
从右到做缩减数组
f
一个函数,可以合并两个值(比如两个数组元素),并返回一个“缩减”的新值
initial
用来缩减数组的可选初始值,如果指定该参数,reduceRight()的行为就会是把该参数插入array的尾部一样
数组的缩减值,该值是最有一次调用f时的返回值
文字待整理
let r = [2, 10, 60].reduceRight((x, y) => x / y); // => r == 3
Array.reduce()
颠倒数组中的元素顺序
文字待整理
let a = new Array(1, 2, 3); // => a[0] == 1, a[2] == 3;
a.reverse(); // => a[0] == 3, a[2] == 1;
移除数组的第一个元素
数组原来的第一元素
文字待整理
let a = [1, [2, 3], 4];
let s = a.shift(); // => s == 1; a == [[2, 3], 4];
let s2 = a.shift(); // => s2 == [2, 3]; a == [4]
Array.pop() Array.unshift()
返回数组的一部分
array.slice(start, end)
start
数组片段开始处的数组序号。如果为负数,则表示从数组的尾部开始计算,也就是说-1代表最有一个元素, -2代表倒数第二个元素。以此类推
end
数组片段结束处的后一个元素的数组序号。如果没有指定,该片段会包含从start开始到数组尾部的所有数组元素。如果为负数,则表示从数组的尾部开始计算
一个新数组,包含array中从start一直到end直接的所有元素(包含start指定的元素,但不包含end指定的元素)
文字待整理
let a = [1, 2, 3, 4, 5];
let s1 = a.slice(0, 3); // => s1 == [1, 2, 3]
let s2 = a.slice(3); // s2 == [4, 5]
let s3 = a.slice(1, -1); // s3 == [2, 3, 4]
let s4 = a.slice(-3, -2); // => s4 == [3] ;但在ie4下有误,返回[1, 2, 3]
在IE4中start参数不能为负数, 在IE的后续版本中已经修复该bug
Array.splice()
ES5
测试是否有元素满足断言函数
predicate
用来测试数组元素的断言函数
o
调用predicate时的可选this值
如果array中至少有一个元素调用predicate时返回真值,则返回true.如果所有元素调用predicate时都返回假值,则返回false
文字待整理
let s1 = [1, 2, 3].some(x => x > 5); // => s1 == false
let s2 = [1, 2, 3].some(x => x > 2); // => s2 == true
let s3 = [1, 2, 3].some(x => false); // => s3 = false; []总是返回false
Array.every() Array.filter() Array.forEach()
对数组元素进行排序
orderFunc
用来指定如何排序的可选函数
该数组的引用。注意是在原数组中进行排序,没有新建数组
文字待整理
// 用来数组排序的排序函数
function numberOreder(a, b) { return a - b };
a = new Array(33, 4, 111, 222);
let s1 = a.sort(); // => s1 == [1111, 222, 33, 4] 字母排序
let s2 = a.sort( numberOrder ); // => s2 == [4, 33, 222, 1111] 数值排序
插入、删除或替换数组元素
start
开始插入和(或)删除处的数组元素的序号
deleteCount
要删除的元素个数,从start开始,并包含start开始出的元素,如果指定为0,表示插入元素,而不是删除元素
value, ...
要插入数组中的零个或多个值,从start序号处开始插入
如果从array中删除了元素,则返回一个新数组,包含这些删除的元素
var a = [1, 2, 3, 4, 5, 6, 7, 8];
let s1 = a.splice(1, 2); // => s1 == [2, 3]; a == [1, 4, 5, 6, 7, 8];
let s2 = a.splice(1, 1); // => s2 == [4]; a == [1, 5, 6, 7, 8];
let s3 = a.splice(1,0,2,3) // => s3 == []; a == [1,2,3,5,6,7,8]
Array.slice()
将数组转化为本地化字符串
array.toLocalString()
数组的本地化字符串表示
TypeError
调用该方法时,如果对象不是Array,则抛出该异常
文字待整理
Array.toString() Object.toLocalString()
将数组转化为字符串
array.toString()
array的字符串表示
TypeError
调用该方法时,如果对象不是Array,则抛出该异常
文字待整理
Array.toLocalString() Object.toString()
在数组头部插入元素
array.unshift(value, ...)
value, ...
要插入array头部的一个或多个值
数组的新长度
文字待整理
// unshift()经常与shift()一起使用
let a = [];
let s1 = a.unshift(1); // => s1 == 1; a == [1]
let s2 = a.unshift(22); // => s2 == 2; a == [22, 1]
let s3 = a.shift(); // => s3 == 1; a == [1];
let s4 = a.unshift(33, [4, 5]); // => s4 == 3 ; a == [33, [4, 5], 1];
Array.shift()
扩展运算符(spread)是三个点(...
)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。
console.log(...[1, 2, 3])
// 1 2 3
console.log(1, ...[2, 3, 4], 5)
// 1 2 3 4 5
[...document.querySelectorAll('div')]
// [<div>, <div>, <div>]
该运算符主要用于函数调用。
function push(array, ...items) {
array.push(...items);
}
function add(x, y) {
return x + y;
}
var numbers = [4, 38];
add(...numbers) // 42
上面代码中,array.push(...items)
和add(...numbers)
这两行,都是函数的调用,它们的都使用了扩展运算符。该运算符将一个数组,变为参数序列。
扩展运算符与正常的函数参数可以结合使用,非常灵活。
function f(v, w, x, y, z) { }
var args = [0, 1];
f(-1, ...args, 2, ...[3]);
扩展运算符后面还可以放置表达式。
const arr = [
...(x > 0 ? ['a'] : []),
'b',
];
如果扩展运算符后面是一个空数组,则不产生任何效果。
[...[], 1]
// [1]
由于扩展运算符可以展开数组,所以不再需要apply
方法,将数组转为函数的参数了。
// ES5 的写法
function f(x, y, z) {
// ...
}
var args = [0, 1, 2];
f.apply(null, args);
// ES6的写法
function f(x, y, z) {
// ...
}
var args = [0, 1, 2];
f(...args);
下面是扩展运算符取代apply
方法的一个实际的例子,应用Math.max
方法,简化求出一个数组最大元素的写法。
// ES5 的写法
Math.max.apply(null, [14, 3, 77])
// ES6 的写法
Math.max(...[14, 3, 77])
// 等同于
Math.max(14, 3, 77);
上面代码中,由于 JavaScript 不提供求数组最大元素的函数,所以只能套用Math.max
函数,将数组转为一个参数序列,然后求最大值。有了扩展运算符以后,就可以直接用Math.max
了。
另一个例子是通过push
函数,将一个数组添加到另一个数组的尾部。
// ES5的 写法
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
Array.prototype.push.apply(arr1, arr2);
// ES6 的写法
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
arr1.push(...arr2);
上面代码的 ES5 写法中,push
方法的参数不能是数组,所以只好通过apply
方法变通使用push
方法。有了扩展运算符,就可以直接将数组传入push
方法。
下面是另外一个例子。
// ES5
new (Date.bind.apply(Date, [null, 2015, 1, 1]))
// ES6
new Date(...[2015, 1, 1]);
(1)合并数组
扩展运算符提供了数组合并的新写法。
// ES5
[1, 2].concat(more)
// ES6
[1, 2, ...more]
var arr1 = ['a', 'b'];
var arr2 = ['c'];
var arr3 = ['d', 'e'];
// ES5的合并数组
arr1.concat(arr2, arr3);
// [ 'a', 'b', 'c', 'd', 'e' ]
// ES6的合并数组
[...arr1, ...arr2, ...arr3]
// [ 'a', 'b', 'c', 'd', 'e' ]
(2)与解构赋值结合
扩展运算符可以与解构赋值结合起来,用于生成数组。
// ES5
a = list[0], rest = list.slice(1)
// ES6
[a, ...rest] = list
下面是另外一些例子。
const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest // [2, 3, 4, 5]
const [first, ...rest] = [];
first // undefined
rest // []
const [first, ...rest] = ["foo"];
first // "foo"
rest // []
如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。
const [...butLast, last] = [1, 2, 3, 4, 5];
// 报错
const [first, ...middle, last] = [1, 2, 3, 4, 5];
// 报错
(3)函数的返回值
JavaScript 的函数只能返回一个值,如果需要返回多个值,只能返回数组或对象。扩展运算符提供了解决这个问题的一种变通方法。
var dateFields = readDateFields(database);
var d = new Date(...dateFields);
上面代码从数据库取出一行数据,通过扩展运算符,直接将其传入构造函数Date
。
(4)字符串
扩展运算符还可以将字符串转为真正的数组。
[...'hello']
// [ "h", "e", "l", "l", "o" ]
上面的写法,有一个重要的好处,那就是能够正确识别32位的Unicode字符。
'x\uD83D\uDE80y'.length // 4
[...'x\uD83D\uDE80y'].length // 3
上面代码的第一种写法,JavaScript会将32位Unicode字符,识别为2个字符,采用扩展运算符就没有这个问题。因此,正确返回字符串长度的函数,可以像下面这样写。
function length(str) {
return [...str].length;
}
length('x\uD83D\uDE80y') // 3
凡是涉及到操作32位 Unicode 字符的函数,都有这个问题。因此,最好都用扩展运算符改写。
let str = 'x\uD83D\uDE80y';
str.split('').reverse().join('')
// 'y\uDE80\uD83Dx'
[...str].reverse().join('')
// 'y\uD83D\uDE80x'
上面代码中,如果不用扩展运算符,字符串的reverse
操作就不正确。
(5)实现了 Iterator 接口的对象
任何 Iterator 接口的对象(参阅 Iterator 一章),都可以用扩展运算符转为真正的数组。
var nodeList = document.querySelectorAll('div');
var array = [...nodeList];
上面代码中,querySelectorAll
方法返回的是一个nodeList
对象。它不是数组,而是一个类似数组的对象。这时,扩展运算符可以将其转为真正的数组,原因就在于NodeList
对象实现了 Iterator 。
对于那些没有部署 Iterator 接口的类似数组的对象,扩展运算符就无法将其转为真正的数组。
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};
// TypeError: Cannot spread non-iterable object.
let arr = [...arrayLike];
上面代码中,arrayLike
是一个类似数组的对象,但是没有部署 Iterator 接口,扩展运算符就会报错。这时,可以改为使用Array.from
方法将arrayLike
转为真正的数组。
(6)Map 和 Set 结构,Generator 函数
扩展运算符内部调用的是数据结构的 Iterator 接口,因此只要具有 Iterator 接口的对象,都可以使用扩展运算符,比如 Map 结构。
let map = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);
let arr = [...map.keys()]; // [1, 2, 3]
Generator 函数运行后,返回一个遍历器对象,因此也可以使用扩展运算符。
var go = function*(){
yield 1;
yield 2;
yield 3;
};
[...go()] // [1, 2, 3]
上面代码中,变量go
是一个 Generator 函数,执行后返回的是一个遍历器对象,对这个遍历器对象执行扩展运算符,就会将内部遍历得到的值,转为一个数组。
如果对没有 Iterator 接口的对象,使用扩展运算符,将会报错。
var obj = {a: 1, b: 2};
let arr = [...obj]; // TypeError: Cannot spread non-iterable object
Array.from
方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括ES6新增的数据结构Set和Map)。
下面是一个类似数组的对象,Array.from
将它转为真正的数组。
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};
// ES5的写法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']
// ES6的写法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
实际应用中,常见的类似数组的对象是DOM操作返回的NodeList集合,以及函数内部的arguments
对象。Array.from
都可以将它们转为真正的数组。
// NodeList对象
let ps = document.querySelectorAll('p');
Array.from(ps).forEach(function (p) {
console.log(p);
});
// arguments对象
function foo() {
var args = Array.from(arguments);
// ...
}
上面代码中,querySelectorAll
方法返回的是一个类似数组的对象,可以将这个对象转为真正的数组,再使用forEach
方法。
只要是部署了Iterator接口的数据结构,Array.from
都能将其转为数组。
Array.from('hello')
// ['h', 'e', 'l', 'l', 'o']
let namesSet = new Set(['a', 'b'])
Array.from(namesSet) // ['a', 'b']
上面代码中,字符串和Set结构都具有Iterator接口,因此可以被Array.from
转为真正的数组。
如果参数是一个真正的数组,Array.from
会返回一个一模一样的新数组。
Array.from([1, 2, 3])
// [1, 2, 3]
值得提醒的是,扩展运算符(...
)也可以将某些数据结构转为数组。
// arguments对象
function foo() {
var args = [...arguments];
}
// NodeList对象
[...document.querySelectorAll('div')]
扩展运算符背后调用的是遍历器接口(Symbol.iterator
),如果一个对象没有部署这个接口,就无法转换。Array.from
方法还支持类似数组的对象。所谓类似数组的对象,本质特征只有一点,即必须有length
属性。因此,任何有length
属性的对象,都可以通过Array.from
方法转为数组,而此时扩展运算符就无法转换。
Array.from({ length: 3 });
// [ undefined, undefined, undefined ]
上面代码中,Array.from
返回了一个具有三个成员的数组,每个位置的值都是undefined
。扩展运算符转换不了这个对象。
对于还没有部署该方法的浏览器,可以用Array.prototype.slice
方法替代。
const toArray = (() =>
Array.from ? Array.from : obj => [].slice.call(obj)
)();
Array.from
还可以接受第二个参数,作用类似于数组的map
方法,用来对每个元素进行处理,将处理后的值放入返回的数组。
Array.from(arrayLike, x => x * x);
// 等同于
Array.from(arrayLike).map(x => x * x);
Array.from([1, 2, 3], (x) => x * x)
// [1, 4, 9]
下面的例子是取出一组DOM节点的文本内容。
let spans = document.querySelectorAll('span.name');
// map()
let names1 = Array.prototype.map.call(spans, s => s.textContent);
// Array.from()
let names2 = Array.from(spans, s => s.textContent)
下面的例子将数组中布尔值为false
的成员转为0
。
Array.from([1, , 2, , 3], (n) => n || 0)
// [1, 0, 2, 0, 3]
另一个例子是返回各种数据的类型。
function typesOf () {
return Array.from(arguments, value => typeof value)
}
typesOf(null, [], NaN)
// ['object', 'object', 'number']
如果map
函数里面用到了this
关键字,还可以传入Array.from
的第三个参数,用来绑定this
。
Array.from()
可以将各种值转为真正的数组,并且还提供map
功能。这实际上意味着,只要有一个原始的数据结构,你就可以先对它的值进行处理,然后转成规范的数组结构,进而就可以使用数量众多的数组方法。
Array.from({ length: 2 }, () => 'jack')
// ['jack', 'jack']
上面代码中,Array.from
的第一个参数指定了第二个参数运行的次数。这种特性可以让该方法的用法变得非常灵活。
Array.from()
的另一个应用是,将字符串转为数组,然后返回字符串的长度。因为它能正确处理各种Unicode字符,可以避免JavaScript将大于\uFFFF
的Unicode字符,算作两个字符的bug。
function countSymbols(string) {
return Array.from(string).length;
}
Array.of
方法用于将一组值,转换为数组。
Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1
这个方法的主要目的,是弥补数组构造函数Array()
的不足。因为参数个数的不同,会导致Array()
的行为有差异。
Array() // []
Array(3) // [, , ,]
Array(3, 11, 8) // [3, 11, 8]
上面代码中,Array
方法没有参数、一个参数、三个参数时,返回结果都不一样。只有当参数个数不少于2个时,Array()
才会返回由参数组成的新数组。参数个数只有一个时,实际上是指定数组的长度。
Array.of
基本上可以用来替代Array()
或new Array()
,并且不存在由于参数不同而导致的重载。它的行为非常统一。
Array.of() // []
Array.of(undefined) // [undefined]
Array.of(1) // [1]
Array.of(1, 2) // [1, 2]
Array.of
总是返回参数值组成的数组。如果没有参数,就返回一个空数组。
Array.of
方法可以用下面的代码模拟实现。
function ArrayOf(){
return [].slice.call(arguments);
}
数组实例的copyWithin
方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。
Array.prototype.copyWithin(target, start = 0, end = this.length)
它接受三个参数。
这三个参数都应该是数值,如果不是,会自动转为数值。
[1, 2, 3, 4, 5].copyWithin(0, 3)
// [4, 5, 3, 4, 5]
上面代码表示将从3号位直到数组结束的成员(4和5),复制到从0号位开始的位置,结果覆盖了原来的1和2。
下面是更多例子。
// 将3号位复制到0号位
[1, 2, 3, 4, 5].copyWithin(0, 3, 4)
// [4, 2, 3, 4, 5]
// -2相当于3号位,-1相当于4号位
[1, 2, 3, 4, 5].copyWithin(0, -2, -1)
// [4, 2, 3, 4, 5]
// 将3号位复制到0号位
[].copyWithin.call({length: 5, 3: 1}, 0, 3)
// {0: 1, 3: 1, length: 5}
// 将2号位到数组结束,复制到0号位
var i32a = new Int32Array([1, 2, 3, 4, 5]);
i32a.copyWithin(0, 2);
// Int32Array [3, 4, 5, 4, 5]
// 对于没有部署 TypedArray 的 copyWithin 方法的平台
// 需要采用下面的写法
[].copyWithin.call(new Int32Array([1, 2, 3, 4, 5]), 0, 3, 4);
// Int32Array [4, 2, 3, 4, 5]
数组实例的find
方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true
的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined
。
[1, 4, -5, 10].find((n) => n < 0)
// -5
上面代码找出数组中第一个小于0的成员。
[1, 5, 10, 15].find(function(value, index, arr) {
return value > 9;
}) // 10
上面代码中,find
方法的回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组。
数组实例的findIndex
方法的用法与find
方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1
。
[1, 5, 10, 15].findIndex(function(value, index, arr) {
return value > 9;
}) // 2
这两个方法都可以接受第二个参数,用来绑定回调函数的this
对象。
另外,这两个方法都可以发现NaN
,弥补了数组的IndexOf
方法的不足。
[NaN].indexOf(NaN)
// -1
[NaN].findIndex(y => Object.is(NaN, y))
// 0
上面代码中,indexOf
方法无法识别数组的NaN
成员,但是findIndex
方法可以借助Object.is
方法做到。
fill
方法使用给定值,填充一个数组。
['a', 'b', 'c'].fill(7)
// [7, 7, 7]
new Array(3).fill(7)
// [7, 7, 7]
上面代码表明,fill
方法用于空数组的初始化非常方便。数组中已有的元素,会被全部抹去。
fill
方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置。
['a', 'b', 'c'].fill(7, 1, 2)
// ['a', 7, 'c']
上面代码表示,fill
方法从1号位开始,向原数组填充7,到2号位之前结束。
ES6 提供三个新的方法——entries()
,keys()
和values()
——用于遍历数组。它们都返回一个遍历器对象(详见《Iterator》一章),可以用for...of
循环进行遍历,唯一的区别是keys()
是对键名的遍历、values()
是对键值的遍历,entries()
是对键值对的遍历。
for (let index of ['a', 'b'].keys()) {
console.log(index);
}
// 0
// 1
for (let elem of ['a', 'b'].values()) {
console.log(elem);
}
// 'a'
// 'b'
for (let [index, elem] of ['a', 'b'].entries()) {
console.log(index, elem);
}
// 0 "a"
// 1 "b"
如果不使用for...of
循环,可以手动调用遍历器对象的next
方法,进行遍历。
let letter = ['a', 'b', 'c'];
let entries = letter.entries();
console.log(entries.next().value); // [0, 'a']
console.log(entries.next().value); // [1, 'b']
console.log(entries.next().value); // [2, 'c']
Array.prototype.includes
方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes
方法类似。ES2016 引入了该方法。
[1, 2, 3].includes(2) // true
[1, 2, 3].includes(4) // false
[1, 2, NaN].includes(NaN) // true
该方法的第二个参数表示搜索的起始位置,默认为0
。如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为-4
,但数组长度为3
),则会重置为从0
开始。
[1, 2, 3].includes(3, 3); // false
[1, 2, 3].includes(3, -1); // true
没有该方法之前,我们通常使用数组的indexOf
方法,检查是否包含某个值。
if (arr.indexOf(el) !== -1) {
// ...
}
indexOf
方法有两个缺点,一是不够语义化,它的含义是找到参数值的第一个出现位置,所以要去比较是否不等于-1
,表达起来不够直观。二是,它内部使用严格相等运算符(===
)进行判断,这会导致对NaN
的误判。
[NaN].indexOf(NaN)
// -1
includes
使用的是不一样的判断算法,就没有这个问题。
[NaN].includes(NaN)
// true
下面代码用来检查当前环境是否支持该方法,如果不支持,部署一个简易的替代版本。
const contains = (() =>
Array.prototype.includes
? (arr, value) => arr.includes(value)
: (arr, value) => arr.some(el => el === value)
)();
contains(['foo', 'bar'], 'baz'); // => false
另外,Map 和 Set 数据结构有一个has
方法,需要注意与includes
区分。
has
方法,是用来查找键名的,比如Map.prototype.has(key)
、WeakMap.prototype.has(key)
、Reflect.has(target, propertyKey)
。has
方法,是用来查找值的,比如Set.prototype.has(value)
、WeakSet.prototype.has(value)
。数组的空位指,数组的某一个位置没有任何值。比如,Array
构造函数返回的数组都是空位。
Array(3) // [, , ,]
上面代码中,Array(3)
返回一个具有3个空位的数组。
注意,空位不是undefined
,一个位置的值等于undefined
,依然是有值的。空位是没有任何值,in
运算符可以说明这一点。
0 in [undefined, undefined, undefined] // true
0 in [, , ,] // false
上面代码说明,第一个数组的0号位置是有值的,第二个数组的0号位置没有值。
ES5 对空位的处理,已经很不一致了,大多数情况下会忽略空位。
forEach()
, filter()
, every()
和some()
都会跳过空位。map()
会跳过空位,但会保留这个值join()
和toString()
会将空位视为undefined
,而undefined
和null
会被处理成空字符串。// forEach方法
[,'a'].forEach((x,i) => console.log(i)); // 1
// filter方法
['a',,'b'].filter(x => true) // ['a','b']
// every方法
[,'a'].every(x => x==='a') // true
// some方法
[,'a'].some(x => x !== 'a') // false
// map方法
[,'a'].map(x => 1) // [,1]
// join方法
[,'a',undefined,null].join('#') // "#a##"
// toString方法
[,'a',undefined,null].toString() // ",a,,"
ES6 则是明确将空位转为undefined
。
Array.from
方法会将数组的空位,转为undefined
,也就是说,这个方法不会忽略空位。
Array.from(['a',,'b'])
// [ "a", undefined, "b" ]
扩展运算符(...
)也会将空位转为undefined
。
[...['a',,'b']]
// [ "a", undefined, "b" ]
copyWithin()
会连空位一起拷贝。
[,'a','b',,].copyWithin(2,0) // [,"a",,"a"]
fill()
会将空位视为正常的数组位置。
new Array(3).fill('a') // ["a","a","a"]
for...of
循环也会遍历空位。
let arr = [, ,];
for (let i of arr) {
console.log(1);
}
// 1
// 1
上面代码中,数组arr
有两个空位,for...of
并没有忽略它们。如果改成map
方法遍历,空位是会跳过的。
entries()
、keys()
、values()
、find()
和findIndex()
会将空位处理成undefined
。
// entries()
[...[,'a'].entries()] // [[0,undefined], [1,"a"]]
// keys()
[...[,'a'].keys()] // [0,1]
// values()
[...[,'a'].values()] // [undefined,"a"]
// find()
[,'a'].find(x => true) // undefined
// findIndex()
[,'a'].findIndex(x => true) // 0
由于空位的处理规则非常不统一,所以建议避免出现空位。
JavaScript 存在四种循环语句:while
、 do/while
、 for
、 for/in
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.