「面试梳理」ES6

209 阅读10分钟

谈谈对ES6的理解

变量var、let 和 const 区别?

关于解构赋值?

模板字符串?

扩展运算符?

rest参数?

块级作用域?

箭头函数?

Promise、generator 和async?

for...of 和 for...in 的区别?

Set?

WeakSet?

Map?

WeakMap?

ES6 模块化如何使用,开发环境如何打包?

Class类?

1. 变量let 和 const 区别?

  • 第一点: 「变量提升」方面
1. var: 「存在」变量提升,变量可在声明前调用,值为undefined
2. letconst: 「不存在」变量提升,一定要在声明后使用,否则报错(暂时性死区)
  • 第二点: 「作用域」方面
1. var:  不存在块级作用域
2. letconst: 存在块级作用域
  • 第三点: 「声明」方面
1. var:  统一作用域下,允许声明重复变量名
2. letconst: 统一作用域下,不允许声明重复变量名
  • 第四点: [声明类型]方面
1. var:  可以声明「常量」和「变量」
2. let: 声明「变量」
3. const: 声明「常量」,一旦声明,它的值不会改变

// 问题: 如何使const声明的对象属性不可变?
const 声明了「对象」,其对象属性是可以改变的,因为const声明的对象只保存了其「地址」,地址不变,不会出错。

可以使用Object.freeze()递归一层层冻结

2. 关于解构赋值?

  • 作用: 一种提取数据的模式,写法简洁,减少代码量

  • A.「数组」的解构赋值: 以元素的位置「下标」匹配

const [a, b] = ['0''1']
数据交换
console.log(a, b)
  • B.对象的解构赋值
const {a, b} = {a: '0', b: '1'}
console.log(a, b)

3.模板字符串?

  • 定义: 增强版的 「字符串」,用反引号``标识,可以在字符串中嵌入「变量」
  • 作用:
1. 支持换行,不需要转义符
2. 反引号``标识,可以嵌入 变量

4. 块级作用域?

5. 扩展运算符?

  • (...)定义:将「数组」变成以「空格」分隔的参数序列
  • (...)作用:
// 第一点: 复制(拷贝):
1. 「数组」的复制
数组中存放的是「基本数据类型」 实现的是「深拷贝」
数组中存放的是「引用数据类型」 实现的是「浅拷贝」
arr2 = [...arr1]

2.「对象」的复制
对象中存放的是「基本数据类型」 实现的是「深拷贝」
对象中存放的是「引用数据类型」 实现的是「浅拷贝」
obj2 = {...obj1}


// 第二点: 合并
1.「数组」的合并
var arr1 = [1, 2], var arr2 = [3]
var arr3 = [...arr1, ...arr2]

2. 「对象」的合并
var obj1 = {a: '11'}, var obj2 = {b: '2'}
var obj3 = {...obj1, ...obj2}

//  第二点:字符串改为数组
var test = [...'he']
结果['h', 'e']

6. rest参数?

  • 作用: 获取函数多余的参数,和扩展运算符结合使用
  • rest 参数只能放在最后一个, 不然就会报错
function(...argus) {
    console.log(argus)
}
fun(1, 2, 3)

7. 箭头函数?

面试:箭头函数和普通函数的区别?

  • 第一点: this指向的区别
// 箭头函数

1. 箭头函数中的 this 是在函数定义时就决定的,指向定义的时候,外层第一个普通函数的this,不可修改(call, bind, apply),


  • 第二点: 是否可以被new
// 箭头函数

1. 箭头函数 不可以 「new」(不能当作构造函数)
2. 箭头函数没有「prototype」

  • 第三点: 关于arguments
// 箭头函数

1. 箭头函数 没有 「arguments

8. Promise、generator 和async?

面试1: Promise有几种状态?

三种状态:
Pending: 进行中
Fulfilled: 已成功
Rejected: 已失败

面试2: Promise用于解决什么问题?

1. 「异步编程」的解决方案
2. 解决了「回调地狱」(回调地狱,维护性差,可读性差)

问题: .then 层层递进,写法不够简洁

