如果视频作者当面试官会问

异步

什么是单线程,和异步有什么关系

  • 单线程,只有一个线程,同一时间只能做一件事,两段 JS 代码不能同时执行

  • 原因,避免 DOM 渲染的冲突

    • 浏览器需要渲染 DOM
    • JS 可以修改 DOM 结构
    • JS 执行的时候,浏览器 DOM 渲染会暂停
    • 两段 JS 也不能同时执行(都修改 DOM 就冲突了)
    • webworker 支持多线程,但是不能访问 DOM
  • 解决单线程方法:异步

    • 问题一:没按照书写方式执行,可读性差
    • 问题二:callback 中不容易模块化

什么是 event-loop

  • 事件轮询,JS 实现异步的具体解决方案
  • 同步代码,直接执行
  • 异步函数先放在 异步队列 中
  • 待同步函数执行完毕,轮询执行 异步队列 的函数

是否用过 jQuery 的 Deferred

Deferred 延迟

jQuery 1.5 之前

// 修改开发扩展封闭
const ajax = $.ajax({
url: 'data.js',
success: () => {
console.log('success')
},
error: () => {
console.log('error')
}
})
console.log(ajax) // 返回 XHR 对象
// success

jQuery 1.5 及之后

const ajax = $.ajax('data.js')
ajax.done(() => console.log('success1'))
.fail(() => console.log('error'))
.done(() => console.log('success2'))

console.log(ajax); // 返回 deferred 对象
// success1 success2
// 很像 Promise 的写法 -- 扩展开发修改封闭
const ajax = $.ajax('data.js')
ajax.then(() => {
console.log('success1')
}, () => {
console.log('error1')
}).then(() => {
console.log('success2')
}, () => {
console.log('error2')
})

console.log(ajax)
// success1 success2

jQuery 1.5 的变化

  • 无法改变 JS 异步和单线程的本质
  • 只能从写法上杜绝 callback 这种形式
  • 它只是一种语法糖形式,但是解耦了代码
  • 很好的体现:开放封闭原则

使用 jQuery Deferred

不使用
const wait = () => {
const task = () => {
console.log('task success')
}
setTimeout(task, 2000)
}
wait()
使用

成功

function waitHandler (){
const dtd = $.Deferred()
const wait = (dtd) => {
const task = () => {
console.log('task success')
dtd.resolve('ok') // 成功
// dtd.reject('fail') // 失败
}
setTimeout(task, 2000)
return dtd
}
return wait(dtd)
}

const w = waitHandler()
w.then((res) => {
console.log(res)
console.log('suc1')
}, () => {
console.log('err1')
}).then(() => {
console.log('suc2')
}, () => {
console.log('err2')
})
/*
task success
ok
suc1
suc2
*/

失败

function waitHandler (){
const dtd = $.Deferred()
const wait = (dtd) => {
const task = () => {
console.log('task fial')
// dtd.resolve('ok') // 成功
dtd.reject('fail') // 失败
}
setTimeout(task, 2000)
return dtd
}
return wait(dtd)
}

const w = waitHandler()
w.then(() => {
console.log('suc1')
}, (err) => {
console.log(err)
console.log('err1')
})
w.then(() => {
console.log('suc2')
}, (err) => {
console.log(err)
console.log('err2')
})
w.then(() => {
console.log('suc3')
}, (err) => {
console.log(err)
console.log('err3')
})
/*
task fial
fail
err1
fail
err2
fail
err3
*/

但在 const w = waitHandler() 可以直接调用 w.reject() 会导致后面的结果出现偏差

function waitHandler (){
const dtd = $.Deferred()
const wait = (dtd) => {
const task = () => {
console.log('task fial')
dtd.resolve('ok') // 成功
// dtd.reject('fail') // 失败
}
setTimeout(task, 2000)
return dtd.promise()
}
return wait(dtd)
}

const w = waitHandler() // 返回 promise 对象
// w.reject() // w.reject is not a function
w.then((res) => {
console.log(res)
console.log('suc1')
}, () => {
console.log('err1')
})

Promise 的基本使用和原理

加载一张图片

function loadImg(src) {
return new Promise((resolve, reject) => {
let img = document.createElement('img')
img.src = src
img.onload = function () {
resolve(img)
}
img.onerror = function () {
reject(new Error('图片加载失败'))
}
})
}
const img1 = loadImg('./allpage.jpg')
img1.then(img => {
console.log(img)
console.log(img.width)
}).catch(err => {
console.log(err)
})

加载第一张后再加载第二张

function loadImg(src) {
return new Promise((resolve, reject) => {
let img = document.createElement('img')
img.src = src
img.onload = function () {
resolve(img)
}
img.onerror = function () {
reject(new Error('图片加载失败'))
}
})
}
const img1 = loadImg('./allpage.jpg')
const img2 = loadImg('./bird.gif')
普通
img1.then(img1 => {
console.log(img1)
console.log(img1.width)
return img2
}).then(img2 => {
console.log(img2)
console.log(img2.width)
}).catch(err => {
console.log(err)
})
使用 Promise.all()
Promise.all([img1, img2]).then(res => {
console.log(res[0])
console.log(res[0].width)
console.log(res[1])
console.log(res[1].width)
}).catch(err => {
console.log(err)
})
使用 await/async
function loadImg(src) {
return new Promise((resolve, reject) => {
let img = document.createElement('img')
img.src = src
img.onload = function () {
resolve(img)
}
img.onerror = function () {
reject(new Error('图片加载失败'))
}
})
}
async function createImg() {
let img1 = await loadImg('./allpage.jpg')
let img2 = await loadImg('./bird.gif')
console.log(img1)
console.log(img1.width)
console.log(img2)
console.log(img2.width)
}
createImg()

介绍一下 async/await(和 Promise 的区别、联系)

  • 不是取代 Promise 的,而是对 Promise 的补充
  • 使用了 Promise,并乜有和 Promise 冲突
  • 使用 await,函数必须用 async 标识
  • await 后面跟的是一个 Promise 实例
  • 完全是同步的写法,再也没有回调函数
  • 但是:改变不了 JS 单线程、异步的本质

总结一下当前 JS 解决异步的方案

  • jQuery deferred
  • Promise
  • async/await
  • Generator
    • 不是异步的直接替代方式

原型

说一个原型的实际应用

  • 描述 jquery 如何使用原型
  • 描述 zepto 如何使用原型
  • 结合自己的开发经验,其中使用的原型

原型如何体现它的扩展性

  • 说一下 jquery 和 zepto 的插件机制
  • 结合自己的开发经验,做过的基于原型的插件

vdom、虚拟 dom、virtual dom

  • vdom 是 vue 和 react 的核心
  • 如果面试问道 vue 和 react 和实现,免不了问 vdom

vdom 是什么?为何会存在(为何使用) vdom?

  • 虚拟 DOM

  • 用 JS 模拟 DOM 结构

  • DOM 变化的对比,放在JS 层来做(图灵完备语言)

  • 提高重绘性能

  • DOM 操作是“昂贵”的,JS 运行效率高

  • 劲量减少 DOM 操作,而不是“推到重来”

  • 项目越复杂,影响就越复杂

  • vdom 可以解决

vdom 如何应用,核心 API 是什么

  • 如何使用?可用 snabbdom 库的用法举例
  • 核心 API:h 函数、patch 函数
  • vdom-snabbdom 使用 vdom 实现的一个库
  • h(‘<标签名>’, {…属性…}, […子元素…]) –> 有很多子元素
  • h(‘<标签名>’, {…属性…}, ‘…’) –> 只有一个文本节点的子元素
  • patch(container, vnode) –> 将 节点 添加到容器中,初次渲染
  • patch(vnode, newVnode) –> 新旧节点对比,将需改的内容添加,再次渲染

介绍一下 diff 算法

  • 什么是 diff 算法
  • 去繁就简
    • diff 算法非常复杂,实现难度很大,源码量很大
    • 去繁就简,讲明白核心流程,不关心细节,
    • 2、8原则:
    • 学习的东西太多了,现在不能一行一行的学习,根据目录去查找自己需要补充的重点,然后纤细去看那部分,余下部分过一遍就行。
    • 面试官也大部分不清楚细节,但是很关心核心流程
  • vdom 为何用 diff 算法
    • DOM 操作是“昂贵”的,因此劲量减少 DOM 操作
    • 找出本次 DOM 必须更新的节点来更新,其他的不更新
    • 这个“找出”的过程,就需要 diff 算法
    • vdom 中应用 diff 算法是为了找出需要更新的节点
  • diff 算法的实现流程

vue

看视频、看博客、看文章都属于看源码,并不是非要去看代码才算看源码

说一下使用 jQuery 和使用框架的区别(vue、react)

代码演示、使用总结

  • 数据与视图的分离,解耦(开放封闭原则)

  • 以数据驱动视图,只关心数据变化,DOM 操作被封装

说一下对 MVVM 的理解

先讲 MVM 再讲 MVVM

MVC:

  • Model(数据)、View(视图、界面)、Controller(控制器、逻辑处理)
  • 用户 –> View –> Controller –> Model(View 发生变化出发 Conatroller,Controller 修改 Model,Model 导致 View 更新)
  • 用户 –> Controller –> Model –> View

MVVM

  • Model(模型、数据)、View(视图、模板)、ViewModel(桥梁,连接 Model 和 View)
  • 视图和模型是分离的
  • MVVM 不算一种创新,是在 MVC 上的一种微创新
  • 但其中的 ViewModel 确是一种创新
  • 真正结合前端场景应用的创建

MVVM 框架的三要素

响应式:vue 如何监听到 data 的每个属性变化?

模板引擎:vue 的模板如何被解析,指令如何处理?

渲染:vue 的模板如何被渲染成 html?以及渲染过程

vue 中如何实现响应式?

  • 关键是理解 Object.defineProperty
  • 讲一下 data 的属性代理到 vm 上

什么是响应式?

  • 修改 data 属性后,vue 立刻监听到
  • data 属性被代理到 vm 上

Object.defineProperty() 如何使用?在 vue 中如何使用?

模拟、演示

vue 中如何解析模版?

  • 模版是什么

    • 模版最终必须转化为 JS 代码,因为:

    • 有逻辑(v-if v-for),必须用 JS 才能实现(图灵完备)

    • 转换为 html 渲染页面,必须用 JS 才能实习

    • 因此,模板后需要转换成一个 JS 函数(render 函数,渲染函数)

    • 模板:字符串,有逻辑,嵌入 JS 变量…

    • 模板必须转换为 JS 代码(有逻辑、渲染 html、JS 变量)

  • with 的用法(劲量不要用,render 函数用到了,会给开发调试带来很大的成本)

  • 读一读《js语言精粹》美国比较牛的js程序员写的,js创始人,有价值,有干货

  • render 函数

    • 哪里可以看到 render 函数?– 在 vue.js 源码中搜索 render,在 code.render 中可以看到
    • render 函数是什么样子的?
    • v-if v-for v-on 都是怎么处理的?
    • vm._c是什么?render 函数返回了什么?
    • render 函数执行返回 vnode
  • render 函数与 vdom

  • 如何解析模版

vue 的整个实现流程

  1. 解析模板成 render 函数
    • 模板中的所有信息都被 render 函数包含
    • 模板中用到的 data 中的属性,都变成了 JS 变量
    • 模板中的 v-model v-for v-on 都变成了 JS 逻辑
    • render 函数返回 vnode
  2. 响应式开始监听
    • 使用 Object.defineProperty()
    • 将 data 的属性代理到 vm 上
  3. 首次渲染,显示页面,且绑定依赖
    • 初次渲染,执行 updateComponent,执行vm._render()
    • 执行 render 函数,会访问到 vm.list 和 vm.title
    • 会被响应式的 get 方法监听到
    • 执行 updateComponent,会走到 vdom 的 patch 方法
    • patch 将 vnode 渲染成 DOM,初次渲染完成
    • 为何要监听 get,直接监听 set 不行吗?
    • data 中有很多属性,有些被用到,有些可能不被用到
    • 被用到的会走 get,不被用到的不会走到 get
    • 未走到 get 中的属性,set 的时候我们也无需关心
  4. data 属性变化,触发 rerender
    • 修改属性,被响应式的 set 监听到
    • set 执行 updateComponent
    • updateComponent 重新执行 vm.render()
    • 生成的 vnode 和 preVnode,通过 patch 进行对比
    • 渲染到 html 中

hybrid