ES6 入门汇总

前言 ES6 核心知识点学习整理记录

目录

本章分享的ES6语法核心常用知识点开发必会 - 面试必备;

  • let , const
  • 解构赋值
  • 字符串,正则,数组,对象,函数的扩展 + Symbol
  • Set , Map 数据结构、
  • Proxy 代理 / 拦截 , Reflect
  • Generator 函数
  • async 函数
  • Promise对象
  • class 类
  • module 模块儿

针对ES6的知识点做一个简要的概述与实践,常用核心的知识点会讲述的稍微细一点,其它的都是略带过,我也是奋斗在学习中的小白,望各位大佬多多建议。

let , const
ES6新增了let,const 命令,用来声明变量。它的用法类似于var;

let
let声明一个块级变量
1,不存在变量提升 n = 10;let n ; // 报错,告诉你let声明要放在前边
2,只在声明的代码块儿内有效 {let n = 1; var m = 2} // n is not defined , m // 2
3,不允许重复声明 {let n = 1; let n;} // 控制台会报错,不让重复声明

const
const声明一个只读的常量。一旦声明,常量的值就不能改变;
1,常量声明之后不能修改 const n = 10 ; n = 101; // 控制台会发生报错告诉你不能修改常量
2,同样不存在变量提升 //可参考let
3,不能重复声明 //可参考let
4,如果声明一个数组或者对象,则能操作写值,不能直接赋值,看例子

const arr = [];
arr.push(1) // [1]
arr[1] = 2 // [1,2]
arr = [1,2,3] // 控制台会报错, 告诉你常量不能赋值

解构赋值

官方解释:ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。

粗鲁的讲:就是解开变量的结构去给对应的结构赋值,哈哈哈,通过一个简单的例子来表达一下

var a = 1;
var b = 2;
var c = 3;
// ES5简化一点
var a = 1, b = 2, c = 3;
// ES6写成下面这样
var [a, b, c] = [1, 2, 3];

是不是好容易,这就是模式匹配,只要两边的结构模式一样,左边的变量就会赋右边对应的值,我们再来看几种情况

数组的解构赋值

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

let [ , , c] = ["a", "b", "c"];
c // "c"

let [x, , y] = [1, 2, 3];
x // 1
y // 3

let [x, ...arr] = [1, 2, 3, 4];
x  // 1
arr // [2, 3, 4]  ...  我们后边要说的 展开运算符 

let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []

对象的解构赋值

var { a, b} = { a: "aaa", b: "bbb" };
a // aaa
b // bbb

var { b, a } = { a: "aaa", b: "bbb" };
a // "aaa"
b // "bbb"

var { c } = { a: "aaa", b: "bbb" };
c // undefined

// 看一个扩展,上边的 var {a,b} = { a: "aaa", b: "bbb" } ; 实际上就是下边这种形式
var { a: a, b: b } = { a: "aaa", b: "bbb" };
a // aaa 
b // bbb 

//我们换一种方式
var { a: aa, b: bb } = { a: "aaa", b: "bbb" };
a // a is not defined
b // a is not defined
// --- 分割线 --- 
aa // aaa
bb // bbb

// 可看出来真正的结果还是 key:value 的value,并不是 key

还有 字符串,布尔值,函数的解构赋值方式,我就不一一列举出来了,

总结

  • 简化变量声明,快捷,方便
  • 简化数组,对象操作
  • 函数参数默认赋值操作
  • 等号左边模式与右边值不匹配则默认输出 undefined 或者 xx is not defined

字符串,正则,数组,对象,函数的扩展 + Symbol

说到这些类型的扩展就更是让人心旷神怡了,为什么这样说,因为看过官方文档的小伙伴们肯定都惊呆了,各种神操作,各种简化,各种爽,有一句话说的好,一直加班一直爽,在这里我就想说:一直扩展一直爽。

数组的扩展

Array.from()
Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括ES6新增的数据结构Set和Map)。

1,下面是一个类似数组的对象,Array.from将它转为真正的数组。

var arr = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    length: 3
};
// ES5的写法
var arr1 = [].slice.call(arr); // ['a', 'b', 'c']
// ES6的写法
let arr2 = Array.from(arr); // ['a', 'b', 'c']

再来看一个例子

