这是ES6的入门篇教程的笔记,网址:链接描述,以下内容中粗体+斜体表示大标题,粗体是小标题,还有一些重点;斜体表示对于自身,还需要下功夫学习的内容。这里面有一些自己的见解,所以若是发现问题,欢迎指出~
上一篇es5的到最后令人崩溃,看来深层的东西还是不太熟,希望这次不要这样了!!!
函数的扩展
1、函数参数的默认值
基本用法
ES6之前,不能直接为函数的参数指定默认值,只能采用变通的方法。
参数默认值不是传值的,而是每次都重新计算默认值表达式的值。也就是说,参数默认值是惰性求值的。
function log(x, y) {
y = y || 'World';
console.log(x, y);
}
log('Hello', '') // Hello World 后一个字段是本意是空字符,也会被赋值为'World'
// 改进一下,赋值时为以下
if (typeof y === 'undefined') {
y = 'World';
}
// ES6允许为函数的参数设置默认值,即直接写在参数定义的后面
function log(x, y = 'World') {
console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello World
log('Hello', '') // Hello
let x = 99;
function foo(p = x + 1) {
console.log(p);
}
foo() // 100
x = 100;
foo() // 101 这就是惰性求值,参数p的默认值是x+1,每次调用函数foo,都会重新计算x + 1,而不是默认p等于100
与解构赋值默认值结合使用
参数默认值可以与解构赋值的默认值,结合起来使用。
function foo({x, y = 5}) { // 只用了对象的结构赋值默认值,没有使用函数参数的默认值。
console.log(x, y);
}
foo({}) // undefined 5
foo() // TypeError: Cannot read prototype 'x' of undefined
function foo({x, y = 5} = {}) { // 如果没有提供参数,函数foo的参数默认为一个空对象。
console.log(x, y);
}
foo() // undefined 5
有点要绕晕了,下面这个是重点,如果能知道两者的区别,说明就已经理解了。
// 第一种
function m1({x = 0, y = 0} = {}) {
return [x, y];
}
// 第二种
function m2({x, y} = {x: 0, y: 0}) {
return [x, y];
}
上面的这两种写法都对函数的参数设定了默认值,区别在于第一种行数参数的默认值是空对象,但是这是了对象解构赋值的默认值;第二种函数参数的默认值是一个有具体属性的对象,但是没有设置对象解构赋值的默认值。
一起看看它们的输出情况。
// 函数没有参数的情况
m1() // [0, 0]
m2() // [0, 0]
// x有值,y无值的情况
m1({x: 3}) // [3, 0]
m2({x: 3}) // [3, undefined]
// x和y都无值的情况
m1({}) // [0, 0]
m2({}) // [undefined, undefined]
// 综上,推荐第一种写法,当然按需写更好
参数默认值的位置
通常情况下,定义了默认值的参数,应该是函数的尾参数。因为这样比较容易看出来,到底省略了那些参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的,除非显式输入undefined。
//
function f(x = 1, y) {
return [x, y];
}
f() // [1, undefined]
f(2) // [2, undefined]
f(, 1) // 报错
f(undefined, 1) // [1, 1]
作用域
一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)。等到初始化结束,这个作用域就会消失。这种语法行为,再不设置参数默认值时,是不会出现的。
let x = 1;
function f(y = x) { // 参数y = x形成一个单独的作用域,在这个作用域里面,变量x本身没有定义,所以指向外层的全局变量x
let x = 2; // 函数调用时,函数体内部的局部变量x影响不到默认值变量x
console.log(y)
}
f() // 1
// 如果此时,全局变量x不存在,就会报错
function f(y = x) {
let x = 2;
console.log(y);
}
f() // ReferenceError: x is not defined
2、rest参数
ES6引入rest参数(形式为...变量名),用于获取函数的多余参数,这样就不需要arguments对象了。rest参数搭配的变量时一个数组,该变量将多余的参数放入数组中。
注:rest参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
函数的length属性,不包括rest参数。
function add(...values) { // 利用rest参数,可以向该函数传入任意数目的参数
let sum = 0;
for(let val of values) {
sum += val;
}
return sum;
}
add(2, 5, 3) // 10
// 利用rest参数改写数组push方法
function push(array, ...items) {
items.forEach(function(item) {
array.push(item);
console.log(item);
})
}
let a = [];
push(a, 1, 2, 3);
// rest参数之后不能再有其他参数
// 报错
function f(a, ...b, c) {
// ...
}
5、箭头函数
基本用法
ES6允许使用“箭头”(=>)定义函数。
如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。
如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。
注:函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象(this对象的指向是可变的,但是在箭头函数中,它是固定的);不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
let f = v => v;
// 等同于
let f = function (v) {
return v;
};
let f = () => 5;
// 等同于
let f = function () { return 5 };
let sum = (num1, num2) => num1 + num2;
// 等同于
let sum = function(num1, num2) {
return num1 + num2;
};
// 报错 返回对象必须在对象外面加上括号,否则会报错。
let getItem = id => { id: id, name: "Temp" };
// 不报错
let getItem = id => ({ id: id, name: "Temp" });
// 箭头函数可以与变量解构结合使用
const full = ({ first, last }) => first + '' + last;
// 箭头函数使得表达更加简介
const isEven = n => n % 2 === 0;
const aqure = n => n * n;
// 简化回调函数
let result = values.sort((a, b) => a-b);
// 正常写法
let result = values.sort(function (a, b) {
return a - b;
});
需要注意this的指向问题:箭头函数让this指向固定化,箭头函数的this绑定定义时错在的作用域,普通函数的this指向运行时所在的作用域。
let handle = {
id: '123456',
init: function() {
document.addEventListener('click',
event => this.doSomething(event.type), false); // 箭头函数,this.doSomething中的this指向handler对象(定义时的作用域);否则的话,this指向document对象
},
doSomething: function(type) {
console.log('Handling ' + type + ' for ' + this.id);
}
};
this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正式因为它没有this,所以也就不能用作构造函数。
// ES6
function foo() {
setTimeout(() => {
console.log('id:', this.id);
}, 100);
}
// ES5
function foo() {
let _this = this;
setTimeout(function () {
console.log('id:', _this.id);
}, 100);
}
箭头函数不适用场合
1、定义对象的方法,且该方法内部包括this。
这是因为对象不构成单独的作用域,导致箭头函数定义时的对象就是全局作用域。
const cat = {
lives: 9,
jumps: () => { // 箭头函数,使得this指向全局对象,不会得到预期解构;如果是普通函数,该方法内部的this指向cat
this.lives--;
}
}
2、需要动态this的时候,也不应使用箭头函数。
let button = document.getElementById('press');
button.addEventListener('click', () => {
this.classList.toggle('on');
});
点击按钮会报错,因为button的监听函数是一个箭头函数,导致里面的this就是全局对象。如果改成普通函数,this就会动态指向被点击的按钮对象。
另外,如果函数体很复杂,有很多行,或者函数内部有大量的读写操作,不单纯是为了计算值,这时也不应该使用箭头函数,而是要使用普通函数,这样可以提高代码可读性。
6、尾调用优化
尾调用(Tail Call)是函数式编程的一个重要概念,本身非常简单,一句话就能说清楚,就是指某个函数的最后一步是调用另一个函数。
function f(x) {
return g(x); // 函数f的最后一步是调用函数g,这就叫尾调用
}
尾递归
函数调用自身,称为递归。如果尾调用自身,就称为尾递归。
数组的扩展
含义
扩展运算符(spread)是三个点(...)。它好比rest参数的逆运算,将一个数组转为用逗号分隔的参数序列。
console.log(1, ...[2, 3, 4], 5);
// 1 2 3 4 5
// 如果扩展运算符后面是一个空数组,则不产生任何效果
[...[], 1]
// [1]
// 注意,只有函数调用时,扩展运算符才可以放在圆括号中,否则会报错。前两种报错,是因为扩展运算符所在的括号不是函数调用。
(...[1, 2]) // Uncaught SyntaxError: Unexpected token ...
console.log((...[1, 2])) // Uncaught SynaxError: Unexpexted token ...
console.log(...[1, 2]) // 1 2
扩展运算符的应用
(1)复制数组
数组是复合的数据类型,直接复制的话,指数复制了指向底层数据结构的指针,而不是克隆一个全新的数组。
const a1 = [1, 2];
const a2 = a1; // a2并不是a1的克隆,而是指向同一份数据的另一个指针,修改a2,会直接导致a1的改变。
a2[0] = 2;
a1; // [2, 2]
// ES5复制数组
const a1 = [1, 2];
const a2 = a1.concat();
a2[0] = 2;
a1 // [1, 2]
// ES6的简便写法
const a1 = [1, 2];
const a2 = [...a1];
const [...a2] = a1; // 这两种写法,a2都是a1的克隆
(2)合并数组
扩展运算符提供了数组合并的新写法。
const arr1 = ['a', 'b'];
const arr2 = ['c'];
// ES5的合并数组
arr1.concat(arr1, arr2);
// ES6的合并数组
[...arr1, ...arr2];
// 注意下面的合并,数组里面的元素是对象,拷贝过去的就只能是地址!!!
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
// 拷贝过去的只有地址,也就是说,如果修改了原数组的成员,会同步反映到新数组。
(3)与解构赋值结合
扩展运算符可以与解构赋值结合起来,用于生成数组。
// ES5
a = list[0], rest = list.slice(1)
// ES6
[a, ...rest] = list
// 如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错
// 报错
const [...butLast, last] = [1, 2, 3, 4, 5];
(4)字符串
扩展运算符还可以将字符串转为真正的数组。
[...'hello']; // ["h", "e", "l", "l", "o"]
5、数组实例的find()和findIndex()
数组实例的find方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined。接受三个参数,依次为当前的值、当前的位置和原数组。
[1, 4, -5, 10].find((n) => n < 0) // -5
数组实例的findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1.
7、数组实例的entries(),keys()和values()
ES6提供三个新的方法——entries(), keys()和values()——用于遍历数组。keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。
感觉可以用foreach一步做到,没必要细看。。。。
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
[1, 2, 3].includes(3, 3); // false 第二个参数表示搜索的起始位置
[1, 2, 3].includes(3, -1); //true
9、数组实例的flat(),flatMap()
数组的成员有时还是数组,Array.prototype.flat()用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响。
[1, 2, [3, [4, 5]]].flat(); // [1, 2, 3, [4, 5]] 默认只会“拉平”一层
[1, 2, , [3, [4, 5]]].flat(2); // [1, 2, 3, 4, 5] 参数为2,表示要“拉平”两层的嵌套数组,会跳过空位
[1, [2, [3]]].flat(Infinity); // [1, 2, 3] Infinity关键字作为参数,不管有多少层嵌套,都要转成一维数组
flatMap()方法对原数组的每个成员执行一个函数(相当于执行Array.prototype.map()),然后回返回值组成的数组执行flat()方法(默认只能展开一层)。该方法返回一个新数组,不改变原数组。
[1, 2, 3, 4].flatMap(x => [[x * 2]]) // [[2], [4], [6], [8]] 相当于[[[2]], [[4]], [[6]], [[8]]].flat()
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。