HTML
HTMLCollenction 和NodeList的区别
Node和Element
- DOM是一棵树,所有节点都是Node
- Node是Element的基类
- Element是其他HTML元素的基类,如HTMLDivElement
script标签的defer和async有什么区别
- 无:HTML暂停解析,下载JS,执行JS,再继续解析HTML
- defer:HTML继续解析,并行下载JS,HTML解析完毕再执行JS
- async:HTML继续解析,并行下载JS,执行JS,再解析HTML
prefetch和dns-prefetch分别是什么
- prefetch是资源预获取(和preload相关)
- dns-prefetch是DNS预查询(和preconnect相关)
前端攻击手段有哪些
XSS
- cross site script跨站脚本攻击
- 手段:黑客将JS代码插入到网页内容中,渲染时执行JS代码
- 预防:特殊字符替换
CSRF
- CROSS Site Request Forgery 跨站请求伪造
- 手段:黑客诱导用户去访问另一个网址的接口,伪造请求
- 预防:严格的跨域限制 + 验证码机制
预防
- 严格的跨域请求限制,如判断referrer
- 为cookie设置Samesite,禁止跨域传递cookie
- 关键接口使用短信验证码
点击劫持
- clickjacking
- 手段:诱导界面上有一个透明的iframe,诱导用户点击
- 预防:让iframe不能跨域加载
CSS
px % em rem vw vh的区别
- px基本单位,绝对单位(其他都是相对单位)
- %相对于父元素的宽度比例
- em 相对于font-size 自身优化,后是上一级父元素
- rem相对于根元素的font-size
- vw指视窗宽度的1%
- vH指视窗高度的1%
offsetHeight scrollHeight clientHeight的区别
- offsetHeight :border + padding + content
- clientHeight:padding + content
- scrollHeight:padding + 实际内容尺寸
减少重排
- 集中修改样式
- 修改之前先设置display:none,脱离文档流
- 使用BFC特性,不影响其他元素
- 频繁触发的(resize scroll) 使用节流和防抖
- 使用createDocumentFragment批量操作DOM
- 优化动画,使用CSS3和requestAnimationFrame
HTTP
Ajax Fetch Axios三者有什么区别
- 三者都是网络请求,但是不同维度
- Ajax 一种技术统称
- Fetch,一个具体的原生API
- Axios,第三方库
// 使用XMLHttpRequest实现一个请求
function ajax1(url, successFn) {
const xhr = new XMLHttpRequest()
xhr.open("GET", url, false)
xhr.onreadystatechange = function () {
// 这里的函数异步执行,可参考之前 JS 基础中的异步模块
if (xhr.readyState == 4) {
if (xhr.status == 200) {
successFn(xhr.responseText)
}
}
}
xhr.send(null)
}
function ajax2(url) {
return fetch(url).then(res => res.json())
}
请描述TCP三次握手和四次挥手
建立TCP连接
- 先建立连接(确保双方都有收发消息的能力)
- 再传输内容(如发送给一个get请求)
- 网络连接是TCP协议,传输内容是HTTP协议
- Client发包,Server接收。Server:有Client要找我
- Server发包,Client接收。Client:Server已经接收到信息
- Client发包,Server接收。Server:Client要准备发送了
四次挥手 --断开连接
- Client发包,Server接收。Server:Client已请求结束
- Server发包,Client接收。Client:Server已收到,我等待他关闭
- Server发包,Client接收。Client:Server此时可以关闭连接
- Client发包,Server接收。Server:可以关闭了
HTTP跨域请求时为何发送options请求
跨域的理解
- 浏览器同源策略
- 同源策略一般限制Ajax网络请求,不能跨域请求server
- 不会限制 link img script iframe加载第三方资源
产生的结果
- options请求,是跨域请求之前的预检查
- 浏览器自行发起,无需我们干预
- 不影响实际功能
网络请求中,token和cookie有什么区别
- cookie是HTTP规范,有跨域限制,配合session使用
- token是自定义的,无跨域限制,用于JWT
- cookie会默认被浏览器存储,而token需自己存储
cookie
- HTTP无状态,每次请求都要带cookie,以帮助识别身份
- 服务端也可以向客户端set-cookie,cookie大小限制4kb
- 默认有跨域限制:不可跨域共享,传递cookie
HTTP协议和UDP协议有什么区别
- HTTP是应用层,TCP UDP是传输层
- TCP有连接,有断开,稳定传输
- UDP无连接,无断开,不稳定传输,但效率高
HTTP协议1.0和1.1和2.0有什么区别
HTTP 1.0
- 最基础的HTTP协议
- 支持基础的GET POST方法
HTTP 1.1
- 缓存策略cache-control E-tag
- 支持长连接connection:keep-alive ,一次TCP连接多次请求
- 断点续传,状态码206
HTTP2.0
- 可压缩header,减少体积
- 多路复用,一次TCP连接中可以多个HTTP并行请求
- 服务端推送
什么是HTTPS中间人攻击,如何预防
中间人攻击是指攻击者通过与客户端和客户端的目标服务器同时建立连接,作为客户端和服务器的桥梁,处理双方的数据,整个会话期间的内容几乎是完全被攻击者控制的。攻击者可以拦截双方的会话并且插入新的数据内容
中间人攻击的过程:
- 服务器向客户端发送公钥。
- 攻击者截获公钥,保留在自己手上。
- 然后攻击者自己生成一个伪造的公钥,发给客户端。
- 客户端收到伪造的公钥后,生成加密哈希(此时加密内容是对称加解密秘钥) 值发给服务器。
- 攻击者获得加密哈希值,用自己的私钥解密获得真秘钥。
- 同时生成假的加密哈希值,发给服务器。
- 服务器用私钥解密获得假秘钥。
- 服务器用假秘钥加密传输信息。
WebSocket和HTTP协议有什么区别
- Websocket协议名是ws:// ,可双端发起请求
- Websocket没有跨域限制
- 通过send和onmessage通讯(http是req和res)
WebSocket和HTTP长轮询的区别
- HTTP长轮序:客户端发起请求,服务端阻塞,不会立即返回
- WebSocket:客户端可发起请求,服务端也可发起请求
从输入URL 到网页显示的完整过程
- 网络请求:DNS解析,HTTP请求
- 解析:DOM树,CSSOM树,render Tree
- 渲染:计算、绘制、同时执行JS
如何实现网页和iframe之间的通讯
- 使用postMessage进行请求
Javascript
防抖和节流
- 节流:限制执行频率,有节奏的执行
- 防抖:限制执行次数,多次密集的触发只执行一次
- 节流关注“过程”,防抖关注“结果”
防抖
防抖,防止抖动 “你先抖动着,啥时候停了再进行下一步”。
例如,一个输入框,等输入停止之后,再触发搜索
function debounce(fn, delay = 100) {
let timer = 0;
return function () {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, arguments);
timer = 0;
}, delay);
};
}
节流
节流,节省交互沟通 “别急,一个一个来,按时间来”
例如,drag或scrool期间触发某个回调,要设置一个时间间隔
function throttle(fn, delay = 100) {
let timer = 0
return function () {
if (timer) return
timer = setTimeout(() => {
fn.apply(this, arguments)
timer = 0
}, delay)
}
}
箭头函数
箭头函数的缺点
- 没有arguments
- 无法通过apply call bind 改变this
不适用箭头函数的场景
- 对象方法
- 对象原型
- 构造函数
- 动态上下文的回调函数
- Vue生命周期method
for..in 和for..of的区别
key和value
- for...in 便利得到key
- for...of遍历得到value
适用于不同的数据类型
- 遍历对象:for..in可以,for..of不可以
- 遍历Map Set:for...of可以,for..in不可以
- 遍历generator:for..of可以,for...in不可以
可枚举 可迭代
- for...in用于可枚举数据,如对象、数组、字符串
- for...of用于迭代数据,如数组、字符串、map、set
Js内存泄露如何检测,场景有哪些?
垃圾回收 GC
- 引入计数(之前 判断当前属性的值是否大于0)
- 标记清除(现在 js会定期从 window去遍历各个属性,看看是否能拿到对象 能则保留)
闭包是内存泄露吗
- 闭包不是内存泄露,闭包的数据是不会被垃圾回收的
如何检测内存泄露
使用chrom的 performance来进行查看
内存泄露的场景
以Vue为例
- 被全局变量,函数引用,组件销毁时未清除
- 被全局事件,定时器引用,组件销毁时未清除
- 被自定义事件引用,组件销毁时未清除
weakMap weakSet
是弱引用 ,使用的key是引用类型
浏览器和nodejs的事件循环有什么区别
- 浏览器和nodejs的event loop流程基本相同
- nodejs宏任务和微任务分类型,有优先级
nodejs宏任务类型和优先级
- Timers - setTimerout setInterval
- I/O callbacks - 处理网络、流、TCP的错误回调
- Idle,prepare - 闲置状态(nodejs内部使用)
- Poll轮询 - 执行poll中的I/O队列
- Check 检查 - 存储serImmediate回调
- Close callbacks - 关闭回调,如socket.on(‘close’)
nodejs微任务类型和优先级
- 包括:promise ,async/await ,process.nextTick
- 注意,process.nextTick优先级最高
for和forEach遍历数组哪一个更快
- for更快
- forEach每次都要创建一个函数来调用
- 函数需要独立的作用域,会有额外的开销
requestIdleCallback和requestAnimationFrame有什么区别
- requestAnimationFrame每次渲染完都会执行,高优
- requestIdleCallback空闲时才执行,低优
- 两者都是宏任务
Node
nodejs如何开启进程,进程如何通讯
- 开启子进程child_process.fork 和 cluster.fork
- 使用send和on收发消息
进程 process VS 线程 thread
- 进程:OS进行资源分配和调度的最小单位,有独立内存空间 ( 进程之间相互隔离)
- 线程,OS进行运算调度的最小单位,共享进程内存空间
- 一个进程可有多个线程
- JS 是单线程
Vue
Vue的交互方式
- props和$emit
- $parent
- 自定义事件
- $attrs
- $refs
- provide/inject
- vuex
虚拟dom真的很快吗?
- vdom并不快,JS直接操作DOM才是最快的
- 但 “数据驱动视图”要有合适的技术方案,不能全部DOM重建
Vue2和Vue3和React三者的diff 算法有什么区别
Vue-router的三种模式
- hash
- webHistory
- memoryHistory(V4之前叫做 abstract history)
使用Vue过程中遇到的坑
路由切换时scroll到顶部
- 列表缓存数据和scrollTop值
- 当再次返回列表时,渲染组件,执行scrollTo(XX)
- 使用keep-alive
如何统一监听Vue组件报错
- errorCaptured 监听下级组件错误,返回false会阻止向上传播
- errorHandler监听全局Vue组件的错误
- window。onerror监听其他js错误,如异步
errorCaptured生命周期
- 监听所有下级组件的错误
- 返回false会阻止向上传播
如果data中的一个属性名和methods中的一个方法名相同,在不报错的情况下,先执行哪个
执行顺序:props > methods > data > computed > watch
function initState (vm) {
vm._watchers = [];
var opts = vm.$options;
if (opts.props) { initProps(vm, opts.props); }
if (opts.methods) { initMethods(vm, opts.methods); }
if (opts.data) {
initData(vm);
} else {
observe(vm._data = {}, true /* asRootData */);
}
if (opts.computed) { initComputed(vm, opts.computed); }
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch);
}
}
$nextTick的实现原理
-
- 把回调函数放入callbacks等待执行
-
- 先判断Promise,在判断MutationObserver,在判断setImmediate,最后setTimeout。 将执行函数放到微任务或者宏任务中
-
- 并且通过 watcher.id 来判断 回调数组 中是否已经存在这个 watcher 的更新函数不存在,才 push
-
- 事件循环到了微任务或者宏任务,执行函数依次执行callbacks中的回调
vue用异步队列的方式来控制DOM更新和nextTick回调先后执行,microtask因为其高优先级特性,能确保队列中的微任务在一次事件循环前被执行完毕
手写一个JS函数,实现数组扁平化Array Flatten
//方式一
function arrayFlat(arr) {
let res = [];
arr.forEach((item) => {
res = res.concat(item);
});
return res;
}
//方式二
function arrayFlat2(arr) {
const res = []
arr.forEach(item => {
if (Array.isArray(item)) {
item.forEach(n => res.push(n))
} else {
res.push(item)
}
})
return res
}
手写一个getType函数,获取详细的数据类型
function getType(data) {
let type = Object.prototype.toString.call(data);
let index = type.indexOf(" ");
return type.slice(index + 1, -1).toLowerCase();
}
new一个对象的过程是什么,手写代码表示
function customNew(constructor, ...arg) {
let newObj = Object.create(constructor.prototype);
let res = constructor.apply(newObj, arg);
return typeof newObj === 'object'?res :newObj;
}
手写一个LazyMan,实现sleep机制
class lazyMan {
tasks = [];
constructor(name) {
this.name = name;
setTimeout(() => {
this.next();
});
}
next() {
let task = this.tasks.shift();
if (task) task();
}
eat(food) {
let task = () => {
let res = `${this.name} 正在吃 ${food} `;
this.next();
console.log(res);
};
this.tasks.push(task);
return this;
}
sleep(time) {
let task = () => {
setTimeout(() => {
console.log(`延迟${time}秒后执行`);
this.next();
}, time * 1000);
};
this.tasks.push(task);
return this;
}
}
instanceof原理是什么,请写代码表示
function myInstanceof(instance: any, origin: any): boolean {
if (instance == null) return false // null undefined
const type = typeof instance
if (type !== 'object' && type !== 'function') {
// 值类型
return false
}
let tempInstance = instance // 为了防止修改 instance
while (tempInstance) {
if (tempInstance.__proto__ === origin.prototype) {
return true // 配上了
}
// 未匹配
tempInstance = tempInstance.__proto__ // 顺着原型链,往上找
}
return false
}
手写函数bind功能
function customBind(content, ...bindArg) {
const self = this; //当前函数
return function (...arg) {
//合并参数
const newArg = bindArg.concat(arg);
self.apply(content, newArg);
};
}
手写函数call和apply功能
Function.prototype.customCall = function (content, ...arg) {
if (context == null) context = globalThis;
if (typeof context !== "object") context = new Object(context); // 值类型,变为对象
let fnKey = Symbol();
content[fnKey] = this; //当前函数
let res = content[fnKey](...arg);
delete content[fnKey];
return res;
};
手写EventBus自定义事件
class customEvent {
constructor() {
this.eventObj = {};
}
//注册函数
on(type, fn) {
if (!this.eventObj[type]) {
this.eventObj[type] = [];
}
this.eventObj[type].push(fn);
}
emit(type, ...arg) {
this.eventObj[type].filter((ev) => {
ev(...arg);
});
}
off(type, fn) {
if (fn) {
this.eventObj[type] = [];
} else {
let eventList = this.eventObj[type];
this.eventObj[type] = eventList.filter((ev) => ev != fn);
}
}
}
手写JS深拷贝
function cloneDeep(obj: any, map = new WeakMap()): any {
if (typeof obj !== 'object' || obj == null ) return obj
// 避免循环引用
const objFromMap = map.get(obj)
if (objFromMap) return objFromMap
let target: any = {}
map.set(obj, target)
// Map
if (obj instanceof Map) {
target = new Map()
obj.forEach((v, k) => {
const v1 = cloneDeep(v, map)
const k1 = cloneDeep(k, map)
target.set(k1, v1)
})
}
// Set
if (obj instanceof Set) {
target = new Set()
obj.forEach(v => {
const v1 = cloneDeep(v, map)
target.add(v1)
})
}
// Array
if (obj instanceof Array) {
target = obj.map(item => cloneDeep(item, map))
}
// Object
for (const key in obj) {
const val = obj[key]
const val1 = cloneDeep(val, map)
target[key] = val1
}
return target
}
项目
移动端H5点击有300ms延迟,该如何解决
- 此问题的背景是:double tap to zoom
- fastClick (插件)
- width=device-width (提示移动端不需要做300毫秒的延迟)
H5页面如何进行首屏优化
- 路由懒加载
- 服务端渲染SSR
- 图片懒加载
- 分页
- app预取
后端一次性返回10w条数据,你该如何渲染
- 虚拟列表
如果一个H5很慢,如何排查性能问题
- 分析性能指标,找到慢的原因
- 对症下药,解决问题
- 持续跟进,持续优化
- 使用performance 、 lighthouse
页面加载慢
- 优化服务端硬件配置,使用CDN
- 路由懒加载,大组件异步加载 -- 减少主包体积
- 优化HTTP缓存策略
网页渲染慢
- 优化服务端接口
- 继续分析,优化前端组件内部的逻辑
- 服务端渲染SSR
webpack
webpack的原理
babel-loader的主要原理
调动@babel/core这个包下面的transform方法,将源码通过presets预设来进行转换,然后生成新的代码、map和ast语法树传给下一个loader。这里的presets,比如@babel/preset-env这个预设其实就是各类插件的集合,基本上一个插件转换一个语法,比如箭头函数转换,有箭头函数转换的插件,这些插件集合就组成了预设。
tree-shaking的原理
ES6 Module引入进行静态分析,故而编译的时候正确判断到底加载了那些模块- 静态分析程序流,判断那些模块和变量未被使用或者引用,进而删除对应代码
common.js 和 es6 中模块引入的区别?
1、CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
2、CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
3、CommonJs 是单个值导出,ES6 Module可以导出多个
4、CommonJs 是动态语法可以写在判断里,ES6 Module 静态语法只能写在顶层
5、CommonJs 的 this 是当前模块,ES6 Module的 this 是 undefined
经典&高频面试题
- 1 github.com/ly2011/blog…
- 2 呆呆 juejin.cn/post/684490…
- 3 算法题 juejin.cn/post/684490…
- 4 github vue3js.cn/interview/v…