es6的函数、对象、数组扩展、Set、Map、Iterator


函数的扩展

1. 函数参数的默认值

// es5写法
function add(a,b) {
  a = a || 10;
  b = b || 20;
  return a + b;
}
console.log(add());	// 30
// es6写法
function add(a = 10, b = 20) {
  return a + b;
}
console.log(add());	// 30
// 默认值也可以是一个函数
function add(a = 10, b = getVal(5)) {
  return a + b;
}
function getVal(value) {
  return value + 5;
}
console.log(add());	// 20

2. rest参数

形式为:...变量名
定义函数实现:计算传入所有参数的和

// es5 使用arguments
function sum() {
  let result = 0;
  for (let i = 0; i < arguments.length; i++) {
    result += arguments[i];
  }
  console.log(arguments);
  return result;
}
console.log(sum(1,2,3));	// 6
// es6 使用rest参数
function sum(...value) {
  let result = 0;
  for(let i of value) {
    result += i;
  }
  console.log(value);
  return result;
}
console.log(sum(1,2,3));	// 6

在这里插入图片描述
在这里插入图片描述

这两种方法看似差不多,但是操作的元素有本质上的区别

  • arguments是一个类数组,本质是对象,里面保存的是传递给函数的参数
  • rest参数value是一个真正的数组,可以正常调用数组的所有方法
  • 所以在某些场景中,不用将arguments转为真正的数组,可以直接使用rest参数代替

⚠️:

  • rest参数可以和其他形参共存,但是rest参数只能是最后一个参数,否则会报错
  • 函数的length属性,不包括rest参数
console.log((function(a, ...b) {}).length)  // 1

3. 扩展运算符

剩余运算符和扩展运算符都是...
剩余运算符:把多个独立的参数合并到一个数组中
扩展运算符:把一个数组分割,将各个项作为分离的参数传入函数

const maxNum = Math.max(20,30);
console.log(maxNum);	// 30

const arr = [10,20,50,30,100,200];
cconsole.log(Math.max(...arr));		// 200

4. 箭头函数

let func = a => a;
// 等同于
let func = function a() {
  return a;
}

参数不止一个的时候,()不能省略
代码块不止一条return语句的时候,{}不能省略

let sum = (a,b) => {
  let newA = a + 10;
  return newA + b;
}

如果箭头函数直接返回一个对象,必须在对象外面加上(){}会被解释为代码块

// 报错
let getObj = id => {
  id: id,
  name: "a"
};
// 不报错
let getObj = id => ({
  id: id,
  name: "a"
});

箭头函数也能简化回调函数:

const arr = [1,2,3,4,5];
// es5
[1,2,3,4,5].map(function(x) {
  return x * x;
})
// es6
[1,2,3,4,5].map(x => x * x);

rest参数和箭头函数结合使用:

const headAndTail = (head, ...tail) => [head,tail]; // head: 1 tail: [2,3,4,5]
console.log(headAndTail(1,2,3,4,5));	// [1,[2,3,4,5]]

箭头函数的this指向

箭头函数没有自己的this对象,它内部的this就是定义时上层作用域中的this
而普通函数的内部this指向函数运行时所在的对象,或者说是调用该函数的对象

下面例子对比回调函数分别为箭头函数和普通函数时的this指向:

function Timer() {
  this.s1 = 0;
  this.s2 = 0;
  // 箭头函数
  setInterval(() => this.s1++, 1000);
  // 普通函数
  setInterval(function () {
    this.s2++;
  }, 1000);
}

const timer = new Timer();

setTimeout(() => console.log('s1: ', timer.s1), 100);
setTimeout(() => console.log('s2: ', timer.s2), 100);
// s1: 3
// s2: 0