// Map 对象 ES6 新增后边会讲到
var map = new Map()
map // Map(0) {}
Array.from(map) // []

// Set 对象 ES6 新增后边会讲到
var set = new Set()
set // Set(0) {}[[Entries]]No propertiessize: (...)__proto__: Set
Array.from(set) // []

2, Array.from() 还提供了类似 .map 的回调函数

var arr = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    length: 3
};
let arr = Array.from(arr,(i,n) => {
   // 回调函数类似于 arr.map( () => { ... } )
});

Array.of()
Array.of方法用于将一组字符串值,转换为数组

Array.of(1, 2, 3) // [1,2,3]
Array.of(1) // [1]
Array.of(1).length // 1

entries(),keys() ,values()
这三个常用于对数组实例的遍历。可以用 for…of 循环遍历

for (let index of ['a', 'b'].keys()) {
  console.log(index);
}
// 0
// 1

keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。

展开符 …

let arr = [1,2,3,4]  
console.log(...arr) // 1 2 3 4 
//合并数组 
let arr2 = [5]
let concat = [...arr,...arr2] // (5) [1, 2, 3, 4, 5] 

//其实主要就是一个数组展开运算符  

函数的扩展

函数参数默认值

// 引用官方的样例
function log(x, y = 'World') {
  console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello

作用域

// 引用官方的样例
var x = 1;
function f(x, y = x) {
  console.log(y);
}
f(2) // 2
// 调用函数 f 的时候,变量x有自己的独立作用域
// 紧跟着 y = x 
// 然后打印 y  输出 2

再看另外一种

let x = 1;
function f(y = x) {
  let x = 2;
  console.log(y);
}
f() // 1

// 全局声明 x 
// 函数 f 给 变量 y 默认赋值 x , 此时 x = 1;
// 函数 f 体内声明局部变量 x
// 打印 y , y 等于默认赋值 x , 固 输出 1
// 如果全局 x 没有声明呢, 那么就会报错

箭头函数

相信用过 vue / react 框架的应该都用到了箭头函数这个概念吧。通过一个简单的例子来认识一下

var f = () => 5;
// 等同于
var f = function () { return 5 };
//--------
var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
  return num1 + num2;
};

看得出来,其实是对ES5函数的一种简写方式,让语法更简介,更明了,再通过一个例子来看一下

let getNumber= num => ({ num: num, name: "tom" });
//转换成 es5 就是
var getNumber = function (num) {
	return {
		 num: num, 
		 name: "tom" 
	}
}
//注意,如果需要直接返回一个对象,需要用()括号括起来,否则会报错

通过几个简单的例子就可以明白ES5函数与ES6函数使用的区别了,那咋这里呢,其实ES6 的使用还是有一些需要注意的地方的,
1,函数体内的this对象,指向定义时所在的对象,而不是使用时所在的对象。其实箭头函数是没有 this 的概念的,所以在箭头函数中 this 的指向是不会变的。
2,不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
3,不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest (es6 新增)

对象的扩展

ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。

变量

const x = 'x';
const y = {x};
y // {x: "x"}

// 等同于
const y = {x: x};

函数

function f(x, y) {
  return {x, y};
}
// 等同于
function f(x, y) {
  return {x: x, y: y};
}
f(1, 2) // Object {x: 1, y: 2}

属性的遍历方式

ES6 一共有 5 种方法可以遍历对象的属性

// 常用
for...in
// for...in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。
Object.keys(obj)
// Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。

// 不常用的
Object.getOwnPropertyNames(obj)
//Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。
Object.getOwnPropertySymbols(obj)
//Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有 Symbol 属性的键名。
Reflect.ownKeys(obj)
//Reflect.ownKeys返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。

Set , Map 数据结构

Set
ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值,先来认识它一下

const s = new Set();
s
Set(0) {}
[[Entries]]
No properties
size: (...)
__proto__: Set

add 方法,用于添加一个元素

s.add(1)
Set(1) {1}
s
Set(1) {1}[[Entries]] 
0: 1 
size: (...)
__proto__: Set
// 可以看到 s 对象里有一条数据了 0 : 1

向 Set 加入值的时候,不会发生类型转换;

说到类似数组 or 没有重复的值,瞬间我们是不是就想到了前端经典面试题,实现一个数组去重
Set函数可以接受一个数组

