基础
ajax fetch axios
三者都属于网络请求,基于不同维度
- Ajax 属于技术统称
- Fetch,是一个具体的 API
- Axios,第三方库
XMLHttpRequest
function ajax(url) {
const xhr = new XMLHttpRequest();
xhr.open("GET", url, false);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
console.log(xhr.responseText);
}
}
};
xhr.send(null);
}
ajax("http://tzhen.vip");
fetch
ES6 新增 API,支持 Promise
fetch("http://127.0.0.1:8888/user/login", {
method: "post",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: Qs.stringify({
account: "18310612838",
password: md5("1234567890"),
}),
}).then((response) => {
console.log(response.json());
});
fetch("http://127.0.0.1:8888/user/list").then((response) => {
console.log(response.json());
});
axios
Axios 是一个库,对 ajax 进行封装,支持 Promise
防抖节流
- 节流:限制执行频率,有节奏的执行
- 防抖:限制执行次数,多次的密集执行只执行最后一次
- 节流关注过程,防抖关注结果
px % em rem vw/vh
- px 绝对单位
- % 相对于父元素
- em: 如果当前元素有设置 font-size,则为当前 font-size 大小,否则根据父元素
- rem:
- vw
- vh
什么时候不能用箭头函数
箭头函数的缺点
- 没有 arguments
- 不能用 apply、call、bind 改变 this
不适用场景
- 对象方法
- 对象原型
- 构造函数(没有显示原型-prototype)
- 动态上下文
- Vue 生命周期,method
TCP 三次握手和四次挥手
for in , for of, for await of
相同点
- 都可用于迭代数据
- for in 遍历的到的是 key
- for of 遍历的到的 value
- for of 是 es6API
不同点
- 遍历对象
- for in ✅
- for of ❌
- Map,Set
- for of ✅
- for in ❌
- generator
- for of ✅
- for in ❌
本质
- for in 用于可枚举数据
- for of 用于可迭代数据
offsetHeight/clientHeight/scrollHeight
- offsetHeight: border + padding + content
- clientHeight: padding + content
- scrollHeight: padding + 实际内容尺寸
HTMLCollection 和 NodeList
- Dom 是一颗树,所有节点都是 Node
- Node 是 Element 的基类
- Element 是其他 HTML 元素的基类,如 HTMLDivElement
Node 和 Element 的关系
class Node {}
class CharacterData extends Node {}
class Text extends CharacterData {}
class Comment extends CharacterData {}
class Element extends Node {}
class HTMLElement extends Element {}
class HTMLDivElement extends HTMLElement {}
<div id="d1">
<p>haha</p>
<!--注释-->>
</div>
<script>
const d1 = document.querySelector("#d1");
console.log(d1.children instanceof HTMLCollection);
console.log(d1.childNodes instanceof NodeList);
</script>
结论
- HTMLCollection 是 Element 的集合
- NodeList 是 Node 的集合
computed vs watch
Vue 通信的几种方式
- props/emit(父子)
- 自定义事件(事件总线)
- $attrs/$listeners
- $attrs: 容器对象,存放父组件传过来且子组件未使用 props 声明接收的数据
- $listeners: 实现跨组件数据传递 中间组件v-on=“$listeners”
- inheritAttrs: false; 禁止在 html 标签上显示传过来的数据
- $parent/$children|$refs
- provide 和 inject(跨层级)
// 父组件
provide: {
info: 'hello'
}
// 使用data中的数据
provide() {
return {
info: computed(() => ***.value)
}
}
// 子组件
inject: ['info']
- vuex
vuex action mutation
严格模式
- 全局变量必须先声明
- 禁止使用 width
- 禁止创建 eval 作用域
- 禁止 this 指向 window
- 函数参数不能重名
HTTP 跨域请求时为什么会发送 options 请求
跨域请求时,浏览器自动发出 options 请求(预检测),以检测服务端是否支持
知识深度
JS 内存泄漏
垃圾回收
- 什么是垃圾回收
- 引用计数
- 标记清除
内存泄漏场景
- 被全局变量、函数引用,组件销毁时未清除
- 被全局事件、定时器引用,组件销毁时未清除
- 被自定义事件引用,组件销毁时未清除
WeakMap WeakSet
见 coder 高级 JS
vdom 真的快吗
- vdom 与 js 直接操作 dom 相比,并没有原生操作快
- 数据驱动视图要有合适的方案,不能全部 DOM 重建
- vdom 是目前最合适的技术方案,不是因为快,而是合适
- 扩展:svelet,没有使用 vdom
for vs forEach 哪个速度更快
- for 更快
- forEach 需要创建函数,函数有独立的作用域,开销更大
Node 如何开启多进程,进程如何通信
为什么要开启多进程
- 开启多进程,能够充分利用多核 CPU 处理能力
- 单进程的内存存储有上限,多进程能够分担压力
开启多进程
- child_process.fork
- cluster.fork
- send 和 on 传递消息
JsBridge 原理
webview
是系统渲染网页的一个控件,可执行 javascript,可与页面 JavaScript 交互,实现混合开发
- Android 的 webview 内核
- <4.4: androidWebkit
- >=4.4: 采用了 chrome
- IOS 的 webview
- <ios8: UIWebView
- >=ios8: WKWebView
Native 与 Webview 通信
Native -> Webview
- Android
- <4.4:
loadUrl('javascript:' + jsCode)
;不支持回调 - >=4.4:
evaluateJavascript('javascript:' + jsCode, new ValueCallback<String>() {})
; 可以获取返回值,支持回调
- <4.4:
- IOS
- UIWebview: stringByEvaluatingJavascriptFromString
- WKWebView: evaluateJavascript:
WevView -> Native
URL Schema
- web 发送的请求都会经过 webview 组件,Native 重写 webview 里的方法,拦截 web 发送的请求,对请求格式进行判断
- 如果如何自定义 URL Schema 格式,则进行解析,调用原生 Native 方法
- 如果不符合自定义 URL Schema,则直接转发,请求真正的服务
- web 发起请求的方式
- a 标签: 需用户主动操作
- location.href:可能会引起页面跳转,丢失调用
- ajax:Android 没有相应的拦截方法
- iframe.src:推荐
- 拦截请求
- Android
- shouldOverrideUrlLoading
- IOS - UIWebview: shouldStartLoadWithRequest - WKWebView: decidePolicyForNavigationAction
优点:兼容好;缺点:URL 长度限制且不直观,数据格式限制,发起请求耗时
JS 注入
- Android
- 4.2+: addJavascriptInterface
- IOS
- UIWebview: JavaScriptCore
- WKWebView: WKScriptMessageHandler
requestIdleCallback 和 requestAnimationFrame
- requestAnimationFrame: 每次渲染完都会执行(帧变化), 高优先级
- requestIdleCallback: 空闲时执行, 低级先级
Vue 的每个生命周期都做了什么
- beforeCreate
new Vue
一个空白的 vue 实例data
method
尚未初始化
- crated
- 实例初始化完成,完成响应式绑定
data
method
已初始化完成
- beforeMount
- 编译模版,调用
render
生成 vDom - 还没有开始渲染 Dom
- mounted
- 完成
Dom
渲染 - 组件创建完成
- beforeUpdate(更新阶段)
data
发生变化之后,准备更新 Dom(尚未更新 Dom)
- updated(更新阶段)
data
发生变化,且Dom
更新完成- 不要在
updated
中修改data
,否则可能导致死循环
- beforeUnmount (销毁阶段)
- 组件进入销毁阶段(尚未销毁)
- 可移除、解绑一些全局事件、自定义事件
- mounted (销毁阶段)
- 组件已销毁
- 所有子组件已销毁
keep-alive
- activated
- 缓存组件被激活
- deactivated
- 缓存组件被隐藏
Vue3 Composition API 生命周期与 vue2 有何区别
- 用
setup
代替了beforeCreated
、created
- 使用
Hooks
函数形式,如mounted
->onMounted
Vue2 diff 和 Vue3 diff
- 同层比较
- tag 不同,则删除重建
- 子节点通过 key 区分
vue2
双端比较
vue3
最长递增子序列
react
仅右移
Vue Router 的 MemoryHistory
路由的三种模式
- hash
- history
- abstract(node 中使用,路由地址不会发生变化)
- vue2 中是 mode: abstract
- vue3 中 history: createMemoryHistory()
知识广度
移动端H5 click 300ms延迟
背景
double tap to zoom
android: < chrome 32
ios: < 9.3
解决
FastClick
window.addEventListener("load", () => {
FactClick.attach(document.body);
});
FastClick原理
- 监听
touchend
事件(touchstart/touchend先于click触发) - 使用DOM自定义事件模拟click
- 禁用默认的click事件
现代浏览器
<meta name="viewport" content="width=device-width"></meta>
cookie/session/token
cookie
- http无状态,通过cookie识别用户身份
- 服务端通过set-cookie自动设置,有4kb上限
- 默认有跨域限制:不可跨域共享、传递cookie
- 跨域传递:
- 前端设置 withCredentials=true
- 后端:Access-Control-Allow-Origin:源url(不能为*)
第三方cookie
- 现代浏览器禁止第三方JS设置cookie
- 其目的是打击第三方广告,保护用户隐私
- 新增属性:SameSite: Strict/Lax/None
- Strict: 严格模式;跨站点时任何情况都不发送cookie
- Lax: 宽松模式;a链接/preload/get请求发送
- None: 关闭
session
- cookie通常与session相辅相成
- 服务端保存session(如redis), set-cookie:sessionId=***
- 浏览器下次自动携带
- cookie + session 是常见的登录验证解决方案
token
- 需手动存储
- 自定义header,手动发送
- 无大小限制
session vs JWT 哪个更好
session
- 优点:
- 原理简单,容易上手
- 用户信息存储在服务端,可快速封禁用户
- 缺点:
- 占用服务端内存,硬件成本高
- 多进程,多服务器,不好同步(需使用第三方缓存:redis)
JWT
- 优点:
- 客户端存储,不占服务端内存
- 多进程,多服务器不受影响
- 没有跨域限制
- 跨端(cookie只适用浏览器)
- 缺点:
- 用户信息存储在客户端,无法快速封禁用户(需要服务端设置黑名单,在访问时拦掉)
- 服务端私钥泄漏,客户信息全部丢失
- token体积大于cookie,增加请求的数据量
如何选择
- 如需严格管理用户信息(保密,快速封禁);推荐:session
- 无特殊要求,使用JWT(如初创企业)
单点登录(SSO)
单点登录就是登录了一个网站,另一个网站也自动登录(如:登录百度网盘,则百度账号,百度贴吧等自动登录)
如何实现
- cookie
- 设置
domain=baidu.com
,则主域名下的cookie可以共享
- 设置
- token(第三方登录)
- 客户端访问a.com, 无token,重定向到c.com
- 在c.com登录之后,返回token,并在c.com设置token
- 返回至客户端, 存储token,携带token再次访问a.com
- a.com重定向到c.com, 校验token,返回用户信息至a.com
- -----------------------------
- 访问b.com, 无token,重定向至c.com
- 因为之前c.com已经存储token,则直接使用(没有过期)
- 返回用户信息至b.com, b.com存储token
原理
- 使用第三方c.com作为中间商
- 所有网站都重定向至c.com,在c.com完成验证, 并存储token
- 之后的a,b..其他网站都能在c.com中使用之前生成的token,并在之后自行存储
oAuth2.0
TCP 和 UDP
TCP
- 连接:三次握手
- 断开:四次挥手
- 稳定传输
UDP
- 无连接,无断开
- 不稳定传输,但效率高
- 应用场景:视频会议,语音通话
http1.0/http1.1/http2.0
http1.0
- 只支持GET
http1.1
- 支持GET、POST、PUT、DELETE、HEAD
- 缓存策略:catch-control, e-tag
- 支持长连接:connection: keep-alive
- 断点续传:状态码206
http2.0
- header头压缩
- 多路复用,一个TCP连接支持多个HTTP并行请求
- 服务器推送
https中间人攻击?如何防御?
https加密过程
- 中间人攻击
- 比如DNS劫持,冒充目标服务器
- 防御
- 购买正规CA证书
defer vs async
<script src="***"></script>
- 暂停解析,下载JS,执行,继续解析
<script src="***" defer></script>
- 继续解析,并行下载JS,解析完成,执行JS
<script src="***" async></script>
- 继续解析,并行下载JS,下载完立即执行JS,再继续解析
<script type="module""></script>
- 默认defer
prefetch vs dns-prefetch
prefetch vs preload
- preload: 资源在当前页面使用,优先加载
- prefetch: 资源在未来页面使用,空闲时加载
preconnect vs dns-prefetch
- preconnect: DNS预连接(TCP)
- dns-prefetch: DNS预查询
前端攻击手段
xss
- cross site script 跨站脚本攻击
- 将JS代码插入网页中,渲染时执行JS(比如评论区)
- 预防:标签替换
- vue/react
- vue: v-html
- react: dangerouslySetInnerHTML
CSRF
- cross site request forgery 伪造跨站请求
- 诱导用户访问另一个网站,伪造请求
- 基于用户在之前的网站上已登录,有了cookie
- 再访问一个网站时自动携带cookie
- 预防
- 严格的跨域限制,判断referrer来源
- 为cookie设置SameSite,禁止跨域传递
- 关键接口使用短信验证码再次验证
点击劫持
- 诱导界面上蒙一个透明的iframe,诱导用户点击
- 预防:X-Frame-Options: sameorigin (同域下允许加载iframe)
DDos
- 分布式拒绝服务
- 手段:分布式、大规模的流量访问,使服务器瘫痪
- 预防:硬件预防(阿里云WAF)
sql 注入
- 输入内容提交时,注入sql语句
- 预防:处理输入时,特殊字符替换
websocket vs http
websocket的连接过程
- 先发起一个http请求
- 成功之后升级到websocket,再进行通信
- Status Code: Switching Protocols
- Connection: Upgradge
- Upgradge: Websocket
区别
- WebSocket协议:ws:// ,可双端发起请求
- WebSocket没有跨域限制
- 通过send和onmessage通信(http,req, res)
websocket vs http长轮询
输入URL到页面展示的完整过程
-
网络请求
- DNS解析,得到IP
- TCP三次握手
- 发起HTTP请求
- 得到响应内容(HTML字符串)
-
解析
- DOM树
- CSSOM树
- 结合生成Render tree
-
渲染、计算、绘制
重绘repaint 和 重排reflow
重绘
- 元素外观改变,如:颜色,背景色
- 元素尺寸、定位不变,不会影响其他元素位置
重排
- 重新计算尺寸和布局,可能会影响其他元素的位置
- 如新增、删除元素,改变宽高等
区别
- 重排比重绘影响更大,消耗更大
- 尽量避免无意义重排
减少重排
- 统一修改样式,或切换class
- 修改之前修改 display: none, 脱离文档流
- 利用BFC
- 使用防抖/节流
- createDocumentFragment 批量操作DOM
- 使用css3和requestAnimationFrame
多标签通信
- websocket
- 支持跨域
- localStorage
- 同域限制
- window.addEventListener(‘storage’, event => { console.log(event.key, event.newValue) })
- sharedworker
iframe 通信
- 发送:
- postMessage
- 接受
- window.addEventListener(‘message’, event => event.origin)
koa2 洋葱模型
- nodejs 流行框架
- 中间件组织代码
- 多个中间件以洋葱模型运行
手写题
- 数组拍平
function flatten(arr) {
if (!Array.isArray(arr)) return arr;
let result = [];
arr.forEach((item) => {
if (Array.isArray(item)) {
const child = flatten(item);
result = result.concat(child);
} else {
result.push(item);
}
});
return result;
}
const arr = [1, [2, 3, [4]]];
const result = flatten(arr);
console.log(result);
- 数据类型
Object.prototype.toString.
- 深度优先
// 遍历如下DOM
<div class="box">
<ul>
<li>item</li>
</ul>
<h1>hello</h1>
<!--please say hello-->
<h2>welcome hello</h2>
</div>
function visitNode(node) {
if (node instanceof Comment) {
console.log("comment-", node);
}
if (node instanceof Text) {
console.log("Text-", node);
}
if (node instanceof HTMLElement) {
console.log("Element-", node);
}
}
function depthFirstTraverse1(root) {
visitNode(root);
const childNodes = root.childNodes;
if (childNodes.length > 0) {
childNodes.forEach((child) => {
depthFirstTraverse1(root);
});
}
}
不使用递归如何实现
- 广度优先
function breakFirstTraverse1(root) {
const queue = [];
// 根节点入队
queue.unshift(root);
while (queue.length > 0) {
const currentNode = queue.pop();
if (currentNode === null) return;
if (currentNode.length > 0) {
currentNode.forEach((child) => {
queue.unshift(child);
});
}
}
}