ES6 基础学习笔记

152 阅读8分钟

简介

说到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、新增字符串方法

1includes() // 返回boolean,表示是否找到字符串

2startsWith() // 返回boolean,是否在头部

3endsWith() // 返回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 知识点,希望可以帮助到大家!