ES6的知识点总结(一)
文章目录
一:let和const
1.let语法
- 1.1 基本用法
ES6 新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。
{
let a = 10;
var b = 1;
}
a // ReferenceError: a is not defined.
b // 1
上面代码在代码块之中,分别用let和var声明了两个变量。然后在代码块之外调用这两个变量,结果let声明的变量报错,var声明的变量返回了正确的值。这表明,let声明的变量只在它所在的代码块有效。
for循环的计数器,就很合适使用let命令。
for (let i = 0; i < 10; i++) {
// ...
}
console.log(i);
// ReferenceError: i is not defined
上面代码中,计数器i只在for循环体内有效,在循环体外引用就会报错。
下面的代码如果使用var,最后输出的是10。
- 1.2 不存在变量提升
var命令会发生“变量提升”现象,即变量可以在声明之前使用,值为undefined。这种现象多多少少是有些奇怪的,按照一般的逻辑,变量应该在声明语句之后才可以使用。
为了纠正这种现象,let命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。
// var 的情况
console.log(foo); // 输出undefined
var foo = 2;
// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;
上面代码中,变量foo用var命令声明,会发生变量提升,即脚本开始运行时,变量foo已经存在了,但是没有值,所以会输出undefined。变量bar用let命令声明,不会发生变量提升。这表示在声明它之前,变量bar是不存在的,这时如果用到它,就会抛出一个错误。
- 1.3 暂时性死区
只要作用域内存在 let、const,它们所声明的变量或常量就自动 “绑定” 这个区域,不再受到外部作用域的影响。
let a = 2;
function func() {
console.log(a); // 报错
let a = 1;
}
func();
let a = 2;
function func() {
console.log(a); // 2
}
func();
- 1.4 不允许重复声明
let不允许在相同作用域内,重复声明同一个变量。
因此,不能在函数内部重新声明参数。
2.const 命令
- 2.1 基本用法
const声明一个只读的常量。一旦声明,常量的值就不能改变。
- 2.2 本质
const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。
const foo = {};
// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123
// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only
上面代码中,常量foo储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把foo指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性。
- 2.3 window 对象的属性和方法(全局作用域中)
全局作用域中,var 声明的变量,function 声明的函数,会自动变成 window 对象的属性或方法。
var age = 18;
function add() {}
console.log(window.age); // 18
console.log(window.add === add); // true
let age = 18;
const add = function() {}
console.log(window.age); // undefined
console.log(window.add === add); // false
- 2.4 什么时候用 let,什么使用用 const
原则:如果不知道用什么的时候,就用 const
原因:如果应该是常量,那么刚好符合需求。如果应该是变量,那么后来报错时,再来改为变量也为时不晚。同时,一开始就设置为常量还会避免真的需要为常量时,该值在后来被意外修改的情况。
3.let和const总结
- 1.let 声明的变量会产生块作用域,var 不会产生块作用域
- 2.const 声明的常量也会产生块作用域
- 3.不同代码块之间的变量无法互相访问
- 4.注意: 对象属性修改和数组元素变化不会出发 const 错误 (数组-和对象存的是引用地址)
- 5.应用场景:声明对象类型使用 const,非对象类型声明选择 let
- 6.cosnt声明必须赋初始值,标识符一般为大写,值不允许修改。
二 :解构赋值
1.数组的解构赋值
- 1.1 原理
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。
以前,为变量赋值,只能直接指定值。
let a = 1;
let b = 2;
let c = 3;
ES6 允许写成下面这样。
let [a, b, c] = [1, 2, 3];
上面代码表示,可以从数组中提取值,按照对应位置,对变量赋值。
- 1.2 数组解构赋值的默认值
(1)默认值的基本用法
const [a, b] = [];
console.log(a, b); // undefined undefined
// ---------------------------------------
const [a = 1, b = 2] = [];
console.log(a, b); // 1 2
(2)默认值的生效条件
只有当一个数组成员严格等于 (===) undefined 时,对应的默认值才会生效。
const [a = 1, b = 2] = [3, 0]; // 3 0
const [a = 1, b = 2] = [3, null]; // 3 null
const [a = 1, b = 2] = [3]; // 3 2
(3)默认值表达式
如果默认值是表达式,默认值表达式是惰性求值的(即:当无需用到默认值时,表达式是不会求值的)
const func = () => {
return 24;
};
const [a = func()] = [1]; // 1
const [b = func()] = []; // 24
1.3 数组解构赋值的应用
(1)arguments
function func() {
const [a, b] = arguments;
console.log(a, b); // 1 2
}
func(1, 2);
(2)NodeList
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>NodeList</title>
</head>
<body>
<p>1</p>
<p>2</p>
<p>3</p>
<script>
const [p1, p2, p3] = document.querySelectorAll('p');
console.log(p1, p2, p3);
/*
<p>1</p>
<p>2</p>
<p>3</p>
*/
</script>
</body>
</html>
(3)函数参数的解构赋值
const array = [1, 1];
// const add = arr => arr[0] + arr[1];
const add = ([x = 0, y = 0]) => x + y;
console.log(add(array)); // 2
console.log(add([])); // 0
(4)交换变量的值
let x = 2, y = 1;
// 原来
let tmp = x;
x = y;
y = tmp;
// 现在
[x, y] = [y, x];
// 理解:[x, y] = [2, 1]
console.log(x, y);
// 1 2
(5)跳过某项值使用逗号隔开
在解构数组时,可以忽略不需要解构的值,可以使用逗号对解构的数组进行忽略操作,这样就不需要声明更多的变量去存值了:
var [a, , , b] = [10, 20, 30, 40];
console.log(a); // 10
console.log(b); // 40
上面的例子中,在 a、b 中间用逗号隔开了两个值,这里怎么判断间隔几个值呢,可以看出逗号之间组成了多少间隔,就是间隔了多少个值。如果取值很少的情况下可以使用下标索引的方式来获取值。
(6)剩余参数中的使用
通常情况下,需要把剩余的数组项作为一个单独的数组,这个时候我们可以借助展开语法把剩下的数组中的值,作为一个单独的数组,如下:
var [a, b, ...rest] = [10, 20, 30, 40, 50];
console.log(a); // 10
console.log(b); // 20
console.log(rest); // [30, 40, 50]
在 rest 的后面不能有 逗号 不然会报错,程序会认出你后面还有值。…rest 是剩余参数的解构,所以只能放在数组的最后,在它之后不能再有变量,否则则会报错。
2.对象的解构赋值
2.1 原理
对象的解构和数组基本类似,对象解构的变量是在 {} 中定义的。对象没有索引,但对象有更明确的键,通过键可以很方便地去对象中取值。在 ES6 之前直接使用键取值已经很方便了:
var obj = { name: 'imooc', age: 7 };
var name = obj.name; // imooc
var age = obj.age; // 7
但是在 ES6 中通过解构的方式,更加简洁地对取值做了简化,不需要通过点操作增加额外的取值操作。
var obj = { name: 'imooc', age: 7 };
var { name, age } = obj; // name: imooc, age: 7
在 {} 直接声明 name 和 age 用逗号隔开即可得到目标对象上的值,完成声明赋值操作。
- 模式(结构)匹配
{} = {};
- 属性名相同的完成赋值
const {name, age} = {name: 'jerry', age: 18};
或const {age, name} = {name: 'jerry', age: 18};
- 2.2 对象解构赋值的默认值
-
对象的属性值严格等于 undefined 时,对应的默认值才会生效。
-
如果默认值是表达式,默认值表达式是惰性求值的。
对象的默认值和数组的默认值一样,只能通过严格相等运算符(===)来进行判断,只有当一个对象的属性值严格等于 undefined,默认值才会生效。
var {a = 10, b = 5} = {a: 3}; // a = 3, b = 5
var {a = 10, b = 5} = {a: 3, b: undefined}; // a = 3, b = 5
var {a = 10, b = 5} = {a: 3, b: null}; // a = 3, b = null
所以这里的第二项 b 的值是默认值,第三项的 null === undefined 的值为 false,所以 b 的值为 null。
- 2.3 重命名属性
在对象解构出来的变量不是我们想要的变量命名,这时我们需要对它进行重命名。
var {a:x = 8, b:y = 3} = {a: 2};
console.log(x); // 2
console.log(y); // 3
- 2.4 注意点
(1)如果要将一个已经声明的变量用于解构赋值,必须非常小心。
// 错误的写法
let x;
{x} = {x: 1};
// SyntaxError: syntax error
上面代码的写法会报错,因为 JavaScript 引擎会将{x}理解成一个代码块,从而发生语法错误。只有不将大括号写在行首,避免 JavaScript 将其解释为代码块,才能解决这个问题。
// 正确的写法
let x;
({x} = {x: 1});
上面代码将整个解构赋值语句,放在一个圆括号里面,就可以正确执行。关于圆括号与解构赋值的关系,参见下文。
(2)解构赋值允许等号左边的模式之中,不放置任何变量名。因此,可以写出非常古怪的赋值表达式。
({} = [true, false]);
({} = 'abc');
({} = []);
上面的表达式虽然毫无意义,但是语法是合法的,可以执行。
(3)由于数组本质是特殊的对象,因此可以对数组进行对象属性的解构。
let arr = [1, 2, 3];
let {0 : first, [arr.length - 1] : last} = arr;
first // 1
last // 3
上面代码对数组进行对象解构。数组arr的0键对应的值是1,[arr.length - 1]就是2键,对应的值是3。
3.字符串的解构赋值
既可以用数组的形式来解构赋值,也可以用对象的形式来解构赋值。
// 数组形式解构赋值
const [a, b, , , c] = 'hello';
console.log(a, b, c); // h e o
// 对象形式解构赋值
const {0: a, 1: b, 4: o, length} = 'hello';
console.log(a, b, o, length); // h e o 5
4.小结
本节讲解了 ES6 解构赋值的使用方法,总结下来一共有以下几点:
- 1.解构赋值一般针对对象和数组,如果解构对象是 undefined 或是 null 都会报错;
- 2.默认值的生效条件是,只有当解构的对象的值是严格模式下的 undefined 的情况下,默认值才会生效;
- 3.可以不借助中间变量来交换两个值;
- 4.在解构复杂的数据解构时,注意声明的对象要和目标的对象有着相同的解构形式,才能去解构目标对象。
三:函数的扩展
1.函数参数的默认值
- 1.1 认识函数参数的默认值
调用函数的时候传参了,就用传递的参数;如果没传参,就用默认值
- 1.2 函数参数默认值的基本用法
// ES6 默认值实现方式
const multiply = (x, y = 3) => {
return x * y;
};
console.log(multiply(2, 2)); // 4
console.log(multiply(2)); // 6
- 1.3 默认值的生效条件
不传参数,或者明确的传递 undefined 作为参数,只有这两种情况下,默认值才会生效。
注意:null 就是 null,不会使用默认值。
- 1.4 与解构赋值默认值结合使用
参数默认值可以与解构赋值的默认值,结合起来使用。
function foo({x, y = 5}) {
console.log(x, y);
}
foo({}) // undefined 5
foo({x: 1}) // 1 5
foo({x: 1, y: 2}) // 1 2
foo() // TypeError: Cannot read property 'x' of undefined
上面代码只使用了对象的解构赋值默认值,没有使用函数参数的默认值。只有当函数foo()的参数是一个对象时,变量x和y才会通过解构赋值生成。如果函数foo()调用时没提供参数,变量x和y就不会生成,从而报错。通过提供函数参数的默认值,就可以避免这种情况。
- 1.5 参数默认值的位置
通常情况下,定义了默认值的参数,应该是函数的尾参数。因为这样比较容易看出来,到底省略了哪些参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的。
// 例一
function f(x = 1, y) {
return [x, y];
}
f() // [1, undefined]
f(2) // [2, undefined]
f(, 1) // 报错
f(undefined, 1) // [1, 1]
// 例二
function f(x, y = 5, z) {
return [x, y, z];
}
f() // [undefined, 5, undefined]
f(1) // [1, 5, undefined]
f(1, ,2) // 报错
f(1, undefined, 2) // [1, 5, 2]
上面代码中,有默认值的参数都不是尾参数。这时,无法只省略该参数,而不省略它后面的参数,除非显式输入undefined。
如果传入undefined,将触发该参数等于默认值,null则没有这个效果。
2.rest 参数
- 2.1 前言
剩余语法(Rest syntax 也可以叫剩余参数)看起来和展开语法完全相同都是使用 … 的语法糖,不同之处在于剩余参数用于解构数组和对象。从某种意义上说,剩余语法与展开语法是相反的:展开语法将数组展开为其中的各个元素,而剩余语法则是将多个元素收集起来成为一个整体。
- 2.2 函数参数
在讲解剩余参数前,我们先来看看,剩余参数在函数参数中都解决了哪些问题?为什么会引入剩余参数的概念?
在 ES5 中,函数经常会传入不定参数,在传入不定参数时,ES5 的给出的解决方案是通过 arguments 对象来获取函数调用时传递的参数。 arguments 对象不是一个数组,它是一个类数组对象,所谓类数组对象,就是指可以通过索引属性访问元素并且拥有 length 属性的对象。
使用方式很简单在函数定义时使用 … 紧接着跟一个收集的参数,这个收集的参数就是我们所传入不定参数的集合 —— 也就是数组。这样就很简单地摆脱了 arguments 的束缚。另外,还可以指定一个默认的参数,如下示例:
function fn(name, ...args) {
console.log(name); // 基础参数
console.log(args); // 剩下的参数组成的数组
}
fn('ES6');
// 'ES6'
// []
fn('imooc', 7, 'ES6');
// "imooc"
// [7, "ES6"]
上面的代码中给函数第一个参数,声明一个变量 name,剩余的参数会被 … 收集成一个数组,这就是剩余参数。引入剩余参数就是为了能替代函数内部的 arguments,由于 arguments 对象不具备数组的方法,所以很多时候在使用之前要先转换成一个数组。而剩余参数本来就是一个数组,避免了这多余的一步,使用起来既优雅又自然。
- 2.3 注意事项
箭头函数的剩余参数
箭头函数的参数部分即使只有一个剩余参数,也不能省略圆括号。
const add = (...args) => {};
使用剩余参数替代 arguments 获取实际参数
剩余参数是一个 “真数组”,arguments 是一个 “伪数组”
剩余参数的名字可以自定义
剩余参数的位置
剩余参数只能是最后一个参数,之后不能再有其他参数,否则会报错。
- 2.5 小结
本节结合了 ES5 函数中的 arguments 对象引入了为什么 ES6 会引入剩余参数的概念,可以看到剩余参数所带来的好处。本节内容可以总结以下几点:
- 剩余参数是为了能替代函数内部的 arguments 而引入的;
- 和展开语法相反,剩余参数是将多个单个元素聚集起来形成一个单独的个体的过程。
3.箭头函数
- 3.1 前言
在编程中使用最多的就是函数,在 ES5 中是用 function 关键字来定义函数的,由于历史原因 function 定义的函数存在一些问题,如 this 的指向、函数参数 arguments 等。
ES6 规定了可以使用 “箭头” => 来定义一个函数,语法更加简洁。它没有自己的 this、arguments、super 或 new.target,箭头函数表达式更适用于那些本来需要匿名函数的地方,但它不能用作构造函数。
- 3.2 认识箭头函数
普通函数:
function 函数名() {}
const 变量名 = function () {};
箭头函数:
参数 => 函数体
() => {}
由于箭头函数是匿名函数,所以我们通常把它赋给一个变量
const add = (x, y) => {
return x + y;
};
console.log(add(1, 1)); // 2
- 3.3 箭头函数注意事项
3.3.1 省略写法
const add = (x) => {
return x + 1;
};
// 单个参数可以省略 ()
const add = x => {
return x + 1;
};
// 无参数
const test = () => {
return 1;
};
//或者
const test = _ => {
return 1;
};
3.3.2 单行函数体
const add = (x, y) => {
return x + y;
};
// 单行函数体可以省略 return 和 {},且一但省略就 return 和 {} 都要一起省略
const add = (x, y) => x + y;
- 3.4 其他注意点
3.4.1 不能用作构造器
箭头函数不能用作构造器,和 new 一起用会抛出错误。
var Foo = () => {};
var foo = new Foo(); // TypeError: Foo is not a constructor
3.4.2 没有 prototype 属性
箭头函数没有 prototype 属性。
var Foo = () => {};
console.log(Foo.prototype); // undefined
3.4.3 不能使用 yield 命令
yield 关键字通常不能在箭头函数中使用,因此箭头函数不能用作 Generator 函数。
- 3.5 小结
本节主要讲解了 ES6 的箭头函数,总结了以下几点:
1.更短的函数,优雅简洁;
2.箭头函数不会创建自己的 this,它只会从自己的作用域链的上一层继承 this;
3.不能绑定 arguments, 只能使用 ...args 展开运算来获取当前参数的数组。
4.函数参数的尾逗号
ES2017 允许函数的最后一个参数有尾逗号(trailing comma)。
此前,函数定义和调用时,都不允许最后一个参数后面出现逗号。
function clownsEverywhere(
param1,
param2
) { /* ... */ }
clownsEverywhere(
'foo',
'bar'
);
上面代码中,如果在param2或bar后面加一个逗号,就会报错。
如果像上面这样,将参数写成多行(即每个参数占据一行),以后修改代码的时候,想为函数clownsEverywhere添加第三个参数,或者调整参数的次序,就势必要在原来最后一个参数后面添加一个逗号。这对于版本管理系统来说,就会显示添加逗号的那一行也发生了变动。这看上去有点冗余,因此新的语法允许定义和调用时,尾部直接有一个逗号。
function clownsEverywhere(
param1,
param2,
) { /* ... */ }
clownsEverywhere(
'foo',
'bar',
);
这样的规定也使得,函数参数与数组和对象的尾逗号规则,保持一致了。
5.catch 命令的参数省略
JavaScript 语言的try…catch结构,以前明确要求catch命令后面必须跟参数,接受try代码块抛出的错误对象。
try {
// ...
} catch (err) {
// 处理错误
}
上面代码中,catch命令后面带有参数err。
很多时候,catch代码块可能用不到这个参数。但是,为了保证语法正确,还是必须写。ES2019做出了改变,允许catch语句省略参数。
try {
// ...
} catch {
// ...
}
四:数组的扩展
1.扩展运算符
- 1.1 含义
扩展运算符(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;
}
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()了。
- 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' ]
不过,这两种方法都是浅拷贝,使用的时候需要注意。
(3)字符串转为数组
扩展运算符还可以将字符串转为真正的数组。
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('');
(4)类数组转为数组
// 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.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的成员。
3.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,但是会跳过空位。
4.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 次。
5.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变量。
6.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 号位之前结束。
7.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
8.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