面试3: Generator解决什么问题?

  • '*'代表generator函数
  • yield
  • .next()访问
1. 「异步」的解决方案
2. 重点解决「Promise」.then 写法复杂的问题

问题:写法还是不够简洁,所以出现async

面试3: async和await解决什么问题 ?

1. 将promise写法更加简单化, 「异步编程」最佳解决方案
2. 异步代码交给Promise 包裹
3. asyncawait真正做到了同步写法
4. 缺点:无法处理处理promise 返回的rejected对象,要借助try...catch...

面试4: async和await原理?

1. 是generator 封装的一个语法糖

面试5: 手写实现Promise、Promise.all 和 Promise.race?

  • Promise
1. Promise 是一个类
2. executor是一个执行器,同步执行,传入两个函数
3. resolve方法执行成功的结果,状态变为fulfilled
4. rejected方法执行失败原因,状态变为rejected
5. 定义then方法,两个参数,处理成功的,处理失败的
6. 加限制,因为状态不可逆if (this.status == PENDING) 
7. 处理executor 内部错误 try ... catch
8. 处理异步
9. 思考? -处理链式调用

class myPromise() {
    // 定义三种状态
    const PENDING = 'pending'
    const FULFILLED = 'fulfilled'
    const REJECTED = 'rejected' 
    
    constructor(executor) {
        this.states = PENDING // 默认的状态
        this.value = '' // 成功的结果
        this.reason = '' // 失败的原因
        this.onFulfilledCallbacks = []; // 成功回调集合
        this.onRejectedCallbacks = []; // 失败的回调集合
        
        const resolve = (value) => {
            if (this.status == PENDING) { // 状态唯一性
                this.status = FULFILLED
                this.value = value
                // 调用resolve 的时候遍历函数
                this.onFulfilledCallbacks.forEach((cb) => cb() )
            }
        }
        
        const reject = (reason) => {
            if (this.status == PENDING) { // 状态唯一性
                this.status = REJECTED
                this.reason = reason
                // 调用rejected的时候遍历
                this.onRejectedCallbacks.forEach((cb) => cb())
            }
        }
        
        <!--捕获executor错误-->
        try {
            executor(resolve, reject)
        } catch(error) {
            reject(error)
        }
    }
    
   
    
    then(onFulfilled, onRejected) {
        // onFulfilled 成功的处理函数
        // onRejected 失败的处理函数
        
        // 成功
        if (this.status === FULFILLED) {
            // 微任务中
            queueMicrotask(()=>{
                onFulfilled(this.value);
            })
        }
        // 失败
        if (this.status === REJECTED) {
            // 微任务中
            queueMicrotask(()=>{
               onRejected(this.reason);
            })
        }
        // 处理异步
        if (this.status === Pending) {
            this.onFulfilledCallbacks.push(()=>{
                queueMicrotask(()=>{
                  onFulfilled(this.value);
                })
            })
            
            this.onRejectedCallbacks.push(() => {
                queueMicrotask(()=>{
                   onRejected(this.reason);
                })
            })
        }
  }
}

const p1 = new myPromise((resolve, reject) => {})

p1.then((res) => {})
  • Promise.all
<!--方法1: for 循环-->

1. 参数,接收数组
2. 返回新的Promise实例
3. 设置一个数组用于接收所有结果(要按照顺序)
4. 设置一个计数器,用来判断循环是否结束
4. 对参数进行遍历
5. 判断元素是不是Promise实例
6. 判断结束的标识