const set = new Set([1, 2, 3, 4, 4]);
set // Set(4) {1, 2, 3, 4}
[[Entries]]
0: 1
1: 2
2: 3
3: 4
size: (...)
__proto__: Set

可以看到 set 返回一个类似数组的对象 , 那么如何转换成真正的数组格式呢
ES6 常见的有两种方式,上边我们都有介绍过

const set = new Set([1, 2, 3, 4, 4]);
[...set] // [1, 2, 3, 4]
Array.from(set) // [1, 2, 3, 4]

Set 实例的属性和方法

Set.prototype.constructor:构造函数,默认就是Set函数。
Set.prototype.size:返回Set实例的成员总数。
Set 实例的方法分为两大类:操作方法(用于操作数据)和遍历方法(用于遍历成员)。下面先介绍四个操作方法。

Set.prototype.add(value):添加某个值,返回 Set 结构本身。
Set.prototype.delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
Set.prototype.has(value):返回一个布尔值,表示该值是否为Set的成员。
Set.prototype.clear():清除所有成员,没有返回值。

Map

JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键,这给它的使用带来了很大的限制。

其实Map 解构跟 Set 也挺相似,我们来认识一下Map结构

const m = new Map();
m
Map(0) {} 
[[Entries]]
No properties
size: (...)
__proto__: Map

Map 数据结构的增删方法

// 引用官方的样例
const m = new Map();
const o = {p: 'Hello World'};
m.set(o, 'content')
m.get(o) // "content"

m.has(o) // true
m.delete(o) // true
m.has(o) // false

上面代码使用 Map 结构的set方法,将对象o当作m的一个键,然后又使用get方法读取这个键,接着使用delete方法删除了这个键。

Map 数据解构也可以接收一个数组作为参数,例如:

const map = new Map([
  ['name', '张三'],
  ['title', 'Author']
]);

map.size // 2
map.has('name') // true
map.get('name') // "张三"
map.has('title') // true
map.get('title') // "Author"

其实传入一个数组作为参数,背后只是执行了一次遍历,new 了多次 Map

const items = [
  ['name', '张三'],
  ['title', 'Author']
];

const map = new Map();
items.forEach(
  ([key, value]) => map.set(key, value)
);

那么Map 有什么不一样的呢 , 到底解决了什么问题,通过下边这个例子来看一下

const aa = new Map();
aa.set({obj:'obj 键值对相当key'},"123")
Map(1) {{} => "123"}
[[Entries]]
0: {Object => "123"}
key: {obj: "obj 键值对相当key"} // 重点 key
value: "123" // 重点 value
size: (...)
__proto__: Map

官方解释:

JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键,这给它的使用带来了很大的限制。

Map 解决:
为了解决这个问题,ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。

Map的实例属性和方法

Map.prototype.set(key, value) set方法设置键名key对应的键值为value,然后返回整个 Map 结构。如果key已经有值,则键值会被更新,否则就新生成该键。
Map.prototype.get(key) get方法读取key对应的键值,如果找不到key,返回undefined。

Map.prototype.has(key) has方法返回一个布尔值,表示某个键是否在当前 Map 对象之中。

Map.prototype.delete(key) delete方法删除某个键,返回true。如果删除失败,返回false。

Map.prototype.clear() clear方法清除所有成员,没有返回值。

应用场景:
Set :很显然,我们上边就说到了经典的数组去重
Map :也很显然,解决了键值对,“键” 可以多种类型方式

Proxy

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”

通过官方的一例子先来认识一下 proxy

var obj = new Proxy({}, {
  get: function (target, key, receiver) {
    console.log(`getting ${key}!`);
    return Reflect.get(target, key, receiver);
  },
  set: function (target, key, value, receiver) {
    console.log(`setting ${key}!`);
    return Reflect.set(target, key, value, receiver);
  }
});

//上面代码对一个obj对象架设了一层拦截,重定义了属性的读取(get)和设置(set)行为

obj.a = 1
// VM888:7 setting a!
obj.a // 1
++obj.a
// VM888:3 getting a!
// VM888:7 setting a!
obj.a // 2

可以发现:我们拦截了obj.a 的独写操作。

