JS
变量类型和计算
- 
typeof能判断那些类型 - 何时使用 === 何时使用 ==
 - 值类型和引用类型的区别
 - 手写深拷贝
 
值类型和引用类型
// 值类型
let a = 100
let b = a
a = 200
console.log(b)  // 100
// 引用类型
let a = { age: 20 }
let b = a
b.age = 21
console.log(a.age)  // 21
// 常见值类型
let u   // undefined
const s = 'abc'
const n = 100
const b = true
const s = Symbol('s')
// 常见引用类型
const obj = { x: 100 }
const arr = ['a', 'b', 'c']
const n = null // 特殊引用类型,指针指向为空地址
// 特殊的引用类型,但不用于存储数据,所以没有拷贝、复制函数这一说
function fun() {}
类型判断
typeof 运算符
- 识别所以值类型
 - 识别函数
 - 判断是否是引用类型(不可再细分)
 
深克隆
function deepClone(obj = {}) {
    if (typeof obj !== 'object' || obj === null) {
        // obj 是 null,或是不是对象和数组,直接返回
        return obj
    }
    // 初始化返回结果
    let result
    if (obj instanceof Array) {
        result = []
    } else {
        result = {}
    }
    for (let key in obj) {
        // 保证 key 不是原型的属性
        if (obj.hasOwnProperty(key)) {
            // 递归
            result[key] = deepClone(obj[key])
        }
    }
    return result
}
类型转换
- 字符串拼接
 - ==
 - if 语句逻辑运算
 
const a = 100 + 10 // 110
const b = 100 + '10' // 10010
const c = true + '10' // true10
const d = 100 + parseInt('10') // 110
==
100 == '100' // true
0 == '0' // true
0 == false // true
false == '' // true
null == undefined // true
NaN == NaN // false
// 除了 == null 之外,其他都一律用 === ,例如:
const obj = { x: 100 }
if (obj.a == null) {}
// 相当于
if (obj.a === null || obj.a === undefined) {}
逻辑运算
if 语句和逻辑运算
- truly 变量:!!a === true 的变量
 - falsely 变量:!!a === false 的变量
 
原型和原型链
- 如何判断一个变量是不是数组?
 - 手写一个简易的 jQuery,考虑插件和扩展性
 - class 的原型本质,怎么理解?
 
class
class Student {
    constructor(name, number) {
        this.name = name
        this.number = number
    }
    greeting() {
        console.log(`Hello, My name is ${this.name}, number is ${this.number}`)
    }
}
// 实例化
const zhangsan = new Student('zhangsan', 1)
zhangsan.greeting() // Hello, My name is zhangsan, number is 1
继承
// 父类
class Person {
    constructor(name) {
        this.name = name
    }
    eat() {
        console.log(`${this.name} eat something`)
    }
}
// 子类
class Student extends Person {
    constructor(name, number) {
        super(name)
        this.number = number
    }
    greeting() {
        console.log(`Hello, My name is ${this.name}, number is ${this.number}`)
    }
}
// 子类
class Teacher extends Person {
    constructor(name, subject) {
        super(name)
        this.subject = subject
    }
    teach() {
        console.log(`My name is ${this.name}, and I am teaching ${this.subject}`)
    }
}
// 实例化
const zhangsan = new Student('zhangsan', 1)
zhangsan.greeting() // Hello, My name is zhangsan, number is 1
zhangsan.eat() // zhangsan eat something
const mrWang = new Teacher('wang', 'Math')
mrWang.eat()  // wang eat something
mrWang.teach()  // My name is wang, and I am teaching Math
// 类型判断
console.log(zhangsan instanceof Student) // true
console.log(zhangsan instanceof Person) // true
console.log(zhangsan instanceof Object) // true
// true
console.log([] instanceof Array)
console.log([] instanceof Object)
console.log({} instanceof Object)
原型
// class 实际上是函数,语法糖而已
typeof Person // function
typeof Student // function
// 隐式原型和显示原型
console.log(zhangsan.__proto__)
console.log(Student.prototype)
console.log(zhangsan.__proto__ === Student.prototype) // true
原型关系
- 每个 class 都有显示原型 prototype
 - 每个实例都有隐式原型 __ proto__
 - 实例的 __ proto__指向对应的 class 的 prototype
 