Timer函数内部设置了两个定时器,分别使用了箭头函数普通函数。前者的this指向定义时所在的作用域(即Timer函数),后者的this指向运行时所在的作用域(即全局对象

下面是一个例子,DOM 事件的回调函数封装在一个对象里面:

let obj = {
  id: "123",
  init: function() { 
    document.addEventListener("click", event => {
      this.doSomething(event.type);
    });
  }
  doSomething: function(type) {
    console.log(type + this.id);
  }
};

init()方法中,使用了箭头函数,这导致这个箭头函数里面的this,总是指向obj对象。如果回调函数是普通函数,那么运行this.doSomething()这一行会报错,因为此时this指向document对象

总之,箭头函数根本没有自己的this,导致内部的this就是外层代码块的this

下面是 Babel 转箭头函数产生的 ES5 代码,就能清楚地说明this的指向

// ES6
function foo() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}

// ES5
function foo() {
  var _this = this;

  setTimeout(function () {
    console.log('id:', _this.id);
  }, 100);
}

上面代码中,转换后的 ES5 版本清楚地说明了,箭头函数里面根本没有自己的this,而是引用外层的this


不适用场合

下面两个场合不应该使用箭头函数:

  • 定义对象的方法,且该方法内部包括this
const cat = {
  lives: 9,
  jumps: () => {
    this.lives--;
  }
}

上面代码中,cat.jumps()方法是一个箭头函数。调用cat.jumps()时,如果是普通函数,该方法内部的this指向cat对象,如果写成上面那样的箭头函数,该方法内部的this就是它外层的this,由于对象不构成单独的作用域,所以外层的this指向的就是全局对象

  • 需要动态this的时候,也不应使用箭头函数
let btn = document.getElementById("press");
btn.addEventListener("click", () => {
  this.classList.toggle("on");
});

上面代码运行时,点击按钮会报错
因为btn监听函数是一个箭头函数,导致里面的this就是全局对象
如果改成普通函数this就会动态指向被点击的按钮对象


解构赋值

1. 数组的解构赋值

// es5写法
let a = 1;
let b = 2;
let c = 3;

// es6写法
let [a,b,c] = [1,2,3];

es6的写法:本质上属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值

下面还有一些使用嵌套数组进行解构的例子:

let [a,[b],c] = [1, [2], 3];
console.log(a,b,c);	// 1 2 3

let [x, , y] = [1, 2, 3];
console.log(x,y);	// 1 3

let [h, ...t] = [1, 2, 3, 4];
console.log(h,t); // 1 [2,3,4]

// 如果解构不成功,变量的值就等于undefined
let [m, n, ...p] = ["hello"];
console.log(m, n, p); // hello undefind []

还有一种情况是不完全结构,即等号左边的模式,只匹配一部分的等号右边的数组

let [x,y] = [1, 2, 3];
console.log(x, y);	// 1 2

解构赋值也可以指定默认值:

let [x, y = 'b'] = ['a']; 	// x = 'a', y = 'b'
let [x, y = 'b'] = ['a', undefined]; // x = 'a', y = 'b'

⚠️:ES6 内部使用严格相等运算符(===),判断一个位置是否有值。所以,只有当一个数组成员严格等于undefined默认值才会生效。


2. 对象的解构赋值

对象的解构与数组有一个重要的不同:数组的元素按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序变量必须与属性同名,才能取到正确的值

let { a, b } = { a: 1, b: 2 };
console.log(a, b);	// 1 2

let { c } = { a: 1, b: 2 };
console.log(c);	 // undefined

对象的解构赋值,可以很方便地将现有对象的属性和方法,赋值到某个变量:

// 变量名和属性名必须一样
let obj = {
  name: 'AIpoem',
  age: 19,
  sayHi: function() {
    console.log("hi");
  }
};
let { name, age, sayHi } = obj;
console.log(name, age);	// AIpoem 19
console.log(sayHi);  // ƒ () { console.log("hi")}

如果变量名与属性名不一致,必须写成下面这样:

let { a: newA } = { a: 1, b: 2 };
console.log(newA);

对象的扩展

1. 属性简洁表示

let name = 'AIpoem';
let age = 19;
// 简洁表示
let obj = {
  name,
  age
}
// 等同于
let obj = {
  name: name
  age: age
}

