简介
说到ES6,有人会想到ES6 蔚来吗?哈哈... 算了,不废话了,进入正题吧!
ES6 全名为 ECMAScript 6.0,也叫 ECMAScript 2015,它是2015年6月发布。此次标准的更新大幅增加了新语法及新特性。利用ES6 的新语法及新特性能让开发者在解决实际工作需求上变得更加简单,代码也会变得更加简洁和优雅,所以掌握ES6 也是学习JS 基础的必经之路。
1、let 及 const
1.1、let 命令
var
1、变量提升
let
1、不存在变量提升
2、块级作用域
3、禁止重复声明
ES5定义作用域:
1、全局作用域
2、函数作用域
ES6定义作用域:
1、全局作用域
2、函数作用域
3、块级作用域,用 {} 表示(新增的)
块级作用域用于声明作用域之外无法访问的变量。主要有两种:
1、函数内部块级作用域
2、在字符{}之间的区域
// 1、函数内部块级作用域
function test(){
let a = 20
}
test()
console.log(a)
// a is not defined
// 2、在字符{}之间的区域
{
let a = 10
}
console.log(a)
// a is not defined
1.2 const 命令
const
1、声明的是常量,不能重新赋值,不能重新声明,必须初始化赋值
2、声明的对象,可以修改对象的属性值,不能修改已经声明的对象
3、如果想让对象属性不能修改,可以借助Object.freeze 函数冻结对象(不能冻结多层对象)
4、符合函数试编程,运算是不能改变值的,只能新建值,有利于分布式运算
const obj = {
name: '张三',
age: 20
}
Object.freeze(obj)
obj.name = '李四'
console.log(obj)
// {name: '张三', age: 20}
// 多层嵌套obj
const obj = {
name: '张三',
age: 20,
family: {
father: {
name: '张大',
age: 48
}
}
}
Object.freeze(obj)
obj.family.father.age = 50
console.log(obj)
// 你可以尝试一下打印输出一下,你会发现居然年龄变了
这不就会有需要解决多层对象的冻结问题的场景,那我这里顺便来个福利:
// 深层冻结函数
function deepFreeze(obj){
Object.freeze(obj)
for(let key in obj){
if(obj.hasOwnProperty(key) && typeof obj[key] === 'object') {
deepFreeze(obj[key])
}
}
}
// 看到该函数,也是很好懂的,很清楚,也就是递归了 Object.freeze() 函数
1.3、临时死区
let 和 const 都是块级标识符,所以let 和 const 都是在当前代码块内有效,常量不存在变量提升的情况。但是通过let 和 const 声明的常量,会放在临时死区。
{
console.log(typeof a)
let a = 10
}
即使通过安全的typeof 操作符也会报错,原因是JS引擎在扫描代码变量时,要么会把变量提升到顶部,要么会把变量放在临时死区。这里通过let 来声明 a 变量,会把 a 变量放在临时死区,所以声明之前打印就会报错。
这个概念我个人总感觉陌生,不知道大家啥感受?不管咋样,都好好理解记忆一下!
1.4、循环中的 let 和 const
let arr = []
for(var i=0;i<5;i++){
arr.push(function(){
console.log(i)
})
}
arr.forEach(item=>item())
// 5 5 5 5 5
我们是想得到 0 1 2 3 4 的,但是却得到 5 个 5,这个就是因为var 声明在循环中作用域共用,并且会把 i 保存在全局作用域中。
其实要想解决该问题,我们在ES5里可以用到闭包:
// 闭包改造for循环
for(var i=0;i<5;i++){
(function(i){
arr.push(function(){
console.log(i)
})
})(i)
}
但是利用ES6 中 let 和 const 的块级作用域可以让写法变得更简单:
// 不能使用 const,可以使用 let
for(let i=0;i<5;i++){
arr.push(function(){
console.log(i)
})
}
在 for-in 或 for-of 循环中使用 const 时, 方法与 let 一致:
let obj = {
name: '张三',
age: 20
}
for(const i in obj){
console.log(i) // name age
}
let arr = ['张三', '李四']
for(const value of arr){
console.log(value) // 张三 李四
}
2、解构赋值
2.1、数组的结构
let [a,b,c] = [10,20,30]
console.log(a) // 10
console.log(b) // 20
console.log(c) // 30
//
let [d,e] = [40,50,60]
console.log(d,e) // 40 50
//
let [,,f] = [70,80,90]
console.log(f) // 90
//
let [g,h,i] = [100,110]
console.log(i) // undefined
// 拓展运算符
let [j,...k] = [120,130,140]
console.log(k) // [130,140]
//
let m = 150;
let n = 160;
[m, n]=[n, m]
console.log(m, n) // 160 150
2.2、对象的结构
let obj = {
name: 'zhangsan',
age: 20,
height: 180
}
let {name,age,height} = obj
console.log(name,age,height) // zhangsan 20 180
解构多层对象
let P = {
name: 'zhangsan',
age: 20,
family: {
father: 'zhangbaba',
mother: 'limama'
}
}
let {name,age,family:{father,mother}} = P
console.log(name, father) // zhangsan zhangbaba
自定义变量名称
let obj = {
name: 'zhangsan',
age: 20
}
let {name:myname,age:myage} = obj
console.log(myname,myage) // zhangsan 20
2.3、解构的默认值和参数的结构
数组结构赋值,对象结构赋值都可以添加默认参数。
// 对象
let {name, age, height=185} = obj
// 数组
let [a, b, c=30] = [10, 20]
函数参数中也可以使用解构,同样参数解构也可以给默认参数:
function fn({name,age}={age: 21}){
console.log(name,age)
}
let obj = {
name: 'zhangsan'
}
fn(obj) // zhangsan undefined
fn() // undefined 21
3、字符串扩展
3.1、Unicode 支持
Unicode 的目标是为世界上每一个字符提供唯一标识符,唯一标识符称为码位或码点。
这些码位是用于表示字符的,又称为字符编码。
JS 里边是 可以通过 \uxxxx 的形式来表示一个字符。(有兴趣的自己可以查查自己名字相对应的字符编码)
这种语法限于码点在\u0000~\uFFFF之间的字符。超出这个范围的字符必须用两个字节来表示。
"\uD842\uDFB7"
// "𠮷"
"\u20BB7"
// " 7"
上面代码表示,如果直接在\u后面跟上超过0xFFFF的数值(比如\u20BB7),JavaScript 会理解成\u20BB+7。由于\u20BB是一个不可打印字符,所以只会显示一个空格,后面跟着一个7。
ES6 对这一点做出了改进,只要将码点放入大括号,就能正确解读该字符。
"\u{20BB7}"
// "𠮷"
"\u{41}\u{42}\u{43}"
// "ABC"
let hello = 123;
hell\u{6F} // 123
'\u{1F680}' === '\uD83D\uDE80'
// true
上面代码中,最后一个例子表明,大括号表示法与四字节的 UTF-16 编码是等价的。
有了这种表示法之后,JavaScript 共有 6 种方法可以表示一个字符。
'\z' === 'z' // true
'\172' === 'z' // true
'\x7A' === 'z' // true
'\u007A' === 'z' // true
'\u{7A}' === 'z' // true
3.2、新增字符串方法
1、includes() // 返回boolean,表示是否找到字符串
2、startsWith() // 返回boolean,是否在头部
3、endsWith() // 返回boolean,是否在结尾
4、repeat() // 返回新的字符串将源字符串循环指定次数
let res = 'ab'.repeat(3)
console.log(res) // ababab
3.3、模板字符串
let str = `姓名是${obj.name},年龄是${obj.age}`
//
let obj = {
name: 'zhangsan',
age: 20,
checked: true
}
let str2 = `${obj.checked?`<input type="checkbox" checked />`:`<input type="checkbox" />`}
<span>姓名:${obj.name}</span>
<span>年龄:${obj.age}</span>`
console.log(str2)
// <input type="checkbox" checked />
// <span>姓名:zhangsan</span>
// <span>年龄:20</span>
4、Symbol
数据类型:Undefined、Null、Boolean、String、Number、Object、Symbol
Symbol表示唯一的值。
// 直接创建
let s1 = Symbol();
// 传入字符串创建
let s2 = Symbol('mySymbol')
每个Symbol 都是独一无二的,类型是symbol。
let s1 = Symbol('mySymbol');
let s2 = Symbol('mySymbol');
console.log(s1===s2) // false
console.log(typeof s1) // symbol
目前前端项目都会采用模块化构建,为了防止对象属性名被覆盖,可以通过symbol来定义属性名。
// a.js
const NAME = Symbol('name')
let obj = {
[NAME]: 'zhangsan',
age: 20
}
export default obj;
// b.js
import Obj from './a.js'
const NAME = Symbol('name');
Obj[NAME] = 'lisi'
console.log(Obj)
// {age:20,Symbol():'zhangsan',Symbol():'lisi'}
利用symbol作为属性名,属性名不会被Object.key()、Object.getOwnPropertyNames()、for...in 循环或者返回。
let obj = {
[Symbol()]: 'zhangsan',
age: 20,
height: 180
}
for(let key in obj){
console.log(key) // age height
}
let keys = Object.keys(obj)
console.log(keys) // ["age", "height"]
console.log(Object.getOwnPropertyNames(obj)) // ["age", "height"]
//
console.log(Object.getOwnPropertySymbols(obj)) // [Symbol()]
console.log(Reflect.ownKeys(obj)) // ["age", "height", Symbol()]
同样可以在 “类” 里利用symbol来定义私有属性和方法。
let P = (function(){
let name = Symbol('name')
class P{
constructor(yourName){
this[name] = yourName
}
sayName(){
console.log(this[name])
}
}
return P
})()
let zs = new P('zhangsan')
console.log(zs[Symbol('name')]) // undefined
zs.sayName() // zhangsan
5、函数
5.1、函数形参的默认值
function fn(name='zhangsan',age=20,cb=function(){}){
console.log(name, age, cb)
cb()
}
fn('lisi')
// lisi 20 ƒ (){}
fn(undefined, 30, function(){
console.log('callback...')
})
// zhangsan 30 ƒ (){
// console.log('callback...')
//}
// callback...
fn(undefined, 0)
// zhangsan 0 ƒ (){}
5.2、函数形参不定参数
记得在ES5 里边会通过隐藏参数 arguments 来获取,arguments[0]
在ES6中提供rest剩余参数来处理不定参问题,用 “...” 表示。
function fn(...arg) {
console.log(arg) // ["zhangsan", 20]
console.log(arguments)
}
fn('zhangsan',20)
每个函数只能声明一个剩余参数,且剩余参数必须在参数的末尾。
剩余参数对arguments没有影响。
5.3、箭头函数
let fn = arg => arg
console.log(fn('zhangsan')) // zhangsan
let fn2 = (arg1,arg2) => arg1 + arg2
console.log(fn2(1,2)) // 3
let fn3 = arg => {
return arg+2
}
console.log(fn3(1)) // 3
有一点需要注意,就是{}是函数的,不是对象的,如果是对象的情况下,加个括号A(),或者return 对象:
let fn4 = () => ({
name: 'zhangsan',
age: 20
})
console.log(fn4())
箭头函数还可以解决一些this指向问题,箭头函数没有this绑定。
箭头函数中的this会指向最近的上层this,所以这里指向是Window。
let obj = {
id: 1,
fn5: function(){
console.log(this.id)
},
fn6: ()=>{
console.log(this.id)
}
}
console.log(obj.fn5()) // 1
console.log(obj.fn6()) // undefined
箭头函数没有隐藏参数arguments 的绑定:
let fn7 = (arg1, arg2) => {
console.log(arguments) // arguments is not defined
return arg1 + arg2
}
fn7()
6、类 class
6.1、类的基本语法
ES6 提供了class 关键字来定义类,在写法上变得更加简洁,语义化更强。
class Person{
constructor(name){
this.name = name
this.age = 20
this.work = 'chengxuyuan'
}
fn(){
console.log(`${this.name}现在${this.age}岁`)
console.log(this) // Person {name: "zhangsan", age: 20}
}
get job(){
return this.work
}
set job(newValue){
this.work = newValue
}
}
let zs = new Person('zhangsan')
console.log(zs.name) // zhangsan
zs.fn() // zhangsan现在20岁
console.log(typeof Person) // function
console.log(typeof zs) // object
console.log(zs.job) // chengxuyuan
zs.job = 'xiaoshou'
console.log(zs.job) // xiaoshou
6.2、静态成员
ES5中的静态成员:
function Person(name){
this.name = name
this.age = 20
}
Person.num = 10 // 静态属性
Person.fn = function () { // 静态方法
console.log('fn...')
}
ES6 使用关键字 static 声明静态成员:
class Person{
static num = 20 // 静态属性
constructor(name){
this.name = name
this.age = 20
}
static fn(){ // 静态方法
console.log('fn...')
}
}
6.3、类的继承
ES5 中通过call、apply、bind来实现构造函数的继承。
function Dad(name){
this.lastName = 'zhang'
this.firstName = name
this.fn = function(){
console.log(this.lastName + this.firstName)
}
}
function Son(name){
// Dad.call(this, name)
// Dad.apply(this, [name])
console.log(this, name) // Son{} "san"
// Dad.bind(this)(name)
Dad.bind(this, name)()
}
let zs = new Son('san')
console.log(zs.fn()) // zhangsan
ES6 使用 extends 关键字来实现类的继承:
class Dad{
constructor(name){
this.lastName = 'zhang'
this.firstName = name
}
fn(){
console.log(this.lastName + this.firstName)
}
}
class Son extends Dad{
constructor(name){
super(name)
}
}
let zs = new Son('san')
zs.fn() // zhangsan
在继承中需要注意,需要调用super() 方法继承父类的构造函数。
super() 在使用过程中需要注意一下两点:
1、在访问this之前,一定要调用super()
2、如果不调用super(),可以让子类构造函数返还一个对象。
同样在继承中静态成员也是可以被继承的,因为静态成员属于类自身,所以它的继承也是类本身的继承,实例化对象不能继承到静态成员。
class Dad{
static age = 40
constructor(name){
this.lastName = 'zhang'
this.firstName = name
}
fn(){
console.log(this.lastName + this.firstName)
}
}
class Son extends Dad{
constructor(name){
super(name)
}
}
console.log(Son.age) // 40
let zs = new Son('san')
zs.fn() // zhangsan
console.log(zs.age) // undefined
上述代码可以看出,静态属性可以被子类所继承,但是如果是子类的实例化对象则不能被继承。
7、异步编程
7.1、Promise 基本语法
Promise 是系统中预定义的类,通过实例化可以得到Promise 对象。
promise 对象会有三种状态,分别是 pending、resolved、rejected。
每一个 Promise 对象都会有一个then 方法,then 方法里会接收两个参数(可选),第一个参数成功回调,第二个参数是错误回调。
每一个 Promise 也提供 catch 方法来捕捉 reject 错误。
使用catch 的好处是如果有多个then,会把最先报错的错误抛出到catch里边。写法加单。
调用then函数之后会有三种返还值:
1、then里没有返还值,会默认返还一个Promise对象。
2、then里如果有返还值,会将返还值包装成一个Promise对象返还。
3、如果返还的是Promise对象,then函数也会直接返还原本的Promise对象。
7.2、Promise 处理异步问题
由于每个then方法返还一个Promise对象,所以就可以实现then 的链式调用,从而解决了回调地狱的问题。
ES7 新增了 async 和 await,异步代码同步化,更加易懂,提高可读性和可维护性。
7.3、Promise 里的其他方法
可以通过Promise.resolve 来创建一个resolved状态的Promise 对象,也可以通过Promise.reject 来创建一个rejected 状态的Promise对象。
也可以通过Promise.all 来执行多个Promise 对象,接收的参数是一个数组,当所有Promise对象都执行成功之后才会拿到执行结果的数组。
Promise.race() 方法会返还最先执行的结果,无论成功还是失败。
8、模块化
使用ES6标准中的模块化工具,必须在script 标签里声明 type="module"
<script type="module"></script>
8.1、导入导出基本使用
在ES6标准中导出用关键字 export 或者 export default;导入用 import XXX from 'xxx'
export 导出是可以导出多个的,然而export default 在每个模块中只能导出一个。
通过export 导出的需要通过大括号结构变量,然而export default 导出的可以自定义变量来接收参数。
8.2、导入导出变式写法
在导出的过程中可以导出多个,并且可以通过as 来默认导出。
// a 模块
let obj = {
name: 'zhangsan'
}
export let a = 1
export let b = 2
export {
obj as default
}
// index.html
<script type="module">
import A,{a,b} from './a.js'
console.log(A, a, b) // {name: 'zhangsan'} 1 2
</script>
在导入导出时都可以通过 as 关键字来起别名。
// A.js文件
const arr = [1, 2, 3, 4, 5]
const str = 'str'
export { arr as numbers, str }
// B.js文件
import {numbers, str as string} from "./A.js"
console.log(numbers, string) // [1, 2, 3, 4, 5] 'str'
有时候就是想所有模块都导入,不想把模块都写一遍在 {} 中,那么可以 整体加载。
// A.js文件
const arr = [1, 2, 3, 4, 5]
const str = 'str'
export {arr, str}
// B.js文件
// 使用 * 号导入所有模块,然后用 as 重命名,注意这里没有 {} 了
import * as modules from "./A.js"
console.log(modules.arr, modules.str)
8.3、按需导入
条件加载
if (condition) {
import('moduleA').then(...);
} else {
import('moduleB').then(...);
}
按需加载
document.onclick = function(){
import("./a.js").then(res=>{
console.log(res)
})
}
// async await
document.onclick = async function(){
let res = await import('./a.js')
console.log(res)
}
9、Set 和 Map 集合
9.1、Set 集合
Set集合,是一种无重复元素的列表。
let set = new Set()
// 增加元素
set.add(1)
set.add(2)
console.log(set) // Set(2) {1, 2}
// 元素数量
console.log(set.size) // 2
// 删除某个元素
set.delete(2)
console.log(set) // Set(1) {1}
// 删除所有元素
set.clear()
console.log(set) // Set(0) {}
// 判断是否有某个元素
console.log(set.has(3)) // false
可通过Set 不可重复的属性来做去重的处理。
let arr = [1,2,3,3,4,5,2,6]
// 将数组转换成 Set 集合
let set = new Set(arr)
console.log(set) // Set(6) {1, 2, 3, 4, 5, 6}
// 将集合转换成数组
let newArr = [...set]
console.log(newArr) // [1, 2, 3, 4, 5, 6]
9.2、Map 集合
Map 集合是键值对的集合。
let map = new Map()
// 添加键名和键值
map.set('name', 'zhangsan')
map.set('age', 20)
console.log(map) // Map(2) {"name" => "zhangsan", "age" => 20}
// 获取某个属性名
console.log(map.get('age')) // 20
// 判断是否含有某个元素
console.log(map.has('name')) // true
// 删除
map.delete('name')
console.log(map) // Map(1) {"age" => 20}
// 删除所有
map.clear()
console.log(map) // Map(0) {}
小结
ES6 对于JS 来言,是一次大的变革,目前JS开发中已经离不开ES6了,也是JS开发的基石了。以上是我总结的常用的基础的ES6 知识点,希望可以帮助到大家!