原型链
console.log(Student.prototype.__proto__)
console.log(Person.prototype)
console.log(Person.prototype === Student.prototype.__proto__) // true
instanceof
手动实现 instanceof
function myInstanceof(left, right) {
    // 获取类的原型
    let prototype = right.prototype
    // 获取对象的原型
    left = left.__proto__
    // 判断对象的类型是否等于类型的原型
    while (true) {
        if (left === null)
            return false
        if (prototype === left)
            return true
        left = left.__proto__
    }
}
手写一个简易的 jQuery,考虑插件和扩展性
class jQuery {
    constructor(selector) {
        const result = document.querySelectorAll(selector)
        const length = result.length
        for (let i = 0; i < length; i++) {
            this[i] = result[i]
        }
        this.length = length
        this.selector = selector
    }
    get(index) {
        return this[index]
    }
    each(fn) {
        for (let i = 0; i < this.length; i ++) {
            const elem = this[i]
            fn(elem)
        }
    }
    on(type, fn) {
        return this.each(elem => {
            elem.addEventListener(type, fn, false)
        })
    }
}
// 插件
jQuery.prototype.dialog = function (info) {
    alert(info)
}
// “造轮子”
class myJQuery extends jQuery {
    constructor(selector) {
        super(selector)
    }
    // 扩展自己的方法
    addClass(className) {
        // ...
    }
    style(data) {
        // ...
    }
}
作用域和闭包
- this 的不同应用场景,如何取值?
 - 手写 bind 函数
 - 实际开发中闭包的应用场景,举例说明
 - 创建 10 个  
<a>标签点击的时候弹出对应的序号 
作用域
let a = 0
function fn1() {
    let a1 = 100
    function fn2() {
        let a2 = 200
        function fn3() {
            let a3 = 300
            return a + a1 + a2 + a3
        }
        fn3()
    }
    fn2()
}
fn1()
- 全局作用域
 - 函数作用域
 - 块级作用域(ES6新增)
 
自由变量
- 一个变量在当前作用域没有定义,但被使用了
 - 向上级作用域,一层一层依次寻找,直到找到了为止
 - 如果在全局作用域都没有找到,则报错 xxx is not defined
 
闭包
- 作用域应用的特殊情况,有两种表现
 - 函数作为参数被传递
 - 函数作为值被返回
 
// 函数作为返回值
function create() {
    let a = 100
    return function () {
        console.log(a)
    }
}
let fn = create()
let a = 200
fn() // 100
// 函数作为参数
function print(fn) {
    let a = 200
    fn()
}
let a = 100
function fn() {
    console.log(a)
}
print(fn) // 100
this
- 作为普通函数
 - 使用 call apply bind
 - 作为对象方法被调用
 - 在 class 方法中调用
 - 箭头函数
 