基本语法
var proxy = new Proxy(target, handler);
new Proxy() 表示生成一个Proxy实例,
target 参数表示所要拦截的目标对象,
handler 参数也是一个对象,用来定制拦截行为。
在这里访问 proxy 就相当于访问 target 目标对象,例如:

var target = {};
var handler = {};
var proxy = new Proxy(target, handler);
proxy.a = 'b';
target.a // "b"

扩展属性:Proxy 支持的拦截操作

get(target, propKey, receiver)
// 拦截对象属性的读取,比如proxy.foo和proxy['foo']。
set(target, propKey, value, receiver)
// 拦截对象属性的设置,比如proxy.foo = v或proxy['foo'] = v,返回一个布尔值。
has(target, propKey)
// 拦截propKey in proxy的操作,返回一个布尔值。
deleteProperty(target, propKey)
// 拦截delete proxy[propKey]的操作,返回一个布尔值。
ownKeys(target)
// 拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
getOwnPropertyDescriptor(target, propKey)
// 拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
defineProperty(target, propKey, propDesc)
// 拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值。
preventExtensions(target)
// 拦截Object.preventExtensions(proxy),返回一个布尔值。
getPrototypeOf(target)
// 拦截Object.getPrototypeOf(proxy),返回一个对象。
isExtensible(target)
// 拦截Object.isExtensible(proxy),返回一个布尔值。
setPrototypeOf(target, proto)
// 拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
apply(target, object, args)
// 拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。
construct(target, args)
// 拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)。

Generator 函数

Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。

我们先来认识一下 Generator 函数
1,function关键字与函数名之间有一个星号;
2,函数体内部使用yield表达式,定义不同的内部状态

Generator 函数的调用方法与普通函数略不一样,普通函数调用只是 fun() 而Generator函数只是在这个基础之上需要 .next() 也就是 fun().next() 使得指针移向下一个状态;也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。换言之,Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。

看过我 Javascript 进阶文章二的小伙伴应该知道函数柯里化,感觉有点类似,GO Javascript 进阶二

下边来看一个实际的例子

function* Gen () {
	yield 'Hi';
	yield 'Sen';
	return "HiSen";
}
var G = Gen()
G.next()
{value: "Hi", done: false}
G.next()
{value: "Sen", done: false}
G.next()
{value: "HiSen", done: true}
G.next()
{value: undefined, done: true}

结果显而易见:
变量 G 等于 函数 Gen()G.next() 执行进入下一个状态,每次返回一个对象,它的value属性就是当前yield表达式的值 ,done属性的值false,表示遍历还没有结束。依次执行G.next()返回对应对象,执行到 return 代码时返回最终结果 {value: "HiSen", done: true} 再执行返回 {value: undefined, done: true}

总结,调用 Generator 函数,返回一个遍历器对象,代表 Generator 函数的内部指针。以后,每次调用遍历器对象的next方法,就会返回一个有着value和done两个属性的对象。value属性表示当前的内部状态的值,是yield表达式后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束 。

async await

async 函数是什么?一句话,它就是 Generator函数的语法糖。asyncawait,比起星号和yield,语义更清楚了。async 表示函数里有异步操作,await 表示紧跟在后面的表达式需要等待结果(异步任务)。

基本用法:
函数前面有一个async关键字,表明该函数内部有异步操作。调用该函数时,会立即返回一个Promise对象
Promise 后边我会讲到。await 一般使用在 async 函数内部 。

通过一个简单的例子先来认识一下 async 函数

async function as () {
	return 2;
}
as() 
// Promise {: 2}

可以看到返回一个 promise 对象,并且默认 resolved 成功回调传参 2 ,可以得出结论,async函数内部return语句返回的值,会成为then方法回调函数的参数。

接下来我们来认识一下 await ,正常情况下,await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。接下来我们实践一下

function setTime () {
	return new Promise(function (resolve,reject) {
		setTimeout( 
			function () { 
			 resolve("成功") 
		},2000)
	})
}
setTime()  // 返回一个 Promise {} 对象

// await 接受一个 promise 对象 , 这里我们分离出去使用 setTime 函数。
async function as () {
	var num = 1;
	num = await setTime(); // await 后边跟一个 promise 对象
	console.log(num)
}
as() // Promise {}  2秒之后返回  "成功" 

