Appearance
Fe-interview
△ 338 次 手写题库 'https://github.com/Mayandev/fe-interview-handwrite'
△ 200 次 Vue 中双向数据绑定的实现原理是怎样的?
Vue2
- new Vue() 首先执行初始化,对 data 执行响应化处理,这个过程发生 Observe 中
- 同时对模板执行编译,找到其中动态绑定的数据,从 data 中获取并初始化视图,这个过程发生在 Compile 中
- 同时定义⼀个更新函数和 Watcher,将来对应数据变化时 Watcher 会调用更新函数
- 由于 data 的某个 key 在⼀个视图中可能出现多次,所以每个 key 都需要⼀个管家 Dep 来管理多个 Watcher
- 将来 data 中数据⼀旦发生变化,会首先找到对应的 Dep,通知所有 Watcher 执行更新函数
点击显示代码👇
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
// ...
if (Dep.target) {
// 收集依赖
dep.depend();
}
return value;
},
set: function reactiveSetter(newVal) {
// ...
// 通知视图更新
dep.notify();
},
});
Vue3
改用 ES6 的 Proxy 解决以前使用 Object.defineProperty 所存在的一些问题
- 对象新增属性为什么不更新 - $set
- 数组变异 - 手动 observer,重写数组的方法
△ 188 次 什么是闭包,什么是立即执行函数,它的作用是什么?简单说一下闭包的使用场景
闭包指可以从内部函数访问外部函数的作用域
立即执行函数是一个在定义时就会立即执行的 JavaScript 函数
立即执行函数的作用
- 不必为函数命名,避免了污染全局变量
- IIFE 内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量
闭包的使用场景
- 节流防抖、柯里化
- 立即执行函数
△ 186 次 简述浏览器的渲染过程,重绘和重排在渲染过程中的哪一部分?
- 浏览器解析 URL 获取协议,主机,端口, path
- 浏览器获取主机 IP 地址
- 建立 TCP 连接,然后发送 HTTP 请求
- 服务器将响应报文通过 TCP 连接发送回浏览器,浏览器接收 HTTP 响应,根据资源类型决定如何处理
- 根据 HTML 解析出 DOM 树
- 根据 CSS 解析生成 CSS 规则树
- 结合 DOM 树和 CSS 规则树,生成渲染树
- 根据渲染树计算每一个节点的信息
- 根据计算好的信息绘制页面
重绘和重排
回流时,渲染流程会重新走一遍。重绘时,会重新计算样式,跳过中间步骤直接生成绘制列表。可见,重绘不一定导致回流,但回流一定发生了重绘。
△ 182 次 简述 diff 算法的实现机制和使用场景
diff 同层对比
新旧虚拟 DOM 对比的时候,diff 算法比较只会在同层级进行,不会跨层级比较。所以 diff 算法是:深度优先算法、时间复杂度:O(n)
diff 对比流程
当数据改变时,会触发 setter,并且通过 Dep.notify 去通知所有订阅者 Watcher,订阅者们就会调用 patch 方法,给真实 DOM 打补丁,更新相应的视图
patch 方法
patch 对比当前同层的虚拟节点是否为同一种类型的标签:
- 是:继续执行 patchVnode 方法进行深层比对
- 不是:没必要比对了,直接整个节点替换成新虚拟节点
sameVnode 会比较 key 值、标签名、是否为注释节点、是否定义了 data 和当标签为 input 时,type 必须相同
patchVnode 方法
主要判断:
- 如果 newVnode 和 oldVnode 是同一个对象,则 return
- 如果 newVnode 和 oldVnode 是文本节点,且文本不同,则将 el 中文本更新为 newVnode 的文本
- 如果 newVnode 和 oldVnode 都有子节点,且不同,则对比子节点
- 如果 oldVnode 有子节点而 newVnode 没有,则删除 el 的子节点
- 如果 oldVnode 没有子节点而 newVnode 有,则将 newVnode 的子节点真实化之后添加到 el
updateChildren 方法
子节点不完全一致,则调用 updateChildren,while 循环主要处理了以下五种情景:
- oldStart 和 newStart 使用 sameVnode 方法进行比较,sameVnode(oldStart, newStart)
- oldStart 和 newEnd 使用 sameVnode 方法进行比较,sameVnode(oldStart, newEnd)
- oldEnd 和 newStart 使用 sameVnode 方法进行比较,sameVnode(oldEnd, newStart)
- oldEnd 和 newEnd 使用 sameVnode 方法进行比较,sameVnode(oldEnd, newEnd)
- 如果以上逻辑都匹配不到,再把所有旧子节点的 key 做一个映射到旧节点下标的 key -> index 表,然后用新 vnode 的 key 去找出在旧节点中可以复用的位置
Vue3.0 diff
- PatchFlag。Vue3.0 对于不参与更新的元素,做静态标记并提示,只会被创建一次,在渲染时直接复用
- cacheHandlers。事件侦听器缓存
- 最长递增子序列
△ 178 次 简述 Javascript 中的防抖与节流的原理并尝试实现
点击显示代码👇
// 防抖
function debounce(fn, wait) {
let timeout = null;
return function () {
let context = this;
const args = arguments;
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => {
fn.apply(context, args);
}, wait);
};
}
// 节流
function throttle(fn, wait) {
let pre = new Date();
return function () {
let context = this;
let args = arguments;
let now = new Date();
if (now - pre >= wait) {
fn.apply(context, args);
pre = now;
}
};
}
△ 172 次 promise 有哪些状态?简述 promise.all 的实现原理
Promise 状态
- 待定(pending):初始状态,既没有被兑现,也没有被拒绝。
- 已兑现(fulfilled):意味着操作成功完成。
- 已拒绝(rejected):意味着操作失败。
promise.all 的实现原理
点击显示代码👇
function myPromiseAll(promises) {
return new Promise(function (resolve, reject) {
if (!isArray(promises)) {
return reject(new TypeError("arguments must be an array"));
}
var resolvedCounter = 0;
var promiseNum = promises.length;
var resolvedValues = new Array(promiseNum);
for (var i = 0; i < promiseNum; i++) {
(function (i) {
Promise.resolve(promises[i]).then(
function (value) {
resolvedCounter++;
resolvedValues[i] = value;
if (resolvedCounter === promiseNum) {
return resolve(resolvedValues);
}
},
function (reason) {
return reject(reason);
}
);
})(i);
}
});
}
△ 166 次 简述 CSS 盒模型
在 CSS 中,盒子模型可以分成:
- W3C 标准盒子模型 box-sizing: content-box
- IE 怪异盒子模型 box-sizing: border-box
标准盒子模型
- 盒子总宽度 = width + padding + border + margin
- 盒子总高度 = height + padding + border + margin
width/height 只是内容高度,不包含 padding 和 border 值
IE 怪异盒子模型
- 盒子总宽度 = width + margin;
- 盒子总高度 = height + margin;
width/height 包含了 padding 和 border 值
△ 142 次 简述 Javascript 原型以及原型链
△ 138 次 简述什么是 XSS 攻击以及 CSRF 攻击?
△ 134 次 CSS 的选择器优先级是怎样?
△ 130 次 简述常见异步编程方案 (promise, generator, async) 的原理
△ 126 次 简述浏览器的缓存机制
△ 116 次 简述 Vue 的生命周期
使用场景分析
- beforeCreate 执行时组件实例还未创建,通常用于插件开发中执行一些初始化任务
- created 组件初始化完毕,各种数据可以使用,常用于异步数据获取
- beforeMount 未执行渲染、更新,dom 未创建
- mounted 初始化结束,dom 已创建,可用于获取访问数据和 dom 元素
- beforeUpdate 更新前,可用于获取更新前各种状态
- updated 更新后,所有状态已是最新
- beforeDestroy 销毁前,可用于一些定时器或订阅的取消
- destroyed 组件已销毁,作用同上
△ 106 次 简述 ES6 的新特性
△ 92 次 简述强缓存与协商缓存的区别和使用场景
△ 90 次 简述 Vue 中 watch 和 computed 的区别
△ 90 次 简述 JavaScript 事件循环机制
△ 90 次 简述 Flex 布局的原理和使用场景
△ 86 次 简述 Javascript 的数据类型
△ 82 次 箭头函数和普通函数的区别是什么?
△ 82 次 移动端适配有哪些方案?
△ 76 次 localstorage 与 cookie 的区别是什么?
△ 76 次 const, let, var 关键字有什么区别?
△ 72 次 简述 Javascript 中 this 的指向有哪些
△ 72 次 简述图片的懒加载原理
△ 72 次 什么是跨域,什么情况下会发生跨域请求?
△ 70 次 简述浏览器的垃圾回收机制
△ 64 次 Javascript 中 == 与 === 的区别是什么?
△ 64 次 深拷贝与浅拷贝区别是什么?
△ 62 次 简述 Javascript 的柯里化与逆柯里化
△ 62 次 简述 Vue 和 React 的区别
△ 60 次 简述 React 的生命周期
△ 60 次 数组去重有哪些方式?手写数组去重
△ 58 次 如何实现元素水平垂直居中
△ 56 次 简述 CORS 的用途以及基本设置
△ 48 次 Vue 组件间是如何进行通信的?
△ 46 次 CSS 实现三列布局
△ 44 次 简述 Javascript 事件冒泡和事件捕获原理
△ 44 次 优化首屏渲染的方式有哪几种?
△ 44 次 简述 Webpack 的使用场景和基础原理
△ 42 次 简述 webpack 的打包流程
△ 42 次 sessionStorage 和 localStorage 有什么区别?
△ 40 次 简述 BFC 的原理及其使用场景
△ 40 次 简述 Dom 节点的不同操作方式
△ 39 次 MVC 模型和 MVVM 模型的区别
△ 38 次 简述输入 URL 到浏览器显示的流程
△ 36 次 如何实现深拷贝?
△ 36 次 rem 与 em 的区别以及使用场景
△ 36 次 CSS 实现两列布局
△ 34 次 简述虚拟 dom 实现原理
为什么需要虚拟 DOM
- 操作 DOM 的代价非常昂贵,频繁操作还是会出现页面卡顿,影响用户的体验
- 跨平台、服务端渲染
流程概括
- 用 js 模拟 DOM 树,并渲染这个 DOM 树
- 比较新老 DOM 树,得到比较的差异对象
- 把差异对象应用到渲染的 DOM 树