function fn1() {
    console.log(this)
}
fn1() // window
fn1.call({x: 100}) // {x: 100}
const fn2 = fn1.bind({x: 200})
fn2() // {x: 200}
箭头函数
const zhangsan = {
    name: 'zhangsan',
    greeting() {
        // this 即当前对象
        console.log(this)
    },
    wait() {
        setTimeout(function() {
            // this === window
            console.log(this)
        })
    }
}
// 箭头函数的 this 永远取上级作用域的 this
const zhangsan = {
    name: 'zhangsan',
    // this 即当前对象
    greeting() {
        console.log(this)
    },
    wait() {
        setTimeout(() => {
            // this 即当前对象
            console.log(this)
        })
    }
}
创建 10 个  <a>  标签点击的时候弹出对应的序号
let a
for (let i = 0; i < 10; i++) {
    a = document.createElement('a')
    a.innerHTML = i + '<br>'
    a.addEventListener('click', function(e) {
        e.preventDefault()
        alert(i)
    })
    document.body.appendChild(a)
}
手写 bind 函数
Function.prototype.myBind = function() {
    // 将参数拆解为数组
    const args = Array.prototype.slice.call(arguments)
    // 获取 this(数组第一项)
    const that = args.shift()
    // fn.bind(...) 中的 fn
    const self = this
    // 返回一个函数
    return function() {
        return self.apply(that, args)
    }
}
实际开发中闭包的应用
隐藏数据
如做一个简单的 cache 工具
function createCache() { const data = {} // 闭包中的数据,被隐藏,不被外界访问 return { set(key, value) { data[key] = value }, get(key) { return data[key] } } } const c = createCache() c.set('a', 100) console.log(c.get('a'))
异步
同步和异步得区别是什么?
手写 Promise 加载一张图片
前端使用异步的场景
请描述 event loop (事件循环/事件轮询)的机制,可画图
什么是宏认为和微任务,两者有什么区别?
Promise 有哪三种状态?如何变化?
Promise 的 then 和 catch 的连接问题
async/await 语法
Promise 和 setTimeout 的顺序问题
单线程
- JS 是单线程语言,只能同时做一件事
 - 浏览器和 nodejs 已支持 JS 启动进程,如 Web Worker
 - JS 和 DOM 渲染共用同一个线程,因为 JS 可修改 DOM 结构
 - 异步基于 回调 callback 函数形式
 
callback
// 异步
console.log(100)
setTimeout(function() {
  console.log(200)
}, 1000)
console.log(300)
// 同步
console.log(100)
alert(200)
console.log(300)
应用场景
- 网络请求,如 ajax 图片加载
 - 定时任务,如 setTimeout
 
promise
基本使用
// 加载图片
function loadImg(src) {
    const p = new Promise(
        (resolve, reject) => {
            const img = document.createElement('img')
            img.onload = () => {
                resolve(img)
            }
            img.onerror = () => {
                const err = new Error(`图片加载失败 ${src}`)
                reject(err)
            }
            img.src = src
        }
    )
    return p
}
const url = 'www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png'
loadImg(url).then(img => {
    console.log(img.width)
    return img
}).then(img => {
    console.log(img.height)
}).catch(ex => console.error(ex))
状态
- 三种状态
- pending
 - resolved
 - rejected
 - 变化: pending => resolved 或 pending => rejected
 - 变化是不可逆的
 
 - 状态的表现和变化
- pending 状态,不会触发 then 和 catch
 - resolved 状态,会触发后续的 then 回调函数
 - rejected 状态,会触发后续的 catch 回调函数
 
 
const p1 = new Promise((resolve, reject) => {})
console.log(p1) // pending
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve()
  })
})
console.log(p2) // pending 一开始打印时
setTimeout(() => console.log(p2)) // resolved
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject()
  })
})
console.log(p3) // pending 一开始打印时
setTimeout(() => console.log(p3)) // rejected
// 直接获取一个 resolved 状态的 Promise
const resolved = Promise.resolve(100)
console.log(resolved) // resolved
// 直接获取一个 rejected 状态的 Promise
const resolved = Promise.reject('err')
console.log(resolved) // rejected
- then 和 catch 对状态的影响
- then 正常返回 resolved ,里面有报错则返回 rejected
 - catch正常返回 resolved ,里面有报错则返回 rejected
 
 
// then() 一般正常返回 resolved 状态的 promise
Promise.resolve().then(() => {
    return 100
})
// then() 里抛出错误,会返回 rejected 状态的 promise
Promise.resolve().then(() => {
    throw new Error('err')
})
// catch() 不抛出错误,会返回 resolved 状态的 promise
Promise.reject().catch(() => {
    console.error('catch some error')
})
// catch() 抛出错误,会返回 rejected 状态的 promise
Promise.reject().catch(() => {
    console.error('catch some error')
    throw new Error('err')
})
题
// 第一题
Promise.resolve().then(() => {
    console.log(1)
}).catch(() => {
    console.log(2)
}).then(() => {
    console.log(3)
})
// 第二题
Promise.resolve().then(() => { // 返回 rejected 状态的 promise
    console.log(1)
    throw new Error('erro1')
}).catch(() => { // 返回 resolved 状态的 promise
    console.log(2)
}).then(() => {
    console.log(3)
})
// 第三题
Promise.resolve().then(() => { // 返回 rejected 状态的 promise
    console.log(1)
    throw new Error('erro1')
}).catch(() => { // 返回 resolved 状态的 promise
    console.log(2)
}).catch(() => {
    console.log(3)
})
event-loop
- JS 是单线程运行的
 - 异步要基于回调来实现
 - event loop 就是异步回调的实现原理
 