分析:
setTime 函数返回一个 promise 对象,2秒之后成功回调 resolve("成功")
然后我们定义一个 async await 形式的函数 as ,来简单实现一下如何解决解异步编程,
1,声明变量 num 初始化 1,
2,await setTime() 表示需要等待 setTime() 的结果,才往下走
3,所以2秒后打印 成功

一个简单的 async await 函数就这样出现在大家的眼前,是不是很简单,很神奇,代码看起来就是完完全全的同步操作。声明变量,变量赋值,打印变量,但是里边却包含着异步任务。其实await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象

Promise

上边我们说到 Promise 对象,那 Promise 对象到底是个什么东西呢 ?
Promise 是异步编程的一种解决方案,比传统的解决方案回调函数更合理和更强大。ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。查看 Promise

先来了解一下它的基本用法

//创建 promise 对象
var promise=new Promise(function(resolve,reject){
   // ...some async code 
   // 根据异步的结果来决定调用 resolve 或者 reject
})

//成功与失败回调
promise.then(function(res){
   //success
},function(error){
  // error
});

在分析之前,我们先得知道,promise 的工作原理

Promise的三种状态,注意Promise在某一时刻只能处于一种状态

pending:进行中
fulfilled :执行成功
rejected :执行失败

Promise的状态改变,状态只能由pending转换为resolved或者rejected,一旦状态改变完成后将无法改变(不可逆性)

pending------》fulfilled(resolved)
pending------》rejected

好了,了解完它的工作原理之后,我们再来分析代码:在异步操作完成之后,会针对不同的返回结果调用resolve 和 reject 。

resolve和reject是两个函数,
resolve是异步操作成功时候被调用,将异步操作的返回值作为参数传递到外部;
reject是异步操作出异常时候被调用,将错误信息作为参数传递出去。

异步结果传递出去后,使用then方法来获取异步返回的结果,也就是上边看到的 promise.then(...) ,怎么样,小伙伴们清楚了嘛 ? 简单点来说就是:
1,定义一个 promise 对象,在里边做异步操作,并拿到返回结果
2,根据返回结果调用 resolve 或者 reject,来进行异步回调
3,执行对应的回调

当然了,相信很多童鞋门都在各种博客,论坛上看到了,promise 处理回调地狱这件事儿 , 其实原理也是很简单的,就是成功回调的链式调用,,简单的一个例子看一下吧。

var promise=new Promise(function(resolve,reject){
   	resolve(1)
})
promise.then(res => {
	return res;
}).then(res => 
	console.log(++res)
)
//  2 

结果显而易见了,需要 return 上一次的成功回调,接着链式调用 then 一直使用。

class

说到 class 是ES6 新增了类的说法,Class 可以通过extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。

来认识一下class 类

// 类
class parent{
}
//child 继承 parent , 也就是 子类继承父类, 可以直接控制台实践的
class child extends parent{
}

typeof parent
// "function"

这里可以看出来,typeof parent // "function" parent 还是一个函数,其实 class parent {} 就是 ES2015 function parent () {} 的简写

我们再扩展一下

// 类
class parent{
	constructor(x, y) {
	    this.x = 1;
	    this.y = 2;
	}
}
//child 继承 parent , 也就是 子类继承父类, 可以直接控制台实践的
class child extends parent{
	constructor(x, y, z) {
	    super(x, y); // 调用父类的 constructor(x, y)
	    this.z= 3;
   }
}

那么看到上边的 constructor(x,y) 应该就不难理解了,就是 function parent (x,y) { this.x=x;this.y=y;} 写过 react 项目的小伙伴应该很熟悉这段代码。

super()
super代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次super函数。

module
在这里简单介绍一下,后期我会专门写一篇关于模块儿化的文章

CommonJS: 用于Node.js环境

// 导出
module.exports = moduleA.func;
// 导入
const moduleA = require( url );

AMD: 用于Node.js环境,浏览器。异步方式家在模块 -> requirejs

// 定义模块
define( 'module', ['dep'], function(dep) {
  return exports;
} )
// 导入模块
require( ['module'], function(module) {} )

ES6模块化: 需要转化成es5运行

// 导出
export function  hello(){};
export default {};
// 导入
import { A } from url;
import B from url;

你可能感兴趣的:(ES6)