要属性名和变量名相等时才能这样写

方法也可以简写:

// 简洁写法
let obj = {
  method: function() {
    console.log(1);
  }
}
// 等同于
let obj = {
  method() {
    console.log(1);
  }
}

2. 属性名表达式

定义对象的属性,有两种方法:

let obj = {
  a: 1,
  b: 2
};
// 方法一
let attr1 = obj.a;
// 方法二
let attr2 = obj['b'];

es6允许字面量定义对象时,使用表达式作为对象的属性名:

let obj = {
  ['a' + 'bc']: 123
};
console.log(obj.abc);	// 123
console.log(obj['abc']);	// 123

3. 属性的可枚举性

对象的每个属性都有一个描述对象,用来控制该属性的行为
Object.getOwnPropertyDescriptor()可以获取到该属性的描述对象

let obj = { a: 1 };
console.log(Object.getOwnPropertyDescriptor(obj, 'a'));
//{
//  configurable: true
//  enumerable: true
//  value: 1
//  writable: true
//}

enumerable属性,称为可枚举性。如果该属性为false,表示某些操作会忽略当前属性

目前,有四个操作会忽略enumerablefalse的属性:

  • for...in:只遍历对象自身的和继承的可枚举的属性
  • Object.keys():返回对象自身所有可枚举的属性的键名
  • JSON.stringify():只串行化对象自身的可枚举的属性
  • Object.assign():只拷贝对象自身的可枚举的属性

其中,只有for...in会返回继承的属性,其他三个方法都会忽略继承的属性,只处理对象自身的属性

实际上,引入可枚举这个概念的最初目的,就是让某些属性可以规避掉for...in操作,比如对象原型的toString方法,数组的length属性

Object.getOwnPropertyDescriper(Object.prototype, 'toString').enumerable // false
Object.getOwnPropertyDescriper([], 'length').enumerable // false

toStringlength属性的enumerable都是false,因此for...in不会遍历到这两个属性


Set

es6提供了新的数据结构Set,它类似于数组,但是成员的值都是唯一的,没有重复的值

// 创建Set数据结构
let set = new Set();
// 添加元素
set.add(2);
// 删除元素
set.delete(2);
// 校验某个值是否在Set中
console.log(set.has(2));
// Set实例的成员总数
console.log(set.size);

// 接受一个数组用来初始化
let set2 = new Set([1,2,3,4,4]);

⚠️:向Set中加入值的时候,不会发生类型转换。而且Set内部两个NaN是相等的。但两个对象总是不相等的

let set = new Set();

set.add({});
set.size // 1

set.add({});
set.size // 2

1. 遍历操作

有四个遍历方法:

// 返回键名的遍历器
set.keys()
// 返回键值的遍历器
set.values()
// 返回键值对的遍历器
set.entries()
// 使用回调函数遍历每个成员
set.forEach()

Set的遍历顺序就是插入顺序
⚠️:Set结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys方法和values方法的行为完全一致

let set = new Set(['red','green','blue']);

for (let item of set.keys()) {
  console.log(item);
}
// red
// green
// blue
for (let item of set.values()) {
  console.log(item);
}
// red
// green
// blue
for (let item of set.entries()) {
  console.log(item);
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]

set.forEach((value,key) => {
  console.log(key + ":" + value);
})
// red:red
// green:green
// blue:blue

2. 将集合转为数组

利用扩展运算符

let set = new Set([1,2,3,4,4,2,2]);
let arr = [...set];
console.log(arr); // [1, 2, 3, 4]

Map

es6提供了Map数据结构,它类似于对象,也是键值对的集合
键和值可以是任意类型

let map = new Map();
let key = {p: 'hello world'};
// 添加键值对,第一个参数是键,第二个参数是值
map.set(key,'content');
// 读取这个键
console.log(map.get(key));	// "content"
// 校验是否有某个键
console.log(map.has(key));	// true
// 删除键
map.delete(key);
// 接收一个数组用来初始化,指定了两个键name和title
let map2 = new Map([
  ['name','AIpoem'],
  ['age',19]
]);