JS 如何执行的
- 从前到后,一行一行执行
 - 如果某一行执行报错,则停止下面代码的执行
 - 先把同步代码执行完,再执行异步
 
event loop 执行过程
- 同步代码,一行一行放在 Call Stack 执行
 - 遇到异步,会先 “记录” 下,等待时机(定时,网络请求)
 - 时机到了,就移动到 Callback Queue
 - 如果 Call Stack 为空(即同步代码执行完)Event Loop 开始工作
 - 轮询查找 Callback Queue ,如果有则移动到 Call Stack 执行
 - 然后继续轮询查找(永动机一样)
 
DOM 事件和 event loop
- 异步(setTimeout,ajax 等)使用回调,基于 event loop
 - DOM 事件也使用回调,基于 event loop
 
async/await
- 异步回调 callback hell
 - Promise 基于 then catch 链式调用,但也是基于回调函数
 - async/await 是同步语法,彻底消灭回调函数
 
有很多 async 的面试题,例如
- async 直接返回,是什么
 - async 直接返回 promise
 - await 后面不加 promise
 
基本语法
function loadImg(src) {
    const promise = new Promise((resolve, reject) => {
        const img = document.createElement('img')
        img.onload = () => {
            resolve(img)
        }
        img.onerror = () => {
            reject(new Error(`图片加载失败 ${src}`))
        }
        img.src = src
    })
    return promise
}
async function loadImg1() {
    const src1 = 'www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png'
    const img1 = await loadImg(src1)
    return img1
}
async function loadImg2() {
    const src2 = 'https://avatars3.githubusercontent.com/u/9583120'
    const img2 = await loadImg(src2)
    return img2
}
(async function () {
    // 注意:await 必须放在 async 函数中,否则会报错
    try {
        // 加载第一张图片
        const img1 = await loadImg1()
        console.log(img1)
        // 加载第二张图片
        const img2 = await loadImg2()
        console.log(img2)
    } catch (ex) {
        console.error(ex)
    }
})()
async/await 和 promise 的关系
- async/await 是消灭异步回调的终极武器
 - 但和 promise 并不互斥
 - 两者反而相辅相成
 - await 相当于 promise 的 then
 - try...catch 可捕获异常,代替了 promise 的 catch
 
- async 函数返回结果都是 Promise 对象(如果函数内没返回 Promise ,则自动封装一下)
 
async function fn2() {
    return new Promise(() => {})
}
console.log( fn2() )
async function fn1() {
    return 100
}
console.log( fn1() ) // 相当于 Promise.resolve(100)
- await 后面跟 Promise 对象:会阻断后续代码,等待状态变为 resolved ,才获取结果并继续执行
 - await 后续跟非 Promise 对象:会直接返回
 
(async function () {
    const p1 = new Promise(() => {})
    await p1
    console.log('p1') // 不会执行
})()
(async function () {
    const p2 = Promise.resolve(100)
    const res = await p2
    console.log(res) // 100
})()
(async function () {
    const res = await 100
    console.log(res) // 100
})()
(async function () {
    const p3 = Promise.reject('some err')
    const res = await p3
    console.log(res) // 不会执行
})()
- try...catch 捕获 rejected 状态
 
(async function () {
    const p4 = Promise.reject('some err')
    try {
        const res = await p4
        console.log(res)
    } catch (ex) {
        console.error(ex)
    }
})()
总结来看:
- async 封装 Promise
 - await 处理 Promise 成功
 - try...catch 处理 Promise 失败
 
异步本质
await 是同步写法,但本质还是异步调用。
async function async1 () {
  console.log('async1 start')
  await async2()
  console.log('async1 end') // 关键在这一步,它相当于放在 callback 中,最后执行
}
async function async2 () {
  console.log('async2')
}
console.log('script start')
async1()
console.log('script end')
即,只要遇到了 await ,后面的代码都相当于放在 callback 里。
for...of
- for ... in (以及 forEach for)是常规的同步遍历
 - for ... of 常用与异步的循环
 
