△ 338 次 手写题库
△ 200 次 Vue 中双向数据绑定的实现原理是怎样的?
Vue2
- new Vue() 首先执行初始化,对 data 执行响应化处理,这个过程发生 Observe 中
- 同时对模板执行编译,找到其中动态绑定的数据,从 data 中获取并初始化视图,这个过程发生在 Compile 中
- 同时定义⼀个更新函数和 Watcher,将来对应数据变化时 Watcher 会调用更新函数
- 由于 data 的某个 key 在⼀个视图中可能出现多次,所以每个 key 都需要⼀个管家 Dep 来管理多个 Watcher
- 将来 data 中数据⼀旦发生变化,会首先找到对应的 Dep,通知所有 Watcher 执行更新函数
点击显示代码👇
js
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 中的防抖与节流的原理并尝试实现
点击显示代码👇
js
// 防抖
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 的实现原理
点击显示代码👇
js
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 原型以及原型链
原型
实例的 proto 指向构造函数的 prototype
原型链
- constructor 指向构造函数,每个对象的 proto 指向创建它的构造函数的 prototype,⽽构造函数的 prototype 也有 proto 指向他的⽗辈或者是 Object,当查找⼀个对象中不存在的属性时,会去它的 proto、proto 中的 proto 中进⾏寻找,直到找到或者是 null 为⽌
- instanceof 判断对象的 proto 和构造函数的 prototype 是不是同⼀个地址
- Object.setPrototypeOf 改变对象的 proto
△ 138 次 简述什么是 XSS 攻击以及 CSRF 攻击?
XSS 跨站脚本攻击
- 可能是写⼀个死循环、获取 cookie 登录
- 监听⽤户⾏为
- 修改 DOM 伪造登录表单
- 页⾯⽣成浮窗⼴告
XSS 防范
-
对输⼊转码过滤
-
利⽤ CSP 浏览器内容安全策略,核⼼就是服务器决定浏览器加载哪些资源,功能:
- 限制其他域下的资源加载
- 禁⽌向其它域提交数据
- 提供上报机制
-
HttpOnly HttpOnly 类型的 cookie 阻⽌ JS 对其的访问(标记或授权对话) 阻⽌ XSS 攻击:服务器对脚本进⾏过滤或转码,利⽤ CSP 策略,使⽤ HttpOnly;
CSRF-跨站伪造请求(钓鱼)
攻击者诱导受害者进⼊第三⽅⽹站,在第三⽅⽹站中,向被攻击⽹站发送跨站请求。利⽤受害者在被攻击⽹站已经 获取的注册凭证(⽐如 cookie) ,绕过后台的⽤户验证,因此可以冒充⽤户对被攻击的⽹站执⾏某项操作。
CSRF 防范
-
SameSite SameSite 可以设置为三个值,Strict、Lax 和 None。 a. 在 Strict 模式下,浏览器完全禁⽌第三⽅请求携带 Cookie。⽐如请求 sanyuan.com ⽹站只能在 sanyuan.com 域名当 中请求才能携带 Cookie,在其他⽹站请求都不能。 b. 在 Lax 模式,宽松⼀点,但是只能在 get ⽅法提交表单况或者 a 标签发送 get 请求的情况下可以携带 Cookie,其 他情况均不能。 c. 在 None 模式下,也就是默认模式,请求会⾃动携带上 Cookie。
-
验证来源站点
请求头中的 origin 和 referer origin 只包含域名信息,referer 包含具体的 URL 路径。
-
CSRF Token
利⽤ token(后端⽣成的⼀个唯⼀登陆态,传给前端保存)每次前端请求都会带 token,后端检验通过才同意请求。 敏感操作需要确认 敏感信息的 cookie 只能有较短的⽣命周期
-
安全框架
如 Spring Security。
△ 134 次 CSS 的选择器优先级是怎样?
选择器 | 格式 | 优先级权重 |
---|---|---|
id 选择器 | #id | 100 |
类选择器 | .classname | 10 |
属性选择器 | a[ref = "eee"] | 10 |
伪类选择器 | li:last-child | 10 |
标签选择器 | div | 1 |
伪元素选择器 | li:after | 1 |
相邻兄弟选择器 | h1 + p | 0 |
⼦选择器 | ul > li | 0 |
后代选择器 | li a | 0 |
通配符选择器 | * | 0 |