遍历操作

有四个遍历方法:

// 返回键名的遍历器
map.keys()
// 返回键值的遍历器
map.values()
// 返回键值对的遍历器
map.entries()
// 使用回调函数遍历每个成员
map.forEach()
let map = new Map([
  ['F','no'],
  ['T','yes']
]);

数组的扩展

1. 扩展运算符的应用

1. 1 复制数组

直接复制数组的话只是复制了指向底层数据结构的指针,而不是克隆一个全新的数组

const arr1 = [1,2];
const arr2 = arr1;

arr2[0] = 2;
console.log(arr1);	// [2,2]

上面代码中,arr2并不是arr1的克隆,而是指向同一份数据的另一个指针。修改arr2,会直接导致arr1的变化

// es5 复制数组
const arr1 = [1,2];
const arr2 = arr1.concat();

arr2[0] = 2;
console.log(arr1);	// [1,2]

扩展运算符提供了复制数组的简便写法:

// es6 复制数组
const arr1 = [1,2];
const arr2 = [...arr1];

1. 2 合并数组

const arr1 = ['a', 'b'];
const arr2 = ['c'];
const arr3 = ['d', 'e'];

// es5 合并数组
arr1.concat(arr2,arr3);

// es6 合并数组
[...arr1,...arr2,...arr3]

1.3 与解构赋值结合

扩展运算符和解构赋值可以一起用于生成数组

如果扩展运算符用于数组赋值,只能放在最后一位

const [first, ...rest] = [1, 2, 3, 4, 5];
console.log(first,rest);	// 1 [2, 3, 4, 5]

// 报错
const [...first, last] = [1, 2, 3, 4, 5];

1.4 字符串

扩展运算符还可以字符串转为数组

[...'hello']
// [ "h", "e", "l", "l", "o" ]

2. Array.from()

Array.from()用于类数组对象可遍历对象转为真正的数组

// 类数组对象
let arrLike = {
  '0': 'a',
  '1': 'b'
  length: 2
};
// es5 将类数组对象转为数组
let arr1 = [].slice.call(arrLike);
// es6 将类数组对象转为数组
let arr2 = Array.from(arrLike);

实际应用中,常见类数组对象是dom操作返回的NodeList集合,函数内部的arguments对象,Array.from都可以将它们转为真正的数组

// NodeList对象
let p = document.querySelectorAll('p');
// 之后就能使用一些数组的方法
let arrP = Array.from(p);

// arguments对象
function func() {
  // 之后就能使用一些数组的方法
  let args = Array.from(arguments);
}

⚠️:扩展运算符能将一些可遍历对象转为数组,而Array.from()还支持类数组对象类数组对象本质特征是必须有length属性,因此任何有length属性的对象可以使用Array.from()转为数组,扩展运算符此时则不能

Array.from()还可以接受第二个参数,对每个元素进行处理,将处理完的值放入返回的数组

let arrLike = [1, 2, 3];
Array.from(arrLike, x => x * x);
// 等同于
Array.from(arrLike).map(x => x * x);
// [1, 4, 9]

3. Array.of()

Array.of()用于一组值转换为数组

console.log(Array.of(3, 11, 8))	// [3, 11, 8]
console.log(Array.of(3, 11, 20, [1,2,3], {id:1}))	// [3, 11, 20, [1,2,3], {id:1}]

4. copyWithin()

在数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),使用这个方法,会修改当前数组

接受3个参数:

  • target(必需):从该位置开始替换数据
  • start(可选):从该位置开始读取数据
  • end(可选):到该位置前停止读取数据,默认为数组长度
// 从下标3开始读取数据,未传第三个参数,所以默认读到末尾,就是复制4和5
// 从下标0开始替换数据,4和5替换掉1和2
[1, 2, 3, 4, 5].copyWithin(0, 3)
// [4, 5, 3, 4, 5]