// 定时算乘法
function multi(num) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve(num * num)
        }, 1000)
    })
}
// // 使用 forEach ,是 1s 之后打印出所有结果,即 3 个值是一起被计算出来的
// function test1 () {
//     const nums = [1, 2, 3];
//     nums.forEach(async x => {
//         const res = await multi(x);
//         console.log(res);
//     })
// }
// test1();
// 使用 for...of ,可以让计算挨个串行执行
async function test2 () {
    const nums = [1, 2, 3];
    for (let x of nums) {
        // 在 for...of 循环体的内部,遇到 await 会挨个串行计算
        const res = await multi(x)
        console.log(res)
    }
}
test2()
微任务/宏任务
- 宏任务:setTimeout setInterval DOM 事件
 - 微任务:Promise(对于前端来说)
 - 微任务比宏任务执行的更早
 
console.log(100)
setTimeout(() => {
    console.log(200)
})
Promise.resolve().then(() => {
    console.log(300)
})
console.log(400)
// 100 400 300 200
event loop 和 DOM 渲染
- 每一次 call stack 结束,都会触发 DOM 渲染(不一定非得渲染,就是给一次 DOM 渲染的机会!)
 - 然后再进行 event loop
 
const $p1 = $('<p>一段文字</p>')
const $p2 = $('<p>一段文字</p>')
const $p3 = $('<p>一段文字</p>')
$('#container')
            .append($p1)
            .append($p2)
            .append($p3)
console.log('length',  $('#container').children().length )
alert('本次 call stack 结束,DOM 结构已更新,但尚未触发渲染')
// (alert 会阻断 js 执行,也会阻断 DOM 渲染,便于查看效果)
// 到此,即本次 call stack 结束后(同步任务都执行完了),浏览器会自动触发渲染,不用代码干预
// 另外,按照 event loop 触发 DOM 渲染时机,setTimeout 时 alert ,就能看到 DOM 渲染后的结果了
setTimeout(function () {
    alert('setTimeout 是在下一次 Call Stack ,就能看到 DOM 渲染出来的结果了')
})
宏任务和微任务的区别
- 宏任务:DOM 渲染后再触发
 - 微任务:DOM 渲染前会触发
 
// 修改 DOM
const $p1 = $('<p>一段文字</p>')
const $p2 = $('<p>一段文字</p>')
const $p3 = $('<p>一段文字</p>')
$('#container')
    .append($p1)
    .append($p2)
    .append($p3)
// // 微任务:渲染之前执行(DOM 结构已更新)
// Promise.resolve().then(() => {
//     const length = $('#container').children().length
//     alert(`micro task ${length}`)
// })
// 宏任务:渲染之后执行(DOM 结构已更新)
setTimeout(() => {
    const length = $('#container').children().length
    alert(`macro task ${length}`)
})
再深入思考一下:为何两者会有以上区别,一个在渲染前,一个在渲染后?
- 微任务:ES 语法标准之内,JS 引擎来统一处理。即,不用浏览器有任何关于,即可一次性处理完,更快更及时。
 - 宏任务:ES 语法没有,JS 引擎不处理,浏览器(或 nodejs)干预处理。
 
经典面试题
async function async1 () {
  console.log('async1 start')
  await async2() // 这一句会同步执行,返回 Promise ,其中的 `console.log('async2')` 也会同步执行
  console.log('async1 end') // 上面有 await ,下面就变成了“异步”,类似 cakkback 的功能(微任务)
}
async function async2 () {
  console.log('async2')
}
console.log('script start')
setTimeout(function () { // 异步,宏任务
  console.log('setTimeout')
}, 0)
async1()
new Promise (function (resolve) { // 返回 Promise 之后,即同步执行完成,then 是异步代码
  console.log('promise1') // Promise 的函数体会立刻执行
  resolve()
}).then (function () { // 异步,微任务
  console.log('promise2')
})
console.log('script end')
// 同步代码执行完之后,屡一下现有的异步未执行的,按照顺序
// 1. async1 函数中 await 后面的内容 —— 微任务
// 2. setTimeout —— 宏任务
// 3. then —— 微任务
模块化
ES6 Module
略