Promise._all = function(array) {
    return new Promise((resolve, reject) => {
        let result = []
        let count = 0
        for(let i = 0; i < array.length; i++) {
            const item = array[i]
            if (item.constructor === Promise) {
                item.then(data => {
                    result[i] = data
                    count++
                    if (count === array.length) {
                        resolve(result)
                    }
                ).catch(() => {
                    reject()
                })
            } else {
                result[i] = item
                count++
            }
        }
    )
}

<!--方法2: 递归方法, 等前一个结束后,再执行下一个-->

Promise._all = function (array) {
    return new Promise((resolve, reject) => {
        let result = []
        next(0)
        function next(i) {
            let item = array[i]
            if (!item) {
                resolve(result)
                return
            }
            if (item instanceof Promise) {
                item.then(data => {
                    result.push(data)
                    next(i+1)
                }, reject)
            } else {
                result.push(item)
                next(i+1)
            }
        }
    })
}
  • Promise.race
1. 参数,接收数组
2. 返回Promise实例

Promise._race = function (array) {
    return new Promise((resolve, reject) => {
        for(let i = 0; i < array.length; i++) {
            const item = array[i]
            if (item.construct === Promise) {
                item.then(data) => {
                    resolve(data)
                }, err => {
                    reject(err)
                }
            } else {
                resolve(item)
            }
        }
    })
}

9. for...of 和 for...in 的区别?

  • for...of:可遍历「数组」、「对象」、「字符串」、「Set数据结构」
// 遍历对象:

1. 遍历,获取 「对象」的「键值」
2. 遍历, 只会遍历「当前对象」
3. 性能稍好

// 遍历数组:

1. 遍历, 返回下标对应的「属性值」

  • for...in: 主要为了遍历「对象」,不适用「数组」
// 遍历对象:

1. 遍历,获取「对象」的「键名」
2. 遍历, 会遍历该对象的「整个原型链」
3. 所以性能稍差

// 遍历数组:

1. 遍历,返回所有「可枚举」的「属性」

10. Set?

数组: 元素是「不唯一可以重复」,「有序的」,通过下标、索引快速找到某个元素

  • Set解决什么问题?
1. 定义:Set数据结构,它存储的是-组「唯一的」「无序」的元素集合
  • Set构造函数声明?
const set = new Set()
const set1 =new Set([1, 2]) // 初始化
  • Set构造函数原型上的属性或方法?
const set = new Set()

1. 大小: set.size
2. 新增: set.add(value)
3. 删除: set.delete(value) // 返回boolean
4. 判断: set.has(value) // 返回boolean
5. 清空: set.clear()
  • Set构造函数,遍历的方法?
const set = new Set(['red','blue'])

1. set.entries()
2. set.keys()
3. set.values()
4. set.forEach()

1. 遍历「键值对」

for(let item of set.entries()) {
    console.log(item)
    // ['res', 'red'],['blue','blue']
}

2. 遍历「键」
for (let key of set.keys()) {
    console.log(key) // red, blue
}

3. 遍历「值」
for(let value of set.values()) {
    console.log(values) // res, blue
}
  • Set 和数组的互转?
const arr = [1, 2]

// 数组 转化成 Set

1. const set = new Set(arr)

// Set 转化成 数组

1.扩展运算符: const arr1 = [...new Set()]

2. Array.from: const arr2 = Array.from(new Set())

  • Set 实际场景应用?
var set1 = [1, 2, 3, 4]
var set2 = [2, 3, 4, 5]
1. 数组去重: arr1 = [...new Set()]
2. 求两个集合的并集、交集和差集
    a. 并集:new Set([...set1, ...set2])
    b. 交集:[...set1].filter(a => {set2.has(a)})
    c. 差集:[...set1].filter(a => {!set2.has(a)})

11. WeakSet?

  • WeakSet 定义
1. 和 Set 相同点: 存储「唯一的」「无序」元素
2. 区别1:WeakSet 成员只能是「引用类型」
3. 区别2: 不可被遍历, 不计入「垃圾回收机制」
  • WeakSet构造函数, 原型上的属性?
const set = new WeakSet()
1. 添加: set.add()
2. 删除:set.delete()
3. 判断:set.has()
  • WeakSet不可遍历

12. Map?

关于对象: 本质是「键值对」集合(Hash结构),键: 只能是字符串(若不是,内部通过toString转化成字符串)

  • Map 解决什么问题?
1. 定义:Map构造函数, 类似于对象,也是「键值对」集合
2. 解决:「健」可以是「任意数据类型」

  • Map构造函数声明?
const map = new Map()
  • Map构造函数,原型上的属性或方法?
const map = new Map()
// 增、删、改、查
1. 新增:map.set(key, value).set(key, value)
2. 删除:map.delete(key), 返回true or false
3. 取值:map.get(key)
4. 清空:map.clear()
5. 判断是否存在某个key:map.has(keys)
6. 数量: map.size
  • Map构造函数,遍历方法?
1. map.entries()
2. map.keys()
3. map.values()
m4. ap.forEach(), 与数组forEach类似

const map = new Map

1. 遍历整个「键值对」

for (let [key, value] of map) {}
for(let item of map.entries())

2. 遍历「键」

for(let key of map.keys()){}

3. 遍历「值」

for(let value of map.values())

13. WeakMap?

  • WeakMap的定义
1. 定义:WeakMap构造函数,也是「键值对」集合
2. 区别1:「键」只能是「对象」,null除外
3. 区别2: 不可被遍历,WeakMap「键名」指向的对象,不计入「垃圾回收机制」

  • WeakMap构造函数声明?
const map = new WeaMap()
  • WeakMap 原型上的方法?
const map = new WeakMap()

// 增、删、改、查
1. 新增: map.set()
2. 删除: map.delete()
3. 获取: map.get()
4. 判断:  map.has()
  • WeakMap 没有遍历操作(entries、keys、values、size)

14. ES6 模块化?

  • 语法
// 1. 
export default {
    ceshi: '---'
}
import ceshi from './..'

// 2.
export function a() {
    
}
export function b() {
     
}
import {a, b} from './..'

  • 编译: babel 定义: ES6转码器: 将ES6代码转化成浏览器识别的代码
1.根目录新建 ‘.babelrc’文件,
{
    "preset": ["es2015", "latest"],
    "plugins": []
}
2. 安装:
babel-core
babel-preset-es2015
babel-preset-latest
babel-polyfill
  • 开发环境 webpack
1. 安装 webpack 和babel-loader
2. 新建‘webpack.config.js’
export = {
    entry:,
    output:,
    module: {
        rules: []
    },
}

  • 开发环境 rollup
1. 安装:
rollup
rollup-plugin-node-resolve
rollup-plugin-babel
babel-plugin-external-helper
babel-preset-latest

2. rollup 功能单一, webpack功能强大
3. 新建 ‘rollup.config.js'
export default {
    entry: '',
    format: 'umd',
    plugins: [
        resolve()
    ]
}
  • 模块化发展
1.没有标准
2.AMD标准 require.js
3.前端打包工具, nodejs模块化
4. ES6统一模块化标准
5. nodejs 积极支持,尚未统一
15. Class?

js面向对象, 生成实例对象的传统方法是通过「构造函数」

  • Class 由来?
1. 更符合平时理解的「面向对象」的语言
2. class 是一个语法糖,其底层还是通过「构造函数」去创建的

  • Class和普通构造函数的区别?
1. 第一点: Class不能提升,普通函数可以变量提升
2. 第二点: Class只能通过new 实例化
3. 第三点: Class采用「严格模式」
4. 第四点: Class 原型上的属性,不能遍历
5. 第五点: 在Class内部,不可以修改class名称
  • constructor
1. constructor():是类的构造函数,通过new 创建对象实例时, 会自动调用
  • super
作用:
1. 当函数使用: super(), 子类没有自己的this对象,super代表了父类的构造函数

2. 当作对象使用: super 在普通方法中,指向「父类.prototype」, 绑定的确是子类的「this

面试1: 当我们通过new 调用构造函数,都干了啥?

1. 在内存中创建「空的对象」
2. this就会指向刚才创建的「空对象」
3. 执行构造函数里面的代码,给改「空对象」添加属性和方法
4. 隐式返回「该对象」,构造函数不需要return

面试2: Class实现私有属性?

  • 私有属性
1. 能被class内部的不同方法访问,但不能在类外部被访问
2. 子类不能继承父类的私有属性
  • 解决方案:
// 闭包:
class Private() {
    constructor(x) {
        let a = x
        this.getA = function() {
            return a
        }
    }
}
let obj = new Private(1)
console.log(obj.a) // undefined
console.log(obj.getA) // 1