JS入门3

67 阅读7分钟

这节课我们来学习ES6的一些新特性

变量和常量

ES6可以书写一个代码块表示一个独立的作用域,let变量只在块域中有效,也就是局部变量。可以将代码进行有效分割,也可以避免一个变量名在多个地方使用造成冲突。我们建议局部变量都用let定义
常量则用const定义(es6之前没有const,常量用全大写字母表示)。

{
    let count = 0
    count++
    const  BASE_URL = 'https://api.example.com'
}

严格检查模式

我们知道js对变量的检查并没有很严格,就算不写var或者let声明,直接给一个变量赋值,也不会报错,甚至会设置为全局变量。如果引入了多个js文件,里面设置了同名全局变量,肯定会出错呀。那么如何避免出错呢?在es6中,为了避免变量设置混乱,我们建议局部变量都使用let来声明,其次可以使用严格检查模式,也就是在js文件第一行写一个"ues strict",这样如果变量设置得不对,浏览器就会报错。

    <script>
        "use strict"
        i = 0
        console.log(i)
    </script>

image.png

模板字符串

在js入门1中提到过了,使用反引号,${}引用

const str1 = 'abc'
const str2 = `def${str1}
这也是字符串的内容`
console.log(str2) // "def abc\n这也是字符串的内容"

map和set

在es6中可以使用map和set,用法与java类似。map可以储存键值对(二维数组),set(一维数组)无序不重复。如果要遍历,使用for of

        // map
        var map = new Map([['tom', 100], ['jerry', 90], ['lily', 80]]); // 初始化,二维数组
        var name = map.get('tom'); // 通过key获取value
        console.log(name); // 100
        map.set('jack', 70); // 新增或修改
        map.delete('jerry'); // 删除
        for(let x of map) { // 遍历
            console.log(x); 
        }
        
        // set
        var set = new Set([1, 2, 3, 1, 1]); // 初始化,一维数组,set可以去重
        set.add(4); // 新增
        set.delete(2); // 删除
        console.log(set); // Set(3) {1, 3, 4}
        set.has(1); // 判断是否存在 true
        for(let x of set) { // 遍历
            console.log(x); 
        }

解构赋值

我们给数组或对象里的元素赋值时需要一个个赋值,很麻烦,es6提供了一种简写的赋值方法,可以迅速完成赋值操作

// 数组可以进行解构赋值
const [a, b, c] = [1, 2, 3]
console.log(a, b, c)
// 对象也可以进行解构赋值,也可以修改变量名和设置剩余项
const { username, age: userAge, ...otherInfo } = {
    username: '显子',
    age: 25,
    gender: 'famale',
    catrgory: 'user'
}
console.log(username, userAge, otherInfo) // "显子" 25 { gender: "famale", catrgory: "user" }

数组和对象的拓展

拓展运算符

如果想给把旧数组的数值快速赋值给新数组,可以使用拓展运算符...,同时也适用于对象,但是一般会先写新对象本身的属性,把复制的功能放在最后面。

// 1. 扩展运算符 
const arr1 = [1, 2, 3]
const arr2 = [4, 5, 6]
const arr3 = [0, ...arr1, ...arr2, 7, 8, 9]
console.log(arr3) // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
const obj1 = {
    a: 1
}
const obj2 = {
    b: 2
}
const obj3 = {
    name: '显子',
    ...obj1,
    ...obj2
}
console.log(obj3) // { name: '显子', a: 1, b: 2 }

数组方法

伪数组无法使用数组的方法,使用Array.from()函数可以使伪数组拥有数组的方法

function fn() {
    Array.from(arguments).forEach(function (item) {
        console.log(item)
    })
    fn(1, 2, 3)
}

对象方法

我们也可以使用object.assign()做对象的浅拷贝和功能的合并

const objA = {
    name: '显子',
    age: 25
}
const objB = {
    gender: 'famale'
}
const objC = Object.assign({}, objA, objB)
console.log(objA, objB, objC) // { name: '显子', age: 25 } { gender: 'famale' } { name: '显子', age: 25, gender: 'famale' }

Class

一些语法糖,使js更接近传统的面向对象语法

class A { // 定义类
    constructor(name, age) {
        this.name = name
        this.age = age
    }
    introduce() { 
        console.log(`我的名字是${this.name}, 我的年龄是${this.age}`)
    }
}
const a1 = new A('显子', 25) // 对象
console.log(a1) // A { name: '显子', age: 25 }
a1.introduce() // 我的名字是显子, 我的年龄是25

class B extends A { // 继承
    constructor(name, age, gender) {
        super(name, age)
        this.gender = gender
    }
    sayHello() {
        console.log('你好我是' + this.name)
    }
}
const b1 = new B('小明', 18, '男') 
console.log(b1) // B { name: '小明', age: 18, gender: '男' }
b1.sayHello() // 你好我是小明
b1.introduce() // 我的名字是小明, 我的年龄是18

箭头函数

简写的函数,参数 => 返回值,其中比较复杂的返回值也可以用大括号,但要加return