// 下标-2倒着数是下标3 = 4,下标-1倒着数是下标4 = 5,到下标4之前停止读取,也就是复制4
// 从下标0开始替换数据,4替换掉1
[1, 2, 3, 4, 5].copyWithin(0, -2, -1)
// [4, 2, 3, 4, 5]

5. find() 和 findIndex()

find()用于找出第一个符合条件的数组成员

参数是一个回调函数,数组所有成员依次执行该回调,直到找出第一个返回值为true的成员
如果没有符合条件的成员,则返回undefined

// 找出数组中第一个小于0的成员
[1, 4, -5, 10].find(n => n < 0)
// -5

findIndex()用法与find()非常类似,返回第一个符合条件数组成员位置,如果所有成员都不符合条件,则返回-1

[1, 4, -5, 10].findIndex(n => n < 0)
// 2 (-5的下标是2)

6. entries()、 keys()、 values()

es6提供三个新的方法:
entries()keys()values()用于遍历数组,它们都返回一个遍历器对象,可以用for...of循环进行遍历

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

7. includes()

includes()用于判断数组是否包含某个值

[1, 2, 3].includes(2)	// true
[1, 2, 3].includes(4)	// false

没有includes()之前,我们通常使用indexOf(),来检查是否包含某个值,相比起来includes()更加语义化,而且indexOf()内部使用===进行判断,会导致对NaN的误判

if ([1, 2, 3].indexOf(2) !== -1) {// 
}

[NaN].indexOf(NaN)
// -1
[NaN].includes(NaN)
// true

遍历器Iterator

Iterator是一种新的遍历机制

  • 遍历器是一个接口,能快捷访问数据,通过Symbol.iterator来创建遍历器,通过遍历器的next()获取遍历之后的结果
  • 遍历器是用于遍历数据结构的指针对象

Iterator的遍历过程:

  1. 创建一个指针对象,指向当前数据结构的起始位置。也就是说遍历器对象本质上是一个指针对象
  2. 第一次调用指针对象的next()方法,可以将指针指向数据结构的第一个成员
  3. 第二次调用指针对象的next()方法,可以将指针指向数据结构的第二个成员
  4. 不断调用指针对象的next()方法,直到指针指向数据结构的结束位置

每一次调用next()方法,都会返回数据结构的当前成员的信息,就是返回一个对象,有valuedone两个属性,其中value是当前成员的值,done表示遍历是否完成

const items = ['one', 'two', 'three'];
// 创建新的迭代器
const ite = items[Symbol.iterator]();
console.log(ite.next());	// {value: "one", done: false}
console.log(ite.next());	// {value: "two", done: false}
console.log(ite.next());	// {value: "three", done: false}
console.log(ite.next());	// {value: "undefined", done: true}

for…of 循环

一个数据结构只要部署了Symbol.iterator属性,就被视为具有iterator接口,就可以用for...of循环遍历它的成员
也就是说,for...of循环内部调用的是数据结构的Symbol.iterator方法

for...of可以使用的范围包括:
数组、SetMap、类数组对象、后文的Generator对象、字符串

// 数组
const arr1 = ['red', 'green', 'blue'];
for (let item of arr1) {
  console.log(item);	// red green blue
}
// 原有的for...in循环,只能拿到键名,for...of可以获得键值
const arr2 = [
  {
  	a: 1,
  	b: 2
  },
  {
  	c: 3
  }
];
for (let item in arr2) {
  console.log(item);	// 0 1
}
for (let item of arr2) {
  console.log(item);	// {a: 1, b: 2} {c: 3}
}
// 注意:for...of循环调用遍历器接口,数组的遍历器接口值返回具有数字索引的属性
const arr3 = ['one', 'two', 'three'];
arr3.foo = 'hello';

for (let item in arr3) {
  console.log(item);	// 0 1 2 foo
}
for (let item of arr3) {
  console.log(item);	// one two three
}

参考:
https://es6.ruanyifeng.com/?search=filter&x=0&y=0#README

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值