方法 |
描述 |
---|---|
toLowerCase() |
把字符串转化为小写 |
toUpperCase() |
把字符串转化为大写 |
indexOf() |
从原串当中找指定的子串,求出子串下标位置 |
lastIndexOf() |
反向查找字符串所在位置 |
split() |
以指定字符为间隔(切割点)将字符串转化为数组 |
slice() |
从原串当中截取的字符串,形成新串 |
substr() |
从原串当中截取的字符串,形成新串 |
substring() |
从原串当中截取的字符串,形成新串 |
concat() |
把原串和指定的字符串拼接到一起 |
localeCompare() |
比较原串和指定的字符串大小,转成Unicode码比较 |
toString() |
将其他类型转换成字符串 |
charAt() |
找到对应索引位置的字符 |
includes(str) |
判断是否包含指定的字符串 |
startsWith(str) |
判断是否以指定字符串开头 |
endsWith(str) |
判断是否以指定字符串结尾 |
repeat(count) |
重复指定次数 |
charCodeAt() |
找到对应索引位置的字符的Unicode码 |
fromCharCode() |
将指定Unicode码转换成字符 |
trim() |
移除字符串首尾空白 |
(1)
toLowerCase() 全小写
功能: 把字符串转化为小写
参数:无
返回值:返回转化后的字符串,也是新串console.log(str.toLowerCase());
(2)
toUpperCase() 全大写
功能:** 把字符串转化为大写**
参数:无
返回值:返回转化后的字符串,也是新串console.log(str.toUpperCase());
(3)
indexOf() 查找字符或字符串的位置
注意:找到的位置有可能是0,所以写if判断的时候要小心
功能:从原串当中找指定的子串,求出子串下标位置
参数:
- 1、指定的一个子串
- 2、起始位置从哪开始查找 默认是从左边第一个开始往右
- 3、找到第一个就停止查找
返回值:返回对应的子串下标,如果没有找到返回-1
(4)
lastIndexOf()
反向查找字符串所在位置,并从起始位置(0)开始计算返回字符串最后出现的位置,找不到返回-1console.log(str.lastIndexOf('o'));//8,从右到左,第一个o的位置
(5)
split() 以指定字符切割字符串为数组
功能:以指定字符为间隔(切割点)将字符串转化为数组
参数:
- 1、可以不写,那么整个字符串作为数组的一个元素
- 2、可以是空串,那么每个单独的字符都会成为数组的一个元素
- 3、可以是指定的字符串,会以这个字符串为切割点去切割
- 4、如果没有这个切割点,和不传一样
返回值:返回生成的数组
var res = str.split('#');
(6)
slice() 字符串截取
功能:从原串当中截取的字符串,形成新串
参数:
- 指定起始位置和结束位置,位置可以是负数
- 包含起始位置的但是不包含结束位置的
- 参数也可以只写一个,代表起始位置,结束位置不写代表一直截到末尾
- 前大后小,返回空字符串
返回值:返回截取的字符串
console.log(str.slice(2)); //从下标2开始,一直截取到末尾
console.log(str.slice(2, 4)); //'lu'
console.log(str.slice(4, 2)); //''
console.log(str.slice(-1)); //只要倒数第一个
//只要后面4位
console.log(str.slice(-4));
//只要前面3位
console.log(str.slice(0, 3));
(7)
substr() 也是字符串截取
功能:从原串当中截取的字符串,形成新串
参数:
- 1、指定起始位置和截取的长度,位置可以是负数,
- 2、如果只写一个起始位置,默认截取到末尾
- 3、长度<=0,返回空字符串
返回值:返回截取的字符串
console.log(str.substr(2));
console.log(str.substr(2, 4));//从2开始,截取4位
console.log(str.substr(2, -4));//''
//只要前面3位?
console.log(str.substr(0, 3));
//只要后面4位?
console.log(str.slice(-4));
(7)
substr() 也是字符串截取
功能:从原串当中截取的字符串,形成新串
参数:
指定起始位置和截取的长度,位置可以是负数,
如果只写一个起始位置,默认截取到末尾
长度<=0,返回空字符串
返回值:返回截取的字符串
console.log(str.substr(2));
console.log(str.substr(2, 4));//从2开始,截取4位
console.log(str.substr(2, -4));//''
//只要前面3位?
console.log(str.substr(0, 3));
//只要后面4位?
console.log(str.slice(-4));
(8)
substring() 也是字符串截取
功能:从原串当中截取的字符串,形成新串
参数:
- 1、指定两个位置,两个位置不能是负数,
- 2、会自动的根据位置大小决定起始和结束位置
- 3、不包含结束位置的那个字符
在这里插入代码片
返回值:返回截取的字符串
console.log(str.substring(2)); console.log(str.substring(4, 2)); //从2开始,截取到4
slice()/substr()/substring()的区别总结:
1. 都是截取字符串,substr()传位置和长度,其他2个传的都是位置(左开右闭);
2. 只传1个正数的时候,没有区别,都是从指定位置截取到末尾
3. 只传1个负数的时候,substring()返回整个字符串(不支持负数)
4. 传2个数且前大后小的时候:
slice()返回空字符串
substr()的第2个参数是‘截取长度’,<=0时返回空字符串
substring()会自动调换成前小后大
(9)
concat() 字符串拼接,可以直接用+号代替
功能:把原串和指定的字符串拼接到一起
参数:指定一个新的字符串
返回值:返回拼接好的字符串console.log(str.concat(' world')); console.log(str + ' world');
(10)
localeCompare() 字符串比较,可以直接用比较运算符代替
功能:比较原串和指定的字符串大小,转成Unicode码比较
参数:指定一个字符串
返回值:如果原串大返回1 如果原串小返回-1 如果相等返回0
var str = 'b';
console.log(str.localeCompare('a')); //1
console.log(str.localeCompare('b')); //0
console.log(str.localeCompare('c')); //-1
(11)
toString() 一般是将其他类型转换成字符串
功能:把一个对象转化为字符串
参数:无
返回值:就是自己,是个新串
(12)
charAt() 根据下标获取字符内容
功能:找到对应索引位置的字符
参数:一个索引值
//console.log(str.charAt(0)); //早期都使用这个方法
console.log(str[0]); //低级浏览器不支持[]形式
(13)
charCodeAt() 返回指定字符的编码
功能:找到对应索引位置的字符的Unicode码
参数:一个索引值
返回值:返回找到的对应字符的Unicode码
console.log(str.charCodeAt(0));
console.log(str.charCodeAt(1));
var str = '李泽龙';
console.log(str.charCodeAt(0));
console.log(str.charCodeAt(1));
(14)
fromCharCode() 将指定Unicode码转换成字符
这个方法需要通过 String 来调用
console.log(String.fromCharCode(26446));
Es6新增
1.
includes(str) : 判断是否包含指定的字符串
var str = ‘ajshdhak123eiuf’;
console.log(str.includes(‘1235’));
2.startsWith(str) : 判断是否以指定字符串开头
console.log(str.startsWith(‘aa’));
3.endsWith(str) : 判断是否以指定字符串结尾
console.log(str.endsWith(‘ff’));
4.repeat(count) : 重复指定次数
console.log(str.repeat(2));
'字符串'
"字符串"
`字符串`
const name1 = 'zjr';
const name2 = `zjr`;
console.log(name1, name2, name1 === name2);
// zjr zjr true
const person = {
name: 'zjr',
age: 18,
sex: '男'
};
const info =
'我的名字是:' + person.name +
',性别是:' + person.sex +
',今年:' + person.age + '岁';
console.log(info);
// 我的名字是:zjr,性别是:男,今年:18岁
const person = {
name: `zjr`,
age: 18,
sex: `男`
};
const info = `我的名字是:${person.name},性别是:${person.sex},今年:${person.age}岁`;
console.log(info);
// 我的名字是:zjr,性别是:male,今年:18岁
模板字符串最大的优势:方便注入!
// 一般字符串
const info = '第一行\n第二行';
console.log(info);
/*
第一行
第二行
*/
// 模板字符串
const info = `第一行
第二行`; // 注意不能有缩进
console.log(info);
/*
第一行
第二行
*/
模板字符串中,所有的空格、换行或缩进都会被保存在输出中
和
` 等特殊字符const info = `\``; // ```
const info = `\\`; // `\`
const info = `""`; // `""`
const info = `''`; // `''`
const username = 'alex';
const person = {
age: 18,
sex: `male`
};
const getSex = function (sex) {
return sex === `male` ? '男' : '女';
};
const info = `${username},${person.age + 2},${getSex(person.sex)}`;
console.log(info);
// alex,20,男
模板字符串的
${}
注入可以兼容几乎所有的值!模板字符串、字符串、数值、布尔值、表达式、函数……(只要结果是个 “值” 即可)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>模板字符串的应用</title>
<style>
body {
padding: 50px 0 0 300px;
font-size: 22px;
}
ul {
padding: 0;
}
p {
margin-bottom: 10px;
}
</style>
</head>
<body>
<p>学生信息表</p>
<ul id="list">
<li style="list-style: none;">信息加载中……</li>
</ul>
<script>
// 数据(此处只是模拟数据,后期是通过 Ajax 从后台获取)
const students = [
{
username: 'Alex',
age: 18,
sex: 'male'
},
{
username: 'ZhangSan',
age: 28,
sex: 'male'
},
{
username: 'LiSi',
age: 20,
sex: 'female'
}
];
const list = document.getElementById('list');
let html = '';
for (let i = 0; i < students.length; i++) {
html += `我的名字是: ${students[i].username},${students[i].sex},${students[i].age}`;
}
list.innerHTML = html;
</script>
</body>
</html>
传统上,JavaScript 只有indexOf
方法,可以用来确定一个字符串是否包含在另一个字符串中。ES6 又提供了三种新方法。
let s = 'Hello world!';
s.startsWith('Hello') // true
s.endsWith('!') // true
s.includes('o') // true
这三个方法都支持第二个参数,表示开始搜索的位置。
let s = 'Hello world!';
s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
s.includes('Hello', 6) // false
上面代码表示,使用第二个参数n
时,endsWith
的行为与其他两个方法有所不同。它针对前n
个字符,而其他两个方法针对从第n
个位置直到字符串结束。
repeat
方法返回一个新字符串,表示将原字符串重复n
次。
'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""
参数如果是小数,会被取整。
'na'.repeat(2.9) // "nana"
如果repeat
的参数是负数或者Infinity
,会报错。
'na'.repeat(Infinity)
// RangeError
'na'.repeat(-1)
// RangeError
但是,如果参数是 0 到-1 之间的小数,则等同于 0,这是因为会先进行取整运算。0 到-1 之间的小数,取整以后等于-0
,repeat
视同为 0。
'na'.repeat(-0.9) // ""
参数NaN
等同于 0。
'na'.repeat(NaN) // ""
如果repeat
的参数是字符串,则会先转换成数字。
'na'.repeat('na') // ""
'na'.repeat('3') // "nanana"
ES2017 引入了字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。padStart()
用于头部补全,padEnd()
用于尾部补全。
'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax'
'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'
上面代码中,padStart()
和padEnd()
一共接受两个参数,第一个参数是字符串补全生效的最大长度,第二个参数是用来补全的字符串。
如果原字符串的长度,等于或大于最大长度,则字符串补全不生效,返回原字符串。
'xxx'.padStart(2, 'ab') // 'xxx'
'xxx'.padEnd(2, 'ab') // 'xxx'
如果用来补全的字符串与原字符串,两者的长度之和超过了最大长度,则会截去超出位数的补全字符串。
'abc'.padStart(10, '0123456789')
// '0123456abc'
如果省略第二个参数,默认使用空格补全长度。
'x'.padStart(4) // ' x'
'x'.padEnd(4) // 'x '
padStart()
的常见用途是为数值补全指定位数。下面代码生成 10 位的数值字符串。
'1'.padStart(10, '0') // "0000000001"
'12'.padStart(10, '0') // "0000000012"
'123456'.padStart(10, '0') // "0000123456"
另一个用途是提示字符串格式。
'12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12"
'09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"
trimStart()
和trimEnd()
这两个方法,它们的行为与trim()
一致,trimStart()
消除字符串头部的空格,trimEnd()
消除尾部的空格。它们返回的都是新字符串,不会修改原始字符串。
const s = ' abc ';
s.trim() // "abc"
s.trimStart() // "abc "
s.trimEnd() // " abc"
上面代码中,trimStart()
只消除头部的空格,保留尾部的空格。trimEnd()
也是类似行为。
除了空格键,这两个方法对字符串头部(或尾部)的 tab 键、换行符等不可见的空白符号也有效。
浏览器还部署了额外的两个方法,trimLeft()
是trimStart()
的别名,trimRight()
是trimEnd()
的别名。
at()
方法接受一个整数作为参数,返回参数指定位置的字符,支持负索引(即倒数的位置)。
const str = 'hello';
str.at(1) // "e"
str.at(-1) // "o"
如果参数位置超出了字符串范围,at()
返回undefined
。
放法 | 描述 |
---|---|
push() |
从末尾增 |
pop() |
从末尾去删 |
unshift() |
从头部增 |
shift() |
从头部去删 |
splice() |
增删改一体化 |
reverse() |
数组翻转 |
sort() |
数组进行排序 |
join() |
把数组再转化为字符串 |
concat() |
在数组的末尾拼接元素 |
slice() |
在数组当中截取部分元素形成新数组 |
fill() |
填充数组,使用指定的内容填充数组 |
valueOf() |
是Object的原型当中的,返回数组实例对象。 |
toString() |
将数组对象转化为字符串 |
es5新增 | 方法 |
---|---|
forEach() |
遍历数组 |
filter() |
过滤数组,根据过滤条件返回一个新的数组 |
map() |
遍历数组,返回一个新的数组 |
some() |
判断数组里面是否有满足条件的,返回的是一个布尔值 |
every() |
判断数组里面是否所有元素都满足条件 |
reduce() |
遍历数组,reduce的每次遍历,都可以拿到上一次遍历的结果 |
1、
push()
从末尾增
功能:从数组的末尾增加一个或者多个
参数:增加的元素
返回值:返回加入元素后数组的新长度
这个方法对原数组有影响
var result = arr.push(100,200,300,[1,2,3]);
console.log(result);
console.log(arr);
2、
pop()
从末尾去删
功能:从数组的末尾删除一个
参数:无
返回值:返回删除的那一个元素
这个方法对原数组有影响
var result = arr.;
console.log(result);
console.log(arr);
3、
unshift()
从头部增
功能:从数组的头部增加一个或者多个
参数:增加的元素
返回值:返回加入元素后数组的新长度
这个方法对原数组有影响
var result = arr.unshift(100,200,300,[1,2,3]);
console.log(result);
console.log(arr);
4、
shift()
从头部去删
功能:从数组的头部删除一个
参数:无
返回值:返回删除的那一个元素
这个方法对原数组有影响
var result = arr.;
console.log(result);
console.log(arr);
5、
splice()
增删改一体化
这个方法根据参数的不同,有不同的功能
这个方法我们大家先当作删除去理解
功能:从数组的任意位置删除任意个元素
参数:两个 第一个代表从哪开始,第二个代表删除几个
返回值:返回删除的元素组成的新数组
这个方法对原数组有影响
//删除
var result = arr.splice(1,2);
console.log(result);
console.log(arr);
//增加
var result = arr.splice(2,0,400,500,600);
console.log(result);
console.log(arr);
//功能:从数组的任意位置添加任意多个元素
//参数:多个 第一个代表从哪开始,第二个代表删除0个,后面的代表新增的元素
//返回值:返回删除的元素组成的新数组,最终是空数组
//这个方法对原数组有影响
//修改
var result = arr.splice(1,1,1000,2000);
console.log(result);
console.log(arr);
//功能:修改数组当中任意的元素
//参数:多个 第一个代表从哪开始,第二个代表删除几个,后面的代表新增的元素
//返回值:返回删除的元素组成的新数组
//这个方法对原数组有影响
6、
reverse()
翻转数组
功能:翻转数组
参数:无
返回值:返回翻转后的原数组
这个方法影响原数组
var result = arr.reverse();
console.log(result);
console.log(arr);
console.log(result == arr);
7、
sort()
数组进行排序
功能:对数组进行排序
参数:
1、如果不写,默认把每个元素转化为字符串进行排序(按照Unicode码)
2、如果要按照升序降序去排,需要传一个参数是函数
3、这个函数有两个形参
返回值:排好序的原数组
这个方法对原数组有影响
var result = arr.sort(function(a,b){
return b - a;
});
console.log(result);
console.log(arr);
console.log(result === arr);
8、
join()
把数组再转化为字符串 和字符串方法 split是一个逆运算
功能:以指定的字符串为连接符,将数组元素连接成一个字符串
参数:
1、如果不传,默认以逗号为连接符,将元素连接成字符串
2、如果传的是正常字符串,那么以这个字符串为连接符将元素连接成字符串
3、如果传的是空串,那么直接将字符串连接成字符串,中间没有任何连接
返回值:返回连接好的字符串
这个方法对原数组没有影响
var result = arr.join('');
console.log(result);
console.log(arr);
9、
concat()
在数组的末尾拼接元素
功能:在数组的末尾拼接元素
参数:
1、可以不写,相当于复制一个数组
2、也可以写一个或者多个值,会把这些值拼接到数组末尾
3、也可以写数组,会先把数组拆开,把元素拼接到数组末尾
返回值:返回拼接好的新数组
这个方法对原数组没有影响
var result = arr.concat([100,200,300]);
console.log(result);
console.log(arr);
console.log(arr === result);
10、
slice()
在数组当中截取部分元素形成新数组
功能:在数组当中截取部分元素形成新数组
参数:
1、和字符串方法slice一致;起始位置和结束位置,不包含结束位置的元素
2、如果只传一个代表起始位置,一直到结束。两个位置都可以使用负数
返回值:返回截取的元素形成的新数组
这个方法对原数组没有影响
var result = arr.slice(1,3);
console.log(result);
console.log(arr);
11、
fill()
填充数组,使用指定的内容填充数组
arr.length = 100;
arr.fill(1); //100个1
console.log(arr);
12、
valueOf()
是Object的原型当中的,返回数组实例对象。
功能:获取数组对象的基本值,数组实例对象。
参数:无
返回值:返回原数组,因为数组是非包装对象,所以它是没有基本值
var result = arr.valueOf()
console.log(result);
console.log(arr);
console.log(result === arr);
var o = new String('111');
console.log(typeof o);
var s = o.valueOf();
console.log(typeof s);
13、
toString()
将数组对象转化为字符串
功能:将数组对象转化为字符串
参数:无
返回值:把数组的中括号去掉,其余加引号形成字符串返回
var result = arr.toString()
console.log(result);
console.log(arr);
14、
valueOf()
对象/数组在参与数学运算的时候会发生隐式转换,会:
- 调用 valueOf() 看得到的值是不是基本数据类型,如果是,就使用
- 如果 valueOf() 得到的不是基本数据类型,则调用toString()方法,看是不是基本数据类型
- 如果前面2不都得不到基本数据类型,则报错!
console.log(Number(arr));
console.log(arr.valueOf());
console.log(arr.toString());
console.log(arr + 1); //先valueOf()得到数组,再toString()得到'1,2,3,4'
console.log(arr - 1); //NaN
console.log(arr * 1); //NaN
console.log(arr / 1); //NaN
1、
forEach()
遍历数组,需要传递一个function
function有3个参数
第1个是数组里面的每个元素
第2个是元素的下标
第3个是当前操作的数组
可以只写1个,位置是固定的
没有返回值,默认是undefined
var res = arr.forEach(function(item, index, arr) {
console.log(item);
});
console.log(res);
2、
map()
遍历数组,需要传递一个function
将每个数字乘以5,并返回一个新的数组
var arr = [1,2,3,4,5];
map()遍历数组,需要传递一个function
function有3个参数
第1个是数组里面的每个元素
第2个是元素的下标
第3个是当前操作的数组
可以只写1个,位置是固定的
可以将function里面return的东西组成一个新的数组作为返回值
var newArr = arr.map(function(item, index, arr) {
return item * 5;
});
//箭头函数,ES6阶段再学
// newArr = arr.map(item => item*5);
console.log(newArr);
3、
filter() 过滤数组,需要传递一个function
将薪资1w以下的去除,返回一个新的数组
var arr = [12000, 8000, 15000, 18000, 13800];function有3个参数
第1个是数组里面的每个元素
第2个是元素的下标
第3个是当前操作的数组
可以只写1个,位置是固定的
可以将function里面return true;的东西组成一个新的数组作为返回值
newArr = arr.filter(function(item, index, arr) {
console.log(item);
// if (item >= 10000) return true;
return item >= 10000;
});
console.log(newArr);
4、
some() 判断数组里面是否有满足条件的
判断数组里面有没有薪资刚好1w的
var arr = [12000, 80000, 15000, 18000, 13800];
需要传递一个function
function有3个参数
第1个是数组里面的每个元素
第2个是元素的下标
第3个是当前操作的数组
可以只写1个,位置是固定的
返回的是一个布尔值,不写return,默认得到false
挨个遍历数组中的值,判断其中是否有满足条件的
在function里面return true后就停止遍历(break)
使用场景:通常用来检测数组里面是否有某个值
flag = arr.some(function(item, index, arr) {
// console.log(item);
// return true; //一旦返回true,后面的就不用遍历了
return item === 10000;
});
console.log(flag);
5、
every() 判断数组里面是否所有元素都满足条件
判断数组里面是不是所有人的年龄都大于等于18
every() 判断数组里面是否所有元素都满足条件,需要传递一个function
function有3个参数
第1个是数组里面的每个元素
第2个是元素的下标
第3个是当前操作的数组
可以只写1个,位置是固定的
返回的是一个布尔值,不写return,默认得到false
挨个遍历数组中的值,判断其中所有的元素是否满足条件
在function里面return false后就停止遍历(break)
flag = arr.every(function(item, index, arr) {
return item >= 18;
});
console.log(flag);
6、
reduce()方法
,也会对数组进行遍历,与之前的forEach等不同的是:
reduce的每次遍历,都可以拿到上一次遍历的结果
var arr = [1,2,3,4,5];
/*
prev item
1 2 3
3 3 6
6 4 10
10 5 15
*/
//求和:没有为prev设置初始值,默认会将第1个元素的值给prev,所以循环是从第2个元素开始
var res = arr.reduce(function(prev, item, index, arr) {
// console.log(prev, item);
return prev + item;
});
console.log(res);
console.log('-------------');
/*
prev item
0 1 1
1 2 3
3 3 6
6 4 10
10 5 15
*/
var res = arr.reduce(function(prev, item, index, arr) {
console.log(prev, item);
return prev + item;
}, 0); //给prev设置了一个初始值0
console.log(res);
扩展运算符(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')]
// [, , ]
该运算符主要用于函数调用。
function push(array, ...items) {
array.push(...items);
}
function add(x, y) {
return x + y;
}
const numbers = [4, 38];
add(...numbers) // 42
上面代码中,array.push(...items)
和add(...numbers)
这两行,都是函数的调用,它们都使用了扩展运算符。该运算符将一个数组,变为参数序列。
扩展运算符与正常的函数参数可以结合使用,非常灵活。
function f(v, w, x, y, z) { }
const args = [0, 1];
f(-1, ...args, 2, ...[3]);
扩展运算符后面还可以放置表达式。
const arr = [
...(x > 0 ? ['a'] : []),
'b',
];
如果扩展运算符后面是一个空数组,则不产生任何效果。
[...[], 1]
// [1]
注意,只有函数调用时,扩展运算符才可以放在圆括号中,否则会报错。
(...[1, 2])
// Uncaught SyntaxError: Unexpected number
console.log((...[1, 2]))
// Uncaught SyntaxError: Unexpected number
console.log(...[1, 2])
// 1 2
上面三种情况,扩展运算符都放在圆括号里面,但是前两种情况会报错,因为扩展运算符所在的括号不是函数调用。
1.2 替代函数的 apply() 方法
由于扩展运算符可以展开数组,所以不再需要apply()
方法将数组转为函数的参数了。
// ES5 的写法
function f(x, y, z) {
// ...
}
var args = [0, 1, 2];
f.apply(null, args);
// ES6 的写法
function f(x, y, z) {
// ...
}
let 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 的写法
let arr1 = [0, 1, 2];
let 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.3 扩展运算符的应用
(1)复制数组
数组是复合的数据类型,直接复制的话,只是复制了指向底层数据结构的指针,而不是克隆一个全新的数组。
const a1 = [1, 2];
const a2 = a1;
a2[0] = 2;
a1 // [2, 2]
上面代码中,a2
并不是a1
的克隆,而是指向同一份数据的另一个指针。修改a2
,会直接导致a1
的变化。
ES5 只能用变通方法来复制数组。
const a1 = [1, 2];
const a2 = a1.concat();
a2[0] = 2;
a1 // [1, 2]
上面代码中,a1
会返回原数组的克隆,再修改a2
就不会对a1
产生影响。
扩展运算符提供了复制数组的简便写法。
const a1 = [1, 2];
// 写法
const a2 = [...a1];
上面的两种写法,a2
都是a1
的克隆。
(2)合并数组
扩展运算符提供了数组合并的新写法。
const arr1 = ['a', 'b'];
const arr2 = ['c'];
const arr3 = ['d', 'e'];
// ES5 的合并数组
arr1.concat(arr2, arr3);
// [ 'a', 'b', 'c', 'd', 'e' ]
// ES6 的合并数组
[...arr1, ...arr2, ...arr3]
// [ 'a', 'b', 'c', 'd', 'e' ]
不过,这两种方法都是浅拷贝,使用的时候需要注意。
const a1 = [{ foo: 1 }];
const a2 = [{ bar: 2 }];
const a3 = a1.concat(a2);
const a4 = [...a1, ...a2];
a3[0] === a1[0] // true
a4[0] === a1[0] // true
上面代码中,a3
和a4
是用两种不同方法合并而成的新数组,但是它们的成员都是对原数组成员的引用,这就是浅拷贝。如果修改了引用指向的值,会同步反映到新数组。
(4)字符串转为数组
扩展运算符还可以将字符串转为真正的数组。
console.log(...'alex'); // a l e x
console.log('a', 'l', 'e', 'x'); // a l e x
console.log([...'alex']); // [ 'a', 'l', 'e', 'x' ]
// ES6 之前字符串转数组是通过:'alex'.split('');
(5)类数组转为数组
// arguments
function func() {
console.log(arguments); // [Arguments] { '0': 1, '1': 2 }
console.log([...arguments]); // [ 1, 2 ]
}
func(1, 2);
// NodeList
console.log([...document.querySelectorAll('p')].push);
2.Array.from()
2.1 前言
在前端开发中经常会遇到类数组,但是我们不能直接使用数组的方法,需要先把类数组转化为数组。本节介绍 ES6 数组的新增方法 Array.from()
,该方法用于将类数组对象(array-like)和可遍历的对象(iterable)转换为真正的数组进行使用。
2.2 方法详情
2.2.1 基本语法
Array.from()
方法会接收一个类数组对象然后返回一个真正的数组实例,返回的数组可以调用数组的所有方法。
语法使用:
Array.from(arrayLike[, mapFn[, thisArg]])
参数解释:
参数
描述
arrayLike
想要转换成数组的类数组对象或可迭代对象
mapFn
如果指定了该参数,新数组中的每个元素会执行该回调函数
thisArg
可选参数,执行回调函数 mapFn 时 this 对象
2.2 类数组转化
所谓类数组对象,就是指可以通过索引属性访问元素,并且对象拥有 length 属性,类数组对象一般是以下这样的结构:
var arrLike = {
'0': 'apple',
'1': 'banana',
'2': 'orange',
length: 3
};
在 ES5 中没有对应的方法将类数组转化为数组,但是可以借助 call 和 apply 来实现:
var arr = [].slice.call(arrLike);
// 或
var arr = [].slice.apply(arrLike);
有了 ES6 的 Array.from()
就更简单了,对类数组对象直接操作,即可得到数组。
var arr = Array.from(arrLike);
console.log(arr) // ['apple', 'banana', 'orange']
2.2.3 第二个参数 —— 回调函数
在 Array.from
中第二个参数是一个类似 map 函数的回调函数,该回调函数会依次接收数组中的每一项作为传入的参数,然后对传入值进行处理,最得到一个新的数组。
Array.from(obj, mapFn, thisArg)
也可以用 map 改写成这样 Array.from(obj).map(mapFn, thisArg)
。
var arr = Array.from([1, 2, 3], function (x) {
return 2 * x;
});
var arr = Array.from([1, 2, 3]).map(function (x) {
return 2 * x;
});
//arr: [2, 4, 6]
上面的例子展示了,Array.from
的参数可以使用 map
方法来进行替换,它们是等价的操作。
2.2.4 第三个参数 ——this
Array.from
中第三个参数可以对回调函数中 this 的指向进行绑定,该参数是非常有用的,我们可以将被处理的数据和处理对象分离,将各种不同的处理数据的方法封装到不同的的对象中去,处理方法采用相同的名字。
在调用 Array.from 对数据对象进行转换时,可以将不同的处理对象按实际情况进行注入,以得到不同的结果,适合解耦。
let obj = {
handle: function(n){
return n + 2
}
}
Array.from([1, 2, 3, 4, 5], function (x){
return this.handle(x)
}, obj)
// [3, 4, 5, 6, 7]
定义一个 obj
对象可以认作是,Array.from
回调函数中处理数据的方法集合,handle
是其中的一个方法,把 obj
作为第三个参数传给 Array.from
这样在回调函数中可以通过 this
来拿到 obj
对象。
2.2.5 从字符串里生成数组
Array.from()
在传入字符串时,会把字符串的每一项都拆成单个的字符串作为数组中的一项。
Array.from('imooc');
// [ "i", "m", "o", "o", "c" ]
2.2.6 从 Set 中生成数组
用 Set
定义的数组对象,可以使用 Array.from()
得到一个正常的数组。
const set = new Set(['a', 'b', 'c', 'd']);
Array.from(set);
// [ "a", "b", "c", "d" ]
上面的代码中创建了一个 Set 数据结构,把实例传入 Array.from()
可以得到一个真正的数组。
2.2.7 从 Map 中生成数组
Map
对象保存的是一个个键值对,Map
中的参数是一个数组或是一个可迭代的对象。 Array.from()
可以把 Map 实例转换为一个二维数组。
const map = new Map([[1, 2], [2, 4], [4, 8]]);
Array.from(map); // [[1, 2], [2, 4], [4, 8]]
2.3 使用案例
2.3.1 创建一个包含从 0 到 99 (n) 的连续整数的数组
- 一般情况下我们可以使用 for 循环来实现。
var arr = [];
for(var i = 0; i <= 99; i++) {
arr.push(i);
}
这种方法的主要优点是最直观了,性能也最好的,但是很多时候我们不想使用 for 循环来进行操作。
- 使用 Array 配合 map 来实现。
var arr = Array(100).join(' ').split('').map(function(item,index){return index});
Array (100) 创建了一个包含 100 个空位的数组,但是这样创建出来的数组是没法进行迭代的。所以要通过字符串转换,覆盖 undefined,最后调用 map 修改元素值。
- 使用 es6 的
Array.from
实现。
var arr = Array.from({length:100}).map(function(item,index){return index});
Array.from({length:100})
可以定义一个可迭代的数组,数组的每一项都是 undefined,这样就非常方便的定义出所需要的数组了,但是这样定义的数组性能最差,具体可以参考 constArray 的测试结果。
2.3.2 数组去重合并
function combine(){
let arr = [].concat.apply([], arguments); //没有去重复的新数组
return Array.from(new Set(arr));
}
var m = [1, 2, 2], n = [2,3,3];
console.log(combine(m,n)); // [1, 2, 3]
首先定义一个去重数组函数,通过 concat 把传入的数组进行合并到一个新的数组中去,通过 new Set () 可以对 arr 进行去重操作,再使用 Array.from()
返回一个拷贝后的数组。
2.4 小结
本节讲解了字符串的 Array.from()
方法的使用,用于将类数组对象和可迭代的对象转化真正的数组,在编程中主要用于更加方便的初始化一个有默认值的数组,还可以用于将获取的 html 的 DOM 对象转化为数组,可以使用数组方法进行操作。
3.Array.of()
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);
}
4.find(),findIndex(),findLast(),findLastIndex()
数组实例的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
对象。
function f(v){
return v > this.age;
}
let person = {name: 'John', age: 20};
[10, 12, 26, 15].find(f, person); // 26
上面的代码中,find()
函数接收了第二个参数person
对象,回调函数中的this
对象指向person
对象。
另外,这两个方法都可以发现NaN
,弥补了数组的indexOf()
方法的不足。
[NaN].indexOf(NaN)
// -1
[NaN].findIndex(y => Object.is(NaN, y))
// 0
上面代码中,indexOf()
方法无法识别数组的NaN
成员,但是findIndex()
方法可以借助Object.is()
方法做到。
find()
和findIndex()
都是从数组的0号位,依次向后检查。ES2022 新增了两个方法findLast()
和findLastIndex()
,从数组的最后一个成员开始,依次向前检查,其他都保持不变。
const array = [
{ value: 1 },
{ value: 2 },
{ value: 3 },
{ value: 4 }
];
array.findLast(n => n.value % 2 === 1); // { value: 3 }
array.findLastIndex(n => n.value % 2 === 1); // 2
上面示例中,findLast()
和findLastIndex()
从数组结尾开始,寻找第一个value
属性为奇数的成员。结果,该成员是{ value: 3 }
,位置是2号位。
5.filter()
filter()
方法用于过滤数组成员,满足条件的成员组成一个新数组返回。
它的参数是一个函数,所有数组成员依次执行该函数,返回结果为true
的成员组成一个新数组返回。该方法不会改变原数组。
[1, 2, 3, 4, 5].filter(function (elem) {
return (elem > 3);
})
// [4, 5]
上面代码将大于3
的数组成员,作为一个新数组返回。
var arr = [0, 1, 'a', false];
arr.filter(Boolean)
// [1, "a"]
上面代码中,filter()
方法返回数组arr
里面所有布尔值为true
的成员。
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]
上面代码中,过滤器myFilter()
内部有this
变量,它可以被filter()
方法的第二个参数obj
绑定,返回大于3
的成员。
6.map()
map()
方法将数组的所有成员依次传入参数函数,然后把每一次的执行结果组成一个新数组返回。
var numbers = [1, 2, 3];
numbers.map(function (n) {
return n + 1;
});
// [2, 3, 4]
numbers
// [1, 2, 3]
上面代码中,numbers
数组的所有成员依次执行参数函数,运行结果组成一个新数组返回,原数组没有变化。
map()
方法接受一个函数作为参数。该函数调用时,map()
方法向它传入三个参数:当前成员、当前位置和数组本身。
[1, 2, 3].map(function(elem, index, arr) {
return elem * index;
});
// [0, 2, 6]
上面代码中,map()
方法的回调函数有三个参数,elem
为当前成员的值,index
为当前成员的位置,arr
为原数组([1, 2, 3]
)。
map()
方法还可以接受第二个参数,用来绑定回调函数内部的this
变量(详见《this 变量》一章)。
var arr = ['a', 'b', 'c'];
[1, 2].map(function (e) {
return this[e];
}, arr)
// ['b', 'c']
上面代码通过map()
方法的第二个参数,将回调函数内部的this
对象,指向arr
数组。
如果数组有空位,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"]
上面代码中,map()
方法不会跳过undefined
和null
,但是会跳过空位。
7.reduce()
reduce()
方法依次处理数组的每个成员,最终累计为一个值。它们的差别是,reduce()
是从左到右处理(从第一个成员到最后一个成员)。
语法:arr.reduce(function(累计值, 当前元素){}, 起始值)
[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()
方法用来求出数组所有成员的和。reduce()
的参数是一个函数,数组每个成员都会依次执行这个函数。如果数组有 n 个成员,这个参数函数就会执行 n - 1 次。
- 第一次执行:
a
是数组的第一个成员1
,b
是数组的第二个成员2
。
- 第二次执行:
a
为上一轮的返回值3
,b
为第三个成员3
。
- 第三次执行:
a
为上一轮的返回值6
,b
为第四个成员4
。
- 第四次执行:
a
为上一轮返回值10
,b
为第五个成员5
。至此所有成员遍历完成,整个方法的返回值就是最后一轮的返回值15
。
reduce()
方法的第一个参数都是一个函数。该函数接受以下四个参数。
- 累积变量。第一次执行时,默认为数组的第一个成员;以后每次执行时,都是上一轮的返回值。
- 当前变量。第一次执行时,默认为数组的第二个成员;以后每次执行时,都是下一个成员。
- 当前位置。一个整数,表示第二个参数(当前变量)的位置,默认为
1
。
- 原数组。
这四个参数之中,只有前两个是必须的,后两个则是可选的。
[1, 2, 3, 4, 5].reduce(function (
a, // 累积变量,必须
b, // 当前变量,必须
i, // 当前位置,可选
arr // 原数组,可选
) {
// ... ...
如果要对累积变量指定初值,可以把它放在reduce()
方法的第二个参数。
[1, 2, 3, 4, 5].reduce(function (a, b) {
return a + b;
}, 10);
// 25
上面代码指定参数a
的初值为10,所以数组从10开始累加,最终结果为25。注意,这时b
是从数组的第一个成员开始遍历,参数函数会执行5次。
建议总是加上第二个参数,这样比较符合直觉,每个数组成员都会依次执行reduce()
方法的参数函数。另外,第二个参数可以防止空数组报错。
function add(prev, cur) {
return prev + cur;
}
[].reduce(add)
// TypeError: Reduce of empty array with no initial value
[].reduce(add, 1)
// 1
上面代码中,由于空数组取不到累积变量的初始值,reduce()
方法会报错。这时,加上第二个参数,就能保证总是会返回一个值。
总结
//reduce 返回函数累计处理的结果,经常用于求和等
/*
计值参数:
1. 如果有起始值,则以起始值为准开始累计, 累计值 = 起始值
2. 如果没有起始值, 则累计值以数组的第一个数组元素作为起始值开始累计
3. 后面每次遍历就会用后面的数组元素 累计到 累计值 里面(类似求和里面的 sum )
*/
8.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
上面代码中,如果数组arr
有一个成员大于等于3,some
方法就返回true
。
every
方法是所有成员的返回值都是true
,整个every
方法才返回true
,否则返回false
。
var arr = [1, 2, 3, 4, 5];
arr.every(function (elem, index, arr) {
return elem >= 3;
});
// false
上面代码中,数组arr
并非所有成员大于等于3
,所以返回false
。
注意,对于空数组,some
方法返回false
,every
方法返回true
,回调函数都不会执行。
function isEven(x) { return x % 2 === 0 }
[].some(isEven) // false
[].every(isEven) // true
some
和every
方法还可以接受第二个参数,用来绑定参数函数内部的this
变量。
9.fill()
arr.fill(value[, start[, end]])
方法用一个固定值填充一个数组中从起始索引到终止索引内的全部元素。不包括终止索引。
起始索引,默认值为 0。
终止索引,默认值为 this.length。
['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 号位之前结束。
注意,如果填充的类型为对象,那么被赋值的是同一个内存地址的对象,而不是深拷贝对象。
let arr = new Array(3).fill({name: "Mike"});
arr[0].name = "Ben";
arr
// [{name: "Ben"}, {name: "Ben"}, {name: "Ben"}]
let arr = new Array(3).fill([]);
arr[0].push(5);
arr
// [[5], [5], [5]]
10.at()
长久以来,JavaScript 不支持数组的负索引,如果要引用数组的最后一个成员,不能写成arr[-1]
,只能使用arr[arr.length - 1]
。
这是因为方括号运算符[]
在 JavaScript 语言里面,不仅用于数组,还用于对象。对于对象来说,方括号里面就是键名,比如obj[1]
引用的是键名为字符串1
的键,同理obj[-1]
引用的是键名为字符串-1
的键。由于 JavaScript 的数组是特殊的对象,所以方括号里面的负数无法再有其他语义了,也就是说,不可能添加新语法来支持负索引。
为了解决这个问题,ES2022为数组实例增加了at()
方法,接受一个整数作为参数,返回对应位置的成员,并支持负索引。这个方法不仅可用于数组,也可用于字符串和类型数组(TypedArray)。
const arr = [5, 12, 8, 130, 44];
arr.at(2) // 8
arr.at(-2) // 130
如果参数位置超出了数组范围,at()
返回undefined
。
const sentence = 'This is a sample sentence';
sentence.at(0); // 'T'
sentence.at(-1); // 'e'
sentence.at(-100) // undefined
sentence.at(100) // undefined
11.entries(),keys() 和 values()
ES6 提供三个新的方法——entries()
,keys()
和values()
——用于遍历数组。它们都返回一个遍历器对象,可以用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"
12.includes()
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
另外,Map 和 Set 数据结构有一个has
方法,需要注意与includes
区分。
- Map 结构的
has
方法,是用来查找键名的,比如Map.prototype.has(key)
、WeakMap.prototype.has(key)
、Reflect.has(target, propertyKey)
。
- Set 结构的
has
方法,是用来查找值的,比如Set.prototype.has(value)
、WeakSet.prototype.has(value)
。
13.toReversed(),toSorted(),toSpliced(),with()
很多数组的传统方法会改变原数组,比如push()
、pop()
、shift()
、unshift()
等等。数组只要调用了这些方法,它的值就变了。现在有一个提案,允许对数组进行操作时,不改变原数组,而返回一个原数组的拷贝。
这样的方法一共有四个。
Array.prototype.toReversed() -> Array
Array.prototype.toSorted(compareFn) -> Array
Array.prototype.toSpliced(start, deleteCount, ...items) -> Array
Array.prototype.with(index, value) -> Array
它们分别对应数组的原有方法。
toReversed()
对应reverse()
,用来颠倒数组成员的位置。
toSorted()
对应sort()
,用来对数组成员排序。
toSpliced()
对应splice()
,用来在指定位置,删除指定数量的成员,并插入新成员。
with(index, value)
对应splice(index, 1, value)
,用来将指定位置的成员替换为新的值。
上面是这四个新方法对应的原有方法,含义和用法完全一样,唯一不同的是不会改变原数组,而是返回原数组操作后的拷贝。
下面是示例。
const sequence = [1, 2, 3];
sequence.toReversed() // [3, 2, 1]
sequence // [1, 2, 3]
const outOfOrder = [3, 1, 2];
outOfOrder.toSorted() // [1, 2, 3]
outOfOrder // [3, 1, 2]
const array = [1, 2, 3, 4];
array.toSpliced(1, 2, 5, 6, 7) // [1, 5, 6, 7, 4]
array // [1, 2, 3, 4]
const correctionNeeded = [1, 1, 3];
correctionNeeded.with(1, 2) // [1, 2, 3]
correctionNeeded // [1, 1, 3]
14.isArray()
14.1 前言
在程序中判断数组是很常见的应用,但在 ES5 中没有能严格判断 JS 对象是否为数组,都会存在一定的问题,比较受广大认可的是借助 toString 来进行判断,很显然这样不是很简洁。ES6 提供了 Array.isArray()
方法更加简洁地判断 JS 对象是否为数组。
14.2 方法详情
判断 JS 对象,如果值是 Array
,则为 true; 否则为 false。
语法使用:
Array.isArray(obj)
参数解释:
参数
描述
obj
需要检测的 JS 对象
14.3 ES5 中判断数组的方法
通常使用 typeof
来判断变量的数据类型,但是对数组得到不一样的结果
// 基本类型
typeof 123; //number
typeof "123"; //string
typeof true; //boolean
// 引用类型
typeof [1,2,3]; //object
上面的代码中,对于基本类型的判断没有问题,但是判断数组时,返回了 object 显然不能使用 typeof
来作为判断数组的方法。
14.3.1 通过 instanceof 判断
instanceof
运算符用于检测构造函数的 prototype
属性是否出现在某个实例对象的原型链。
instanceof
可以用来判断数组是否存在,判断方式如下:
var arr = ['a', 'b', 'c'];
console.log(arr instanceof Array); // true
console.log(arr.constructor === Array;); // true
在解释上面的代码时,先看下数组的原型链指向示意图:
数组实例的原型链指向的是 Array.prototype
属性,instanceof
运算符就是用来检测 Array.prototype
属性是否存在于数组的原型链上,上面代码中的 arr 变量就是一个数组,所有拥有 Array.prototype
属性,返回值 true
,这样就很好的判断数组类型了。
但是,需要注意的是,prototype
属性是可以修改的,所以并不是最初判断为 true
就一定永远为真。
14.3.2 通过 constructor 判断
我们知道,Array 是 JavaScript 内置的构造函数,构造函数属性(prototype)的 constructor
指向构造函数(见下图),那么通过 constructor
属性也可以判断是否为一个数组。
var arr = new Array('a', 'b', 'c');
arr.constructor === Array; //true
下面我们通过构造函数的示意图来进行分析:
由上面的示意图可以知道,我们 new 出来的实例对象上的原型对象有 constructor
属性指向构造函数 Array,由此我们可以判断一个数组类型。
但是 constructor
是可以被重写,所以不能确保一定是数组,如下示例:
var str = 'abc';
str.constructor = Array;
str.constructor === Array // true
上面的代码中,str 显然不是数组,但是可以把 constructor
指向 Array 构造函数,这样再去进行判断就是有问题的了。
constructor
和 instanceof
也存在同样问题,不同执行环境下,constructor
的判断也有可能不正确。
14.4 Array.isArray () 的使用
下面我们通过示例来看下 Array.isArray()
是怎样判断数组的。
// 下面的函数调用都返回 true
Array.isArray([]);
Array.isArray([10]);
Array.isArray(new Array());
Array.isArray(new Array('a', 'b', 'c'))
// 鲜为人知的事实:其实 Array.prototype 也是一个数组。
Array.isArray(Array.prototype);
// 下面的函数调用都返回 false
Array.isArray();
Array.isArray({});
Array.isArray(null);
Array.isArray(undefined);
Array.isArray(17);
Array.isArray('Array');
Array.isArray(true);
Array.isArray(false);
Array.isArray(new Uint8Array(32))
Array.isArray({ __proto__: Array.prototype });
上面的代码中对 JavaScript 中的数据类型做验证,可以很好地区分数组类型。
14.5 自定义 isArray
在 ES5 中比较通用的方法是使用 Object.prototype.toString
去判断一个值的类型,也是各大主流库的标准。在不支持 ES6 语法的环境下可以使用下面的方法给 Array
上添加 isArray
方法
if (!Array.isArray){
Array.isArray = function(arg){
return Object.prototype.toString.call(arg) === '[object Array]';
};
}
14.6 小结
本节介绍了判断一个值是数组类型的方法 Array.isArray()
此方法可以很准确地判断数组,学习了在 ES5 中判断数组类型的几个方法的缺陷。在不支持 ES6 的情况下也可以通过 Object.prototype.toString
自定义 Array.isArray()
方法。
对象的新增方法
1.1 Object.is()
ES5 比较两个值是否相等,只有两个运算符:相等运算符(==
)和严格相等运算符(===
)。它们都有缺点,前者会自动转换数据类型,后者的NaN
不等于自身,以及+0
等于-0
。JavaScript 缺乏一种运算,在所有环境中,只要两个值是一样的,它们就应该相等。
ES6 提出“Same-value equality”(同值相等)算法,用来解决这个问题。Object.is
就是部署这个算法的新方法。它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。
Object.is('foo', 'foo')
// true
Object.is({}, {})
// false
不同之处只有两个:一是+0
不等于-0
,二是NaN
等于自身。
+0 === -0 //true
NaN === NaN // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
1.2 Object.assign()
Object.assign()
方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。
const target = { a: 1, b: 1 };
const source1 = { b: 2, c: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
汇总
// 基本用法
// Object.assign(目标对象, 源对象1, 源对象2, ...);
const apple = {
color: '红色',
shape: '圆形',
taste: '甜'
};
const pen = {
color: '黑色',
shape: '圆柱形',
use: '写字'
};
console.log(Object.assign(apple, pen));
// 后面的覆盖前面的(最终返回的不是新的,而是修改了前面的)
// { color: '黑色', shape: '圆柱形', taste: '甜', use: '写字' }
// Object.assign 直接合并到了第一个参数中,返回的就是合并后的对象
console.log(apple); // { color: '黑色', shape: '圆柱形', taste: '甜', use: '写字' }
console.log(Object.assign(apple, pen) === apple); // true
// 可以合并多个对象
// 第一个参数使用一个空对象来实现合并返回一个新对象的目的
console.log(Object.assign({}, apple, pen)); // { color: '黑色', shape: '圆柱形', taste: '甜', use: '写字' }
console.log(apple); // { color: '红色', shape: '圆形', taste: '甜' }
console.log({...apple, ...pen}); // { color: '黑色', shape: '圆柱形', taste: '甜', use: '写字' }
// 注意事项
// (1) 基本数据类型作为源对象
// 与对象的展开类似,先转换成对象,再合并
console.log(Object.assign({}, undefined)); // {}
console.log(Object.assign({}, null)); // {}
console.log(Object.assign({}, 1)); // {}
console.log(Object.assign({}, true)); // {}
console.log(Object.assign({}, 'str')); // { '0': 's', '1': 't', '2': 'r' }
// (2) 同名属性的替换
// 后面的直接覆盖前面的
const apple = {
color: ['红色', '黄色'],
shape: '圆形',
taste: '甜'
};
const pen = {
color: ['黑色', '银色'],
shape: '圆柱形',
use: '写字'
};
console.log(Object.assign({}, apple, pen)); // { color: [ '黑色', '银色' ], shape: '圆柱形', taste: '甜', use: '写字' }
// 应用
// 合并默认参数和用户参数
const logUser = userOptions => {
const DEFAULTS = {
username: 'ZhangSan',
age: 0,
sex: 'male'
};
const options = Object.assign({}, DEFAULTS, userOptions);
console.log(options);
};
logUser(); // { username: 'ZhangSan', age: 0, sex: 'male' }
logUser({}); // { username: 'ZhangSan', age: 0, sex: 'male' }
logUser({username: 'Alex'}); // { username: 'Alex', age: 0, sex: 'male' }
1.3 Object.keys()、Object.values() 和 Object.entries()
Object.keys
方法,返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。
Object.values
方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。
Object.entries()
方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。
// 基本用法
const person = {
name: 'Alex',
age: 18
};
// 返回键数组
console.log(Object.keys(person)); // [ 'name', 'age' ]
// 返回值数组
console.log(Object.values(person)); // [ 'Alex', 18 ]
// 返回键值二维数组
console.log(Object.entries(person)); // [ [ 'name', 'Alex' ], [ 'age', 18 ] ]
// 与数组类似方法的区别
console.log([1, 2].keys()); // Object [Array Iterator] {}
console.log([1, 2].values()); // Object [Array Iterator] {}
console.log([1, 2].entries()); // Object [Array Iterator] {}
// 数组的 keys()、values()、entries() 等方法是实例方法,返回的都是 Iterator
// 对象的 Object.keys()、Object.values()、Object.entries() 等方法是构造函数方法,返回的是数组
// 应用(使用 for...of 循环遍历对象)
const person = {
name: 'Alex',
age: 18
};
for (const key of Object.keys(person)) {
console.log(key);
}
// name
// age
for (const value of Object.values(person)) {
console.log(value);
}
// Alex
// 18
for (const entries of Object.entries(person)) {
console.log(entries);
}
// [ 'name', 'Alex' ]
// [ 'age', 18 ]
for (const [key, value] of Object.entries(person)) {
console.log(key, value);
}
// name Alex
// age 18
// Object.keys()/values()/entires() 并不能保证顺序一定是你看到的样子,这一点和 for in 是一样的
// 如果对遍历顺序有要求那么不能用 for in 以及这种方法,而要用其他方法