const getSum1 = n => n + 3
const getSum2 = (n, m, ...other) => console.log(n, m, other)
getSum2(1, 2, 3, 4, 5) // 1 2 [3, 4, 5]
const getResult = arr => {
    let sum = 0
    arr.forEach(item => sum += item)
    return sum
}
console.log(getResult([1, 2, 3, 4, 5])) // 15

Promise Async 异步处理

首先我们来理解一下什么是同步和异步。同步就是收到请求后立即执行,中间按无需等待;异步就是收到请求后过一会再执行,会在同步任务都执行完后再触发。比如console.log()就是同步,setTimeout()就属于异步。

console.log('任务1...同步')
console.log('任务2...同步')
setTimeout(() => {
    console.log('任务3...异步')
    
})
console.log('任务4...同步')
// 实际执行顺序是1,2,4,3

如果任务4需要根据任务3的结果来执行,这怎么办呢?比较简单的情况是把任务4加入timeout,放在任务3 的后面,但是如果任务4后面又有异步任务该怎么办呢?这时候就要用到promise了。promise可以将复杂的嵌套异步结构书写为类似于同步的结构,方便阅读。

// promise会标记设计的代码功能中的异步任务的处理结果,并将通过回调返还到p1
const p1 = new Promise((resolve, reject) => { // 两个参数:成功和失败
    // resolve('任务1成功得到的数据') // 表示成功,会触发then里的data
    reject('任务1失败的信息1') // 表示失败,会触发then里的err,如果找不到就会触发catch
})

p1.then(data => {
    console.log(data)
    // 如果后续还有异步任务,就写在return后面
    // 这个return会被作为p1.then的返回值,并作为下一个then的参数
    return new Promise((resolve, reject) => {
        // resolve('任务2成功得到的数据')
    })
}, err => {
    console.log('任务1失败了')
    // 如果希望后续都失败,返回一个新的promise或者抛出异常
    throw new Error('任务1失败了')
})
.then(data => {
    console.log(data)
    // 如果后续还有异步任务,就写在return后面
    // 这个return会被作为p1.then.then的返回值
    return new Promise((resolve, reject) => {
        resolve('任务3成功得到的数据')
    })
}, err => {
    console.log('任务2失败了')
})   

// 由于上面的每个then都写了err,可以分别处理每个任务失败的情况,所以不会触发catch
.catch(err => {
    console.log(err)
})

async其实是用来进行异步处理的语法糖,可以简化但不能完全替代promise,将异步的格式几乎完全变成同步

// Async
// 步骤1:准备一个返回promise对象的函数
function asyncTask() {
    return new Promise((resolve, reject) => {
        // 假装有一些关键代码  
        const isSuccess = true
        if (isSuccess) {
            resolve('任务2成功的处理结果')
        } else {
            resolve('任务2失败的处理结果')
        }
    })
}
// 步骤2:未使用await的函数添加async
async function main() {
    console.log('任务1')
    const data = await asyncTask()
    console.log(data)
    console.log('任务3')
}
main() // 输出:任务1 任务2成功的处理结果 任务3

Proxy 代理对象

有个情境:当频繁数据变更时,如何在页面上更新?这时候就可以通过代理的方式来处理

// 如果变更一次数据就更新一次显然是不可行的,会有很多冗余代码
const obj = { name: '显子', age: 25 }
const container = document.getElementById('container')
container.textContent = obj.name //更新页面 显子
obj.name = '小明'
container.textContent = obj.name // 更新页面 小明

对代理对象做修改时,每一次不仅会修改原始数据本身,还会执行在set中设置的功能

const obj = { name: '显子', age: 25 }
const container = document.getElementById('container')
const p1 = new Proxy(obj, { // 配置项,属性的获取设置删除
    get(target, property, receiver) {
        return obj[property]
    },
    set(target, property, value) {
        obj[property] = value
        container.textContent = obj.name //更新页面 显子
    }
})
p1.name = 'rose' // 更新数据和页面 rose

Module 模块

把不同类型的功能放到不同的文件里面,就称为模块,分为EMS和CommonJS(只能在node.js中使用)

export const aTitle = 'a模块的标题'
// 导出单个函数
export function aFn() {
    console.log('a模块的方法')
}
// 默认导出
export default {
    name: 'a模块'
}

// 导出单个属性或函数
export const bTitle = 'b模块的标题'
export function bFn() {
    console.log('b模块的方法')
}
// 默认导出
export default {
    name: 'b模块'
}

// ESM 需要在script标签中设置type="module"
// 导入模块的默认导出
import moudleA from './a.js'
import moudleB from './b.js'
// 导出模块的某个属性或函数
import { aTitle, aFn } from './a.js'
import { bTitle, bFn } from './b.js'
console.log(moudleA) // { name: 'a模块' }
console.log(moudleB) // { name: 'b模块' }
console.log(aTitle, aFn, bTitle, bFn) // a模块的标题 function aFn() {...} b模块的标题 function bFn() {...}
module.exports = {
    a: 1,
    b: 2,
    c: 3
}
// 等价于
exports.a = 1
exports.b = 2
exports.c = 3

//CommonJS 不能在浏览器运行
const moudleC = require('./c') // 不用写后缀,默认.js
console.log(moudleC) // { a: 1, b: 2, c: 3 }