本章分享的ES6语法核心常用知识点开发必会 - 面试必备;
针对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
还有 字符串,布尔值,函数的解构赋值方式,我就不一一列举出来了,
总结
字符串,正则,数组,对象,函数的扩展 + 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
函数的语法糖。async
和await
,比起星号和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;