前端知识谱-查缺补漏(更新中)
知识点整理汇总,查缺补漏。
# JS/TS
# 什么是原型/原型链?
JavaScript 中的每个对象都有一个原型(prototype),用于实现对象之间的继承关系,这个原型对象也有自己的原型对象。这就形成了原型链(prototype chain)。 当访问对象的某个属性或方法时,如果该对象本身没有该属性或方法,JavaScript 引擎会沿着该对象的原型链向上查找,直到找到该属性或方法为止。
# 什么是闭包?使用场景有哪些? 怎么清理闭包的作用域?
闭包是一种特殊的函数,它可以访问其他函数作用域中的变量。
具体来说,它是由函数及其相关变量组合而成的包裹体,即相关的函数引用了其自身定义时的词法环境,形成了一个闭合的作用域。
- 封装私有变量和方法。
function createCounter() {
let count = 0;
function increment() {
count++;
console.log(count);
}
return increment;
}
let counter = createCounter();
counter(); // 输出 1
counter(); // 输出 2
- 实现局部变量常驻内存,可用于缓存数据。
function createRequset() {
let cache = {}; // 缓存数据
function request(url) {
if (url in cache) {
console.log(`从缓存中获取 ${url}`);
return cache[url];
} else {
console.log(`发送请求获取 ${url}`);
let result = Math.random();
cache[url] = result;
return result;
}
}
return request;
}
- 实现柯里化函数,用于函数的参数复用。
function add(x, y) {
return x + y;
}
function curryingAdd(x) {
return function (y) {
return add(x, y);
}
}
let curryingAdd1 = curryingAdd(1);
console.log(curryingAdd1(2)); // 输出 3
console.log(curryingAdd1(3)); // 输出 4
- 避免污染全局变量 实现工具库的插件机制,从而使插件中的代码分离出来,并避免命名冲突。 juqery 立即执行函数生成的闭包实现的变量隔离。
清理闭包
- 在退出函数之前,将不使用的局部变量全部删除;
- 手动去除外部引用
# this关键字的指向是什么?
在js中,this关键字指向当前正在执行该函数的对象。 作为对象属性被调用时指向该对象。
- 在全局环境中,全局对象 window 被视为默认的 this 值。
- 在函数内部,使用函数名调用函数时,this 会指向全局对象 window。
- 在函数内部,当该函数作为一个对象的方法被调用时,this 将会指向该对象。
- 在函数内部,当该函数作为构造函数使用时,this 将会指向新创建的对象。
- 在函数内部,当使用 apply、call 或 bind 方法调用函数时,this 将会被显式绑定到函数调用时的指定对象上。
# call appy bind的作用和区别
call、apply 和 bind 都是用来改变函数执行时的 this 指向的方法,它们的作用和区别如下:
- call方法可以改变函数的this指向,并立即调用函数,第一个参数为函数执行时的this指向,其他参数为传递给函数的参数。
- apply方法和call方法作用相同,区别在于接受参数的方式不同,apply方法接受一个数组作为函数的参数。
- bind方法也可以改变函数的this指向,但是它会返回一个新函数,返回的新函数可以在后续任何时候调用,包含原始函数所使用的this值。
# 什么是同步和异步?
同步和异步是针对程序执行的顺序而言的,简单来说:
- 同步:必须执行完才能进行下个任务。
- 异步:任务执行,不会阻止后续代码的执行。异步任务通常会通过回调函数、Promise对象或者async/await语法等方式来处理其结果。
# 浏览器的事件循环?
js是单线程非阻塞的, 这是js执行基础机制, js代码从上而下顺序执行,遇到同步代码直接执行,异步任务会被放进的宏任务和微任务队列
- 先按同步代码顺序运行
- 开始清空微任务队列
- 开始清空宏任务队列(执行一个宏任务,把相关微任务添加入微任务队列)
- 开始清空微任务队列(上一个执行宏任务中加入队列的微任务一次性全部执行完成)
- 开始清空宏任务队列(执行下一个宏任务,把相关微任务添加入微任务队列)
for (macroTask of macroTaskQueue) {// 宏任务队列
// 1. Handle current MACRO-TASK
handleMacroTask();
// 2. Handle all MICRO-TASK
for (microTask of microTaskQueue) { // 微任务队列
handleMicroTask(microTask);
}
}
- 宏任务task:script中代码、setTimeout、setInterval、I/O、UI render,requestAnimationFrame。
- 微任务microtask: Promise.then catch finally、Object.observe、MutationObserver。
- await关键字与Promise.then效果类似 定时器线程为单独线程,到时间会推送至宏任务队列,等待事件循环执行。
所以,如果遇到耗时任务,计时器是无法保证准时的。
# 为什么js是单线程
单线程简化了代码设计与调试。
为了避免多个线程中同时访问或修改相同数据时造成竞争和死锁等问题。
# promise的使用是为了解决什么问题?promise底层是怎么设计的?
promise是为了解决回调嵌套和异步任务代码管理的问题。传统回调嵌套会导致代码难以阅读和维护;promise可以将多个任务组合, 还支持链式调用和错误处理机制,是代码更简洁易读。
Promise底层是基于事件循环机制实现,创建时会立即执行一个函数,该函数接收两个参数resolve和reject,通过调用他们,可以将Promise 的状态从pending变为fullfilled和rejected。状态改变时,会触发微任务队列中的回调函数,在时间循环中执行。
promise有then方法用来接收执行结果,还有catch用来捕获错误,finally用来执行清理。
基于promise设计思想,产生了await和async语法,使异步代码看起来像同步代码,更易于理解和使用。
# promise 错误如何捕获
- promise 内部消化,catch掉
- 使用async和await将异步错误转同步错误再由try catch捕获
- catch方法可以捕获 前面任意then方法抛出的错误
- then方法的第二个参数只能捕获上一层then方法抛出的错误,而不能捕获当前then方法里面抛出的错误
- then的第二个参数本来就是用来处理上一层状态为失败的
# Promise 有哪些潜在的问题?
# 简单说说 js 中有哪几种内存泄露的情况
- 全局变量未销毁 - 无意中创建的全局变量
- 定时器未清除
- 循环引用,两个对象之间如果存在互相引用,垃圾回收将无法回收他们- 避免循环引用
- dom元素的引用 - 及时解除引用
- 创建的闭包中引用大量外部对象时 - 及时解除对闭包的引用
- 大量数据的缓存 - 避免长时间大数据量缓存
# 什么是变量提升?为什么会有变量提升?什么是暂时性死区
js执行过程中,会将变量或者函数声明提升到其所在作用域的顶部,但不会提升赋值。 这导致变量在声明之前就可以访问该变量,但其值为undefined。
console.log(myVar); // 输出: undefined
var myVar = "Hello World!";
原因是,js采用的是解释执行,执行代码分为两步,编译+执行; 编译阶段扫描代码,并将变量提升到作用域顶部,执行阶段再按顺序执行代码。
早期这么做主要是为了提高性能以及容错。
变量提升会导致未知的变量覆盖问题。
ES6中let const声明块级作用域,可以避免这个问题。
let const块级作用域没有变量提升,定义变量之前先访问会报错,这就叫暂时性死区;
具体的,在块级作用域声明的变量会先创建,但此时还未进行词法绑定,所以是不能访问的, 因此,在创建变量到变量可以被访问这段时间,就称为暂时死区。
# 什么是作用域与作用域链?函数执行上下文包含了哪些内容?
作用域是指变量或函数的可访问范围。
作用域链时查找变量或函数的一种机制,他根据函数嵌套关系形成,从当前作用域向上查找,直到查找到目标或到达全局作用域。
作用域分为三种
- 全局作用域 - 包含执行环境所有对象和函数,任何地方都可以访问
- 函数作用域 - 函数创建的独立作用域,只在函数内部可访问
- 块级作用域 - es6新增的作用域,let const声明,其作用域只在代码块内。
函数执行上下文就是js 的执行环境,它包括 this的值、变量、对象和函数 变量或函数的上下文决定了它们可以访问哪些数据,以及它们的行为。
# JavaScript有几种方法判断变量的类型?
- typeof 检测变量数据类型,不能区分对象类型 [] {} 均返回 object
- instanceof 判断属于某个类的之类
- Object.prototype.toString.call('') Object原型方法,返回对象的具体类型;
- Array.isArray 数组判断
# ES6有哪些新特性
- let const - 声明块级作用域的变量
- 箭头函数 - 简化函数的写法,并且自动绑定this关键字
- 解构赋值,模版字符串
- promise - 解决回调地狱和代码复杂度的问题
- 模块化esm
- 函数默认参数
- class类
# 浏览器兼容ES6的方法
- babel 转译
- polyfill 补丁库实现
# 防抖和节流的原理和使用场景
均为了处理高频操作而做的频率控制优化;
- 节流 - 类似公交发车,相同时间间隔发车; 例如监听页面滚动,监听鼠标位置
- 防抖 - 类似手机熄屏,一直点屏幕,就不会熄屏;监听输入实时搜索,监听窗口尺寸变化,防重复提交
# 谈谈浅拷贝与深拷贝,如何实现一个深拷贝?
- 浅拷贝 属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。 ...扩展运算符;Object.assign()
- 深拷贝 一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。
- 变量赋值
- 对基本数据类型的赋值,两个变量相互不影响;
- 引用类型:两个变量指向同一个对象,改变变量a的值会影响变量b的值,哪怕改变的只是对象a中的基础数据类型 遍历对象的属性,递归深层属性,并按照不同的数据类型相应处理
# 谈谈对函数式编程的理解?
函数式编程是一种编程方式,他更注重过程,确定的输入会有确定的输出; 基于细粒度的函数封装,他可以更好的复用,理论上可以减少大量重复代码。 他可以通过组合实现复杂功能。
函数式编程并不能代替面向对象语言,并不是适合除了数学计算分析等大部分的场景,从系统设计的角度来讲,使用面向对象设计还是更亦理解的方式。 函数式编程的优点:
- 代码量少,比如文中的例子就是最直接的展示。
- 因为都是“无状态函数”,固定输入产生固定输出,那么单元测试和调试都很简单
- 同样是因为无状态,所以适合并发编程,不用担心并发安全问题。
缺点:
- 滥用函数式编程会导致代码难以理解,比如一大型项目有大量高阶函数混着变量,开发人员随意把函数当作参数和返回值,项目会变得很难维护。
- 函数式编程会导致大量递归,算法效率太低。
# 设计模式有哪些,用过哪些?
设计模式的作用就是提取实现中变与不变的部分,分离出不变的部分总结出经验范式。
- 单例模式,保证一个类仅有一个实例
- vuex 全局缓存对象 全局工具类 全局弹窗类
- 代理模式 - 对原有对象函数进行封装包装,不修改原有对象,保持一致行为;
- 优点是,原对象不修改,保证其他地方不影响;单一职责原则;新功能修改只需要去掉代理包装
- 例如,事件委托,computed,防抖节流函数, axios对不同请求方法的封装封装
- 装饰器模式 用于增强原有对象行为
- 区别于代理模式,装饰器可以多层装饰,代理一般封装一层调用
- 适配器模式 解决对象不匹配,中间层做转换用
- 数据格式转换,例如后端数据转为echarts配置
- 浏览器兼容性,不同浏览器api封装为统一通用接口
- 策略模式 - 分离业务代码,用于if分支过多时,新增策略时就可以只新增类型,不修改逻辑
- 表单验证规则,验证逻辑相同,但验证规则很多
- 发布订阅模式 消息发布到订阅中心,订阅者通过订阅消息获取消息。
- evtBus
- 缺点是,事件订阅分散,不知道谁订阅了,难以维护
- 观察者模式 - 多个观察者,与被观察者;某个对象变化,然后通知其他观察者对象
- 类似微信订阅号,有消息推给观察者
- 责任链模式 将请求与处理者解耦,使多个处理者可以依次处理请求,直到处理成功为止
- 原型链,作用域链,事件冒泡;-- 找到为止,捕捉到为止
# 理解【观察者模式】和【发布订阅】的区别
- 观察者模式 有观察者和被观察者 ,可以有多个观察者去观察这个对象。二者的关系是通过被观察者主动建立的,被观察者有三个方法——添加、移除、通知观察者。
class Subject {
constructor() {
this.observerList = [];
}
addObserver(observer) {
this.observerList.push(observer);
}
removeObserver(observer) {
const index = this.observerList.findIndex(o => o.name === observer.name);
this.observerList.splice(index, 1);
}
notifyObservers(message) {
const observers = this.observeList;
observers.forEach(observer => observer.notified(message));
}
}
class Observer {
constructor(name, subject) {
this.name = name;
if (subject) {
subject.addObserver(this);
}
}
notified(message) {
console.log(this.name, 'got message', message);
}
}
- 发布订阅 - 分为发布者/订阅者/调度中心三部分;发布者将消息交给调度,订阅者根据自己需求,按需订阅
class PubSub {
constructor() {
this.messages = {};
this.listeners = {};
}
publish(type, content) {
const existContent = this.messages[type];
if (!existContent) {
this.messages[type] = [];
}
this.messages[type].push(content);
}
subscribe(type, cb) {
const existListener = this.listeners[type];
if (!existListener) {
this.listeners[type] = [];
}
this.listeners[type].push(cb);
}
notify(type) {
const messages = this.messages[type];
const subscribers = this.listeners[type] || [];
subscribers.forEach((cb, index) => cb(messages[index]));
}
}
class Publisher {
constructor(name, context) {
this.name = name;
this.context = context;
}
publish(type, content) {
this.context.publish(type, content);
}
}
class Subscriber {
constructor(name, context) {
this.name = name;
this.context = context;
}
subscribe(type, cb) {
this.context.subscribe(type, cb);
}
}
# ts高级类型
- 字面量类型
type Direction = "north" | "east" | "south" | "west";
- 联合类型 多个类型的并集
type UnionType = string | number;
- 交叉类型 类型交集,合并接口,合并对象
type Types = type1 & type2 & .. & .. & typeN
- 泛型 定义函数时,使用类型变量指定具体类型 ,一个函数可以支持多种类型的数据
const showType<T>(args: T)=>{}
- pick类型 - 从已有类型中选择一些属性,创建新类型
Pick<PickType, 'firstName' | 'lastName'>
- Omit类型 - 从已有类型中剔除一些属性,创建新类型
Omit<PickType, 'firstName' | 'lastName'>
- Extract类型 - 从已有类型取交集属性,创建新类型
Extract<keyof FirstType, keyof SecondType>
- Exclude类型 - 从已有类型剔除交集属性,创建新类型
Exclude<keyof FirstType, keyof SecondType>
# 说说单例模式的优缺点;
全局缓存、全局状态管理等等这些只需要一个对象, 就可以使用单例模式.一个变量确保实例只创建一次就行
# async await 是什么?它有哪些作用?
async用于声明一个函数是异步的,await用于等待异步方法执行完成;他将异步方法变成了看起来同步方法
# 请描述一下 ES6 中的 class 类
# 箭头函数有哪些特征,请简单描述一下它?
es6引入的新特性;
- 他可以简化函数语法
- 箭头函数没有自己的this,他会捕获外部作用域的this
- 没有agruments参数列表
# 前端国际化是什么?
- 针对不同语言,打包为不同的项目编译包,在 编译过程 国际化;
- 使用一个项目,通过 id 和语言来映射最终文案,在 运行过程 国际化;
# html/css
# rem, em区别
都是相对单位
- rem 相对于根元素字号大小
- em 相对于父元素
# 伪类和伪元素及使用场景?
- 伪元素,在元素内部创建的虚拟子元素。::after ::before
- 伪类, 表示元素处于特殊状态,:hover,:checked,:nth-child等等
# 如何实现水平垂直居中,文字/图片居中各种方案
- flex布局
.dox {
display: flex;
justify-content: center;
align-items: center;
}
- grid布局
.box {
width: 200px;
height: 200px;
border: 1px solid red;
display: grid;
}
.children-box {
width: 100px;
height: 100px;
background: yellow;
margin: auto;
}
- 绝对定位+transform -50%
.box {
width: 200px;
height: 200px;
border: 1px solid red;
position: relative;
}
.children-box {
position: absolute;
background: yellow;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
- table-cell
.box {
height: 200px;
width: 200px;
display: table-cell;
text-align: center;
border: 1px solid #ccc;
vertical-align: middle;
}
- 行内元素 line-height 与 height 相等;text-align:center
# 谈谈html5语义化的理解
指以恰当的html标记来描述页面结构和含义;可以
- 提高页面的可读性,页面结构更清晰
- 可访问性,方便其他设备解析访问,如屏幕阅读器、盲人阅读器、移动设备
- 利于seo排名,有助于爬虫抓取解析内容,
- 便于开发后期维护;助于理解功能
语义化标签,header nav section main footer,h1,li等
# iframe有什么优点、缺点?
页面隔离,独立加载,不影响主页面展示
独立嵌套,方便整合外部或不同来源的内容
加载时间太慢,资源浪费
不易seo,影响seo收录优化
布局局限,弹窗覆盖层无法全屏等
内存占用高,页面异常可能导致主页面崩溃
# 微前端隔离方案有哪些,优缺点是什么?
- iframe - 每个子页面隔离在单独iframe中
- 优点:兼容性好,隔离度高,互相影响小;
- 缺点:加载速度慢,额外的内存开销,父子通讯比较麻烦,浏览器前进后退问题,弹窗无法全屏
- web components 每个之应用封装成自定义元素;用shadow DOM隔离样式与结构
- 优点:封装性好,可重用性好
- 缺点:兼容性不够高
- webapck - 模块联邦
- qiankun - 单页微前端框架,基于single-spa封装-完整的解决方案
# 谈谈对webComponents的理解
浏览器元素层面的组件化,它允许创建可重用的自定义元素;它包含了三个组成部分
- Custom Element,用户自定义元素
- ShadowDOM,这部分 DOM 默认与外部 DOM 隔离,内部任何代码都无法影响外部。类似iframe,但更轻量。
- HTML template,定义可复用的html片段,作为组件模版
有了这三项技术,webComponents可以实现真正的组件化与模块。
# script 标签中defer 和async 的区别
都是script标签属性
- defer 用于异步加载脚本,并在dom构建完成之后执行;一般用于保证顺序执行的脚本
- async 同样用于异步加载脚本,但是下载完会立即执行;一般用于外部脚本,无依赖不关心顺序执行
# 行内元素和块级元素什么区别,然后怎么相互转换
display属性能够将三者任意转换
块级元素 1.总是从新的一行开始,即各个块级元素独占一行,默认垂直向下排列; 2.高度、宽度、margin及padding都是可控的,设置有效,有边距效果; 3.宽度没有设置时,默认为100%; 4.块级元素中可以包含块级元素和行内元素。
行内元素 1.和其他元素都在一行,即行内元素和其他行内元素都会在一条水平线上排列; 2.高度、宽度是不可控的,设置无效,由内容决定。 3.根据标签语义化的理念,行内元素最好只包含行内元素,不包含块级元素。
# 谈谈css盒模型
css盒模型本质是一个盒子,它包括边距,边框,填充,以及实际内容区域。
- 标准模型 -宽高实际指定的是内容区域
- IE模型(区别),在于宽高包含了填充和边框部分 可以通过 box-sizing:conent-box; box-sizing:border-box;
dom.style.width / height
dom.currentStyle.width / height(ie支持)
window.getComputedStyle(dom).width / height;
dom.getBoundingClientRect().width / height;
# BFC是什么?
块级格式上下文,BFC是一个完全独立的渲染空间(布局环境),让空间里的子元素不会影响到外面的布局 下面的属性可以触发BFC
- overflow: hidden
- display: inline-block
- position: absolute
- position: fixed
- display: table-cell
- display: flex 规则
- BFC就是一个块级元素,块级元素会在垂直方向一个接一个的排列
- BFC就是页面中的一个隔离的独立容器,容器里的标签不会影响到外部标签
- 垂直方向的距离由margin决定, 属于同一个BFC的两个相邻的标签外边距会发生重叠
- 计算BFC的高度时,浮动元素也参与计算 解决边距重叠问题,可以float实现两栏布局
# 如何查找性能瓶颈
- 跑一次perfermance
- lighthouse
- 资源加载 -> 首次需要下载的量是否过大 是否需要懒加载 缓存配置是怎样的 单页面是否过大需要分页
- 网络请求 -> 请求时间是否过长 数据是否可以缓存 需要计算的数据是放到服务端还是客户端
- 运行卡顿 -> 运行环境的支持版本如何 运行环境的性能如何 是否存在内存泄漏 dom元素是否过多 动画安排是否合理
# 用flex实现九宫格讲思路
/*flex*/
ul {
display: flex;
flex-wrap: wrap;
/ / 换行 width: 100 %;
height: 100%;
}
li {
width: 30%;
height: 30%;
margin-right: 5%;
margin-bottom: 5%;
}
li:nth-of-type(3n) {
margin-right: 0;
/ / 第三无右边距
}
li:nth-of-type(n+7) {
margin-bottom: 0;
/ / 789 个无下边距
}
/*// gird*/
ul {
width: 100%;
height: 100%;
display: grid;
grid-template-columns: 30% 30% 30%;
grid-template-rows: 30% 30% 30%;
grid-gap: 5%;
}
# css如何实现切换主题
- css变量,类名切换
- css覆盖,link标签动态引入
- 引入所有主题,类名切换
# 移动端 1px 问题
- 边框图片 - border颜色变了就得重新制作图片;圆角会比较模糊。
- 使用伪元素border -配合secal 缩放
# 移动端适配方案
- viewport 适配 ;initial-scale = 屏幕的宽度 / 设计稿的宽度 适配其他屏幕,需要动态的设置 initial-scale 的值
- rem 适配 计算公示
- 弹性盒适配(合理布局)
# 使默认不可编辑标签变得可编辑 contenteditable=’true’
# rem和vw的使用场景
# src 和 href 的区别?
- src用于加载资源,图片,脚本,iframe等,video等
- href用于指定url,如锚点,页面跳转等;
# 前端可视化
# canvas 优化绘制性能
- requestAnimationFrame
- 根据变化频率分层绘制,多个canvas使用不同的堆叠次序,节省重绘
- 清除画布尽量使用 clearRect,性能clearRect > fillRect > canvas.width=canvas.width;
- 绘制图片等部分区域时,可先裁剪准备好再绘制,可节省canvas裁剪工作
- 尽量集中绘制,如
context.beginPath();
for (var i = 0; i < points.length - 1; i++) {
var p1 = points[i];
var p2 = points[i + 1];
// context.beginPath(); // 不建议
context.moveTo(p1.x, p1.y);
context.lineTo(p2.x, p2.y);
// context.stroke(); // 不建议
}
context.stroke();
- webWorker 来处理一些比较耗时的计算
# canvas fingerprint
# canvas 常用 api
# svg 和 canvas 的概念和区别 ;说说 svg 和 canvas 各自的优缺点?
# canvas 如何使用图层,追问如何避免图层覆盖?
# 可视化方案、svg、canvas 区别使用场景
# canvas 如何进行局部刷新(局部重绘)
# canvas 渲染较大画布的时候性能会较低?为什么?
# echarts 中 svg 模式与 canvans 模式
# 为什么 canvas 的图片为什么会有跨域问题
# 浏览器/网络
# 谈谈事件流
html与js交互是通过事件驱动的,事件流就是页面中接收事件的顺序。 他分为两部分,
- 事件冒泡 - 指事件触发,会由内到外依次触发同类型事件
- 事件捕获 - 指事件触发时,会从根节点开始直到最具体元素 事件委托,子元素绑定太多事件影响性能,占用内存;父节点添加一个事件可以代理所有子节点的事件
# 详细说说 HTTP 缓存
- 强缓存 - 不会向服务器发送请求,直接从缓存读取数据
- 请求数据,不存在缓存结果和标识,则直接请求服务器
- 存在缓存,则检查是否失效,失效则使用协商缓存
- Cache-Control - 取值,max-age,相对于请求时间,意为多少秒后失效 优先级大于Expires
- Expires - 资源到期时间点,告诉浏览器在过期时间前直接从缓存中取数据
- 存在缓存,未失效,则使用缓存
- 协商缓存 - 强制缓存失效后,携带缓存标识请求服务器,服务器根据标识决定是否使用缓存
- 请求中携带last-modify的值,服务端根据最新资源的修改时间对比,如果未更新,则直接返回304,空响应
- 请求中携带etag的值,etag是资源内容的哈希值,服务端对比新生成的eatg值,如果未改变,则返回304,空响应
# 输入 URL 到页面展现的全过程
- 输入url,向dns服务器解析出域名指向的目标服务器ip
- 使用http协议建立tcp连接
- 发送http请求,服务器处理请求,返回响应html正文
- 拿到html正文,浏览器开始解析
- 处理html标记并构建出DOM树
- 处理CSS标记构建为CSSOM树
- 合并为render树
- 根据渲染树来计算布局各个节点的几何信息
- 将各个节点绘制到屏幕上
# 重绘、重排的区别,应该如何避免
- 重排:元素位置/尺寸/布局变化,浏览器会重新计算其在视口中的几何属性;重排会导致重绘,性能影响比较大
- 重绘:元素位置/尺寸/布局不变,样式属性变化,浏览器重新绘制外观
如何避免
- 最小化重绘重排, 样式集中改变,比如添加新样式类名
- 批量操作DOM,比如使用createDocumentFragment,处理完节点再一次性操作dom
- absolute/fixed脱离文档流
- 开启GPU加速 - 利用css属性transform willchage等优化,改变位置使用translate
# TCP 和 UDP 的区别
对比 | tcp | udp |
---|---|---|
可靠性 | 确保完整性/有序性 | 可能乱序/丢失 |
连接 | 需要建立/关闭连接 | 不需要建立/关闭连接 |
速度 | 由于保证可靠性,速度较慢 | 较快 |
应用 | 文件传输/网页浏览 | 在线游戏,音视频 |
# 跨域是什么?如何解决跨域?
同域是指相同域名端口和协议;不满足的请求则会受到浏览器同源策略限制。
- JSONP方式,script标签没有跨域,创建一个script标签提供一个函数接收参数,后台返回数据的包装,直接调用此回调函数即可
- cors 服务端设置响应头允许客户端跨域访问
- 代理,nginx代理等
- websocket通讯
- 本地开发也属于服务器代理请求
# Post 和 Get 区别
# TCP协议三次握手、四次挥手的过程,为什么挥手要4次?
三次握手
- 主机A向B发送建立连接的请求,携带标志位,表示请求连接,请确认
- 主机B收到请求,将一个确认应答的响应返回给主机A,表示我已收到请求
- 主机A收到应答,再发送一个确认应答,表示我已收到回复,开始传输
四次挥手
- 主机A完成数据传输,提出停止连接的请求
- 主机B收到请求,确认连接将关闭,准备关闭连接
- 主机B提出关闭请求,表示准备好了,可以关闭
- 主机A收到请求,确认关闭。
# webSocket与传统的http相比有什么优势?
- webSocket复用长连接,http协议头部太大,重复传输浪费资源
- webSocket支持服务端推送,之间可以即时通讯
Websocket只需要一次HTTP握手,所以说整个通讯过程是建立在一次连接/状态中, 也就避免了HTTP的非状态性,服务端会一直知道你的信息, 直到你关闭请求,这样就解决了接线员要反复解析HTTP协议,还要查看identity info的信息
# 项目用到了 WebSocket,说下 WebSocket 吧?长轮询和短轮询?
- 双向通讯,实时双向数据传输,服务器可推送数据
- 实时性,通讯延时很低
- 减少带宽消耗,节省请求头及频繁建立连接的消耗
- 长轮询 - 请求到有数据为止,服务器有数据在返回
- 短轮询 - 定时请求
# cookie sessionStorage localStorage 区别
- cookie数据在同源http中携带,客户端服务端来回传递, sessionStorage localStorage在本地存储
- cookie 储存大小限制4k,session local 限制5M
- 数据有效期不同,cookie过期时间之前一直有效,即使窗口或浏览器关闭
- 作用域不同 sessionStorage不共享,localStorage /cookie 在所有同源共享,
# 浏览器垃圾回收机制?内存如何管理的?
内存生命周期
- 分配你所需要的内存
- 使用分配到的内存(读、写)
- 不需要时将其释放\归还
垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存。
- 标记清除 - 当变量进入执行环境时即上下午被引用时,标记为''进入',离开时再标记''离开',
- 由于是从根对象查找,无法从根对象找到的就没法清除
- 回收后会有内存碎片,会影响会须大的连续空间的内存分配
- 引用计数 - 声明或者变量被引用一次,则计数+1;重新赋值或去除引用则-1;会清除引用次数为0的变量
- 循环引用问题无法清除
- 新生代/老生代
- 新生代空间生命周期较短,分为两个区域,每次只使用一个,满了就将存活对象放入另一个半区;然后清理掉当前;满足条件(体积/时间)的晋升到老生代;他时间短,频率高;
- 相比而言,老生代存放的较长时间存活的对象,比如全局变量和函数;他采用的时标记+清除配合标记+整理
- 标记清除 - 从根对象扫描出可达对象,其余全部清除
- 标记整理 - 标记出存活对象,将其整理到连续内存空间
# 有什么方法可以保持前后端实时通信
- http定时请求 - 定时器间隔时间发起请求
- http长轮询 - 请求返回之后继续请求
- WebSocket - 基于TCP协议的双向通信协议,可以客户端服务端建立持久连接。
- WebRTC - 支持浏览器之间实时通讯,可以实现屏幕共享
- SSE Server-Sent Events - 服务端推送消息
# HTTP与HTTPS有什么区别?
- http明文传输容易被窃听篡改,https使用ssl/tls加密协议传输,能更好保护数据安全
- 端口号不一样 http:80 https:443
- http不需要证书,https需要数字证书,用来验证实现身份验证和信任机制
- https多了数据的加密解谜,因此性能上会略差一点
# http1.x 和http2.x区别
- http1文本格式传输,http2二进制传输 - 减少开销提高了性能
- http2支持多路复用,建立一个连接可以发多个请求,避免了http1的队头阻塞问题 - 提高了网络吞吐量和响应速度,大文件传输
- http2支持流量控制,可以在客户端/服务端间动态分配带宽,确保传输的平稳高效
- http2支持首部压缩,压缩消息头,减小传输开销
- http2支持服务器推送,可以主动推送资源,避免主动请求 推送必须加载的css文件等
# 常见http 状态码有哪些?
- 1xx - 信息已接收,待处理
- 2xx - 请求成功
- 200 请求成功,数据正常返回
- 204 请求成功,无数据返回
- 3xx - 重定向
- 301 网页永久移动
- 302 网页临时移动
- 304 资源被缓存,要求的资源未被修改
- 4xx - 客户端错误
- 400 请求无效
- 401 未授权
- 403 拒绝请求
- 404 资源未找到
- 5xx - 服务端错误
- 500 服务器内部错误
- 502 网关错误
- 503 暂时无法处理请求
# http请求方式有哪些?
- post 提交数据,用于表单提交,文件上传等
- get 获取资源,一般请求页面或静态资源
- put 修改资源,一般用于更新资源
- delete 删除指定资源
- options 预检请求,主要用于Web服务的探测和测试,非简单请求之前调用,比如跨域CORS的非简单请求,通过options做预检查,用以判断实际发送的请求是否安全
# ajax原理,为什么要用ajax?
通过js发送http请求,从服务端获取数据而不需要刷新页面。 优点如下
- 用户体验好,不刷新更新局部内容,页面响应速度更快
- 减少网络传输数据量,仅更新需要的数据,而不是整个页面
- 支持异步操作,因此可以同时处理多个请求,不会阻塞页面
- 按需取数据,分担了服务器压力
- 数据与呈现分离,有利于开发维护
缺点
- 暴露了与服务端的交互
- 破坏了浏览器历史记录
# axios的拦截器原理及应用
- 设置接口请求前缀:根据开发、测试、生产环境的不同,前缀需要加以区分
- 请求头 : 来实现一些具体的业务,必须携带一些参数才可以请求(例如:会员业务)
- 状态码: 根据接口返回的不同status, 来执行不同的业务,这块需要和后端约定好
- 请求方法:根据get、post等方法进行一个再次封装,使用起来更为方便
- 请求拦截器: 根据请求的请求头设定,来决定哪些请求可以访问
- 响应拦截器: 这块就是根据 后端`返回来的状态码判定执行不同业务
# 同域请求的并发数限制及原因?
浏览器的并发请求数目限制是同一时间针对同一域名下的请求数量限制,超过限制数目的请求会被阻塞(chorme和firefox的限制请求数都是6个)。 原因是:浏览器为了保护自己不可能无限量的并发请求,而且一次性将所有请求发送到服务器,也会造成服务器的负载上升。
# 有哪几种常用的登录验证方式?
- Cookie + Session 登录
- Token 登录
- SSO 单点登录
- OAuth 第三方登录
# a元素除了用于导航外,还有什么作用?
href属性中的url可以是浏览器支持的任何协议,所以a标签可以用来手机拨号110,也可以用来发送短信110,还有邮件,下载
# vue全家桶
# vue组件通信方式有哪些?
- props /emit 父子组件通讯
- vuex 全局状态管理,组件通过getter mutation action访问和修改数据
- provide / inject向子孙组件传递数据
- event bus 事件总线传递
- $refs 获取组件实例
- $attr/$listeners 访问父组件传递的非prop属性和事件处理函数
- $parent / $children访问父子实例
# 虚拟DOM是什么? 它的优缺点
他是一个js对象,用来描述dom节点。 优点
- 跨平台 - js对象描述的dom节点和浏览器无关,这可以使得安卓ios小程序都可以使用
- 保证性能下限 - dom树实现代价太高,更新变化的部分是在js中进行,不管数据多少可以保证不错的性能
- 提高开发效率 - 无需手动操作dom,利于组件封装 缺点
- 大量渲染dom,有时由于多了dom计算,相比直接操作dom,性能会有损失
# Vue数据更新核心diff流程是怎样的?
更新组件意味着数据发生了变化,数据频繁修改,如果直接渲染,会导致大量的重排重绘,这极其消耗性能,而且没有必要。 vue主要用两个思路优化渲染。
- 将多次修改存入队列中,重复的修改会被去重,然后在下一个tick去更新
- diff算法计算出必要的dom来更新
新旧虚拟dom树的对比是为核心,从根节点自顶而下,递归比较子节点; 总的来说包含三种操作
- 节点属性更新
- 文本更新
- 子节点更新 子节点更新比较复杂,包含删除更新添加操作,相同元素引入了key值来区分
# vue双向绑定使用与原理
v-model 实现,相当于:value 与 @input的语法糖,减少了大量的事件处理代码;
通过对事件的监听,视图中输入值变化后触发
# v-if v-show v-html区别
- v-if会调用addIfCondition方法,生成vnode的时候会忽略对应节点,render的时候就不会渲染;
- v-show会生成vnode,render的时候也会渲染成真实节点,只是在render过程中会在节点的属性中修改show属性值,也就是常说的display;
- v-html会先移除节点下的所有节点,调用html方法,通过addProp添加innerHTML属性,归根结底还是设置innerHTML为v-html的值
# v-if for的优先级
- vue2 for高于if,先循环后判断,会浪费资源,不建议使用
- vue3 if高于for,
# vue响应式原理
- 数据劫持 - 增加getter/setter方法,实现数据监测
- 发布订阅模式 - 响应式对象创建Dep实例,用于收集watcher,数据改变,通知Dep中的watcher实例更新视图
- 模版引擎 - 模版编译为虚拟Dom,进行节点渲染,diff出差异,进行按需更新 侦测数据变化,做出响应;只需要关注数据,视图更新,不需要操作dom; 通过数据劫持+发布订阅模式实现。首先通过es5中的Object.defineProperty 将data中的数据各个属性劫持,增加setter/getter 当属性被读取或修改时,触发相应的
主要包含几个部分
- 侦测数据变化(数据劫持,数据代理)
- 收集数据依赖(依赖收集,解析编译)
- 数据变化时更新视图(发布订阅模式) 具体的,
- vue2中有Observer用来劫持并监听属性,属性变化就通知订阅者watcher
- 订阅器Dep,用来收集订阅者,对监听器Observer和watcher统一管理
- 订阅者watcher,收到属性变化就执行相应的方法来更新视图
- 解析器Compile可以解析节点指令,对模版数据和订阅器进行初始化
# computed 和 watch的异同点?
- computed:计算属性
计算属性是由data中的已知值,得到的一个新值。
这个新值只会根据已知值的变化而变化,其他不相关的数据的变化不会影响该新值。
计算属性不在data中,计算属性新值的相关已知值在data中。 别人变化影响我自己。
- computed擅长处理的场景:一个数据受多个数据影响
- watch:监听数据的变化 监听data中数据的变化 监听的数据就是data中的已知值 我的变化影响别人
- watch擅长处理的场景:一个数据影响多个数据
# vue.$nextTick 作用和原理?
平时开发时,如果修改了数据,下一步操作需要依赖数据变化后的dom进行操作,我们就需要在nextTick中执行;
延迟执行回调的钩子,接收一个回调函数作为参数,在下次dom更新循环结束后调用。
vuejs dom的更新是异步执行的,修改数据时,视图并不会立即更新,而是开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。 如果同一个 watcher 被多次触发,只会被推入到队列中一次。 这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的
# Vue3相比Vue2有哪些优化?
- 重构响应式系统,proxy代替defineProperty
- 支持监听数组变化
- 支持属性新增删除的监听
- 支持拦截更多方法
- 监听目标为对象本身,不需要遍历,但仍需要向下递归子对象
- 新的compositionAPi;更好的逻辑复用和代码组织
- 重构虚拟DOM
- 模版编译时优化,将静态节点标记,节省diff时的计算
- slot优化,slot渲染决定权交给子组件
- 模版内联时间提取重用(之前每次会重新生成)
- 代码结构调整,tree shaking 体积更小
- ts代替flow,ts开发体验更一致
# vue项目哪些性能方面的优化?
- 路由懒加载,按路由分块,按需加载
- 使用异步组件,加快首屏渲染速度
- 长列表或大数据量使用窗口化/虚拟滚动优化
- 组件延迟渲染 -延迟渲染,视窗可见渲染;
# 客户端渲染和服务端渲染有什么区别呢?
最重要的区别是,究竟谁来完成html文件的拼接。 服务端渲染,在服务端完成数据与视图的拼接; 优点:
前端首屏速度快,浏览器只需直接渲染html
有益于SEO,完整的html页面,利于定义TDK,更容易获取信息
无需占用客户端资源,(移动端更省电)
对于页面变化不大的页面非常高效(一次生成,一直缓存) 缺点:
占用服务端资源
用户体验不好,刷新会重新请求资源,有频闪和卡顿
客户端渲染 优点:
- 前后端分离,各司其职,前端可承担更多的逻辑计算,
- api可以复用
- 体验更好,页面内交互更加流畅
缺点:
- 不利于SEO,爬虫抓取不到路由页面,因为只有一个html,tdk也不好定义
- 前端首屏响应慢,需要加载js后,js再添加dom,多了额外时间。
# MVC 和 MVVM 区别
- mvc - 单向通讯,就是 Controller 负责将 Model 的数据用 View 显示出来,视图通过控制器协调
- 模型 - 数据
- 视图 - 用户界面
- 控制器 - 业务逻辑
- mvvm 多了vm层,将模型和视图实现双向绑定,实现了view和model的自动同步
# Vue 异常处理
1、全局错误处理:Vue.config.errorHandler 2、全局警告处理:Vue.config.warnHandler 3、单个vue 实例错误处理:renderError 4、子孙组件错误处理:errorCaptured 5、终极错误捕捉:window.onerror
# 懒加载的实现原理是怎样的?vue中路由懒加载怎么实现?
- 图片懒加载是通过监听滚动事件,修改图片的src实现
- webpack代码分割,也可以实现按需懒加载
- vue路由懒加载是结合 Vue 的异步组件和 Webpack 的代码分割功能,可以实现路由组件的懒加载;
# Vue长列表的优化方式怎么做?
- key属性可以优化更新
- 虚拟滚动,只渲染可见部分的dom节点
- 分页加载
- 懒加载图片等资源
- 懒渲染,延迟渲染组件
- keep-alive缓存
# Vue.use函数里面具体做了哪些事
- 判断插件是否安装,已安装就返回
- 执行install方法,传入vue构造函数的参数
- 标记已安装,将插件名缓存
- 返回vue实例,方便链式调用
# vue的权限管理应该怎么做?路由级和按钮级分别怎么处理?
- 用户/非用户 - 登陆验证-axios拦截器跳404
- 角色控制 - 登陆完获取角色-前端保存全部路由表,过滤出角色菜单动态渲染对应菜单和路由;
- 菜单控制 -由服务端返回菜单集合,动态增加菜单和路由;导航守卫增加权限验证
- 按钮控制
- 获取权限集,全局provide/ 需要的地方inject
- 权限集放入vuex中管理
- 封装权限指令v-permission使用
# vue父组件潜嵌套了子组件,他的生命周期函数顺序是怎么执行的
父创建前-父创建-父挂载前-子创建前-子创建-子挂载前-子挂载-父挂载
# 介绍下vue生命周期?
- beforeCreate - Vue实例已创建,数据监听/事件初始化未完成,一般不做操作
- created -指vue实例创建完成, 挂载数据,绑定事件完成,一般可以做http请求数据
- beforeMounted - 实例挂载前,虚拟DOM已经创建,这里也可以请求数据
- mounted - 虚拟dom渲染,挂载到真实dom节点上,这里可以操作dom
- beforeUpdate - 数据更改后,虚拟DOM重建完成,diff新旧dom
- updated - 对比完成的虚拟dom渲染完成,完成一轮更新
- beforeDestroy - 组件销毁前,可以执行一些善后工作,清除定时器,事件绑定之类的
- destroyed - 组件数据绑定监听已销毁,也可以在这做善后工作
# Vuex是什么,有什么优势,如何使用?
vuex是vusjs的状态管理模式,他是单一状态树,他能通过集中状态管理驱动组件变化。
- 集中式管理数据,便于理解维护
- 组件共享数据,单向数据流,利于开发解决组件通讯问题
- 响应式数据,能保存数据更新和页面同步 vuex api
- state - 用于存储全局状态变量
- mutations - 用于更改store的数据
- actions - 用于提交mutations,支持异步
- getters - 获取状态,支持计算类似计算属性 vue组件中可以使用api修改更新store
- dispatch - 触发action更新
- commit - 提交mutation
- mapState - 创建计算属性返回store状态
- mapGetters - 创建计算属性返回getter中的值
- mapActions - 分发actions
- mapMutations - 分发mutations
# 为什么 mutation 必须是同步函数?
为了实现单项数据流,即每次提交对应一个状态快照,保证每一条mutation都会被记录,如果有异步函数,就会出现mutation触发后,回调函数没有被调用。 事实上造成了回调函数中进行的状态改变不可追踪。
# vue2的data为什么是一个函数而不是对象
一个vue组件就是一个vue实例。vue的data数据其实是vue组件原型上的属性,数据存在于内存当中 vue组件为了保证每个实例上的data数据的独立性,规定了必须使用函数,而不是对象。 使用对象的话,每个实例(组件)上使用的data数据是相互影响的
# 修改ElementUI 样式的几种方式?
- 全局样式导入
- 单组件中增加style,不添加scoped属性
- 选择器/deep/向下覆盖
- 通过内联样式或者绑定类样式覆盖默认样式
# vue-router有哪些导航守卫,分别有哪些具体使用场景?
- beforeEach 全局前置 -
- afterEach 全局后置 -
- beforeResolved 全局解析 -
- beforeEnter 路由内
- beforeRouteEnter 组件内 -
- beforeRouteUpdate 组件内 -
- beforeRouteLeave 组件内 - 阻止页面跳转,保存未保存表单
# vue-router有几种模式,HashRouter 和 HistoryRouter的区别和原理?
- hash是用#模拟url,监听hashchange事件来监听url变化
- history使用history api来实现前端路由,url变化会发送请求,服务端需要配置支持,
# vue 中keep-alive的作用? 如何使用?
keep-alive包裹动态组件component时,会缓存不活动的组件实例, 而不是销毁它们,这样在组件切换过程中将状态保留在内存中,防止重复渲染DOM。
结合属性include和exclude可以明确指定缓存哪些组件或排除
- 缓存组件,此标签包裹组件时,组件不会被销毁,而是缓存起来,方便重新渲染
- 保留组件状态,组件内数据/事件都会保存,实现重新渲染恢复
- 避免重复渲染 提高性能
原理: keep-alive是一个通用组件,它内部定义了一个map,缓存创建过的组件实例, 它返回的渲染函数内部会查找内嵌的component组件对应组件的vnode, 如果该组件在map中存在就直接返回它。
# 工程化webpack/vite/rollup
# 谈谈对rollup的理解?
rollup比webpack晚出现2年,定位明显有所不同。主要体现在
- 建议开发者使用esm,esm是未来的趋势,浏览器原生支持;
对比webpack和rollup打包的产物我们会发现,rollup产物很干净,webpack因为需要兼容很多
# 简单说一下 Webpack 的原理?
他是一个模块打包器,可以将各种静态资源打包成符合规范的静态代码。
- 解析配置文件,初始化compiler对象
- 读取入口文件,生成模块列表
- 根据依赖关系,生成依赖图谱
- 编译模块,根据类型使用loader编译文件
- 生成模块,根据编译结果打包成块
- 优化,对块进行优化,提取公共块,压缩等
- 输出,根据配置路径和文件名,写入磁盘
# webpack的执行流程和生命周期?
webpack的生命周期:
- 初始化参数:从配置文件和Shell语句中读取与合并参数,得出最终的参数。
- 初始化Compiler:创建Compiler对象,加载所有插件。
- 准备编译:根据Compiler对象创建Compilation对象。
- 编译:从Entry开始,递归找出所有依赖的模块,逐个使用Loader读取并编译,再逐个通过各种插件的处理加工打包成最终的JS文件。
- 构建完成:完成编译后,得出最终要输出的资源。
- 输出资源:将最终的资源写入到指定的文件系统中。
- 执行完成:所有插件执行完成后触发其中的回调函数,输出日志完成整个构建流程。
# 介绍下webpack的plugin 和loader
- plugin 扩展机制,在webpack构建的各个环节,都可以编写插件处理需求
- beforeRun 运行实例前执行
- afterRun 运行实例后执行
- emit
- done
- loader 处理各种非js文件
# webpack的hash策略
- hash - 每次构建都会新生成
- contentHash - 根据内容生成,css/图片常用
- ChunkHash - 不同的块生成,块内容变化时变化
# vite原理,与webpack对比
vite是基于ESM的构建工具,底层基于es build,性能优异,对比webpack有这么几个优势
- 构建速度很快,因为vite采用了esm原生导入的方式,构建时不会对整个项目分析构建,只需要分析当前用到的模块,只构建当前模块的依赖关系;而webpack在启动时会全部build一遍,这需要消耗很多时间
- vite引入EsBuild,使用go语言编译ts文件比node编译快几十倍。
# 做过哪些 webpack 的优化
- 合并脚本拆分初始化负载
- 小图片使用base64,大图片压缩
- 字体图标代替图片图标(可以改样式大小不失真,文件体积更小)
- 压缩js/css/html
- tree shaking优化点无用代码
- 使用cdn加载静态资源
- 优化文件搜索 - include、exclude
- Dll插件缓存第三方模块产物
- thread-loader开启多进程转换
- 代码分割,按需加载
- gzip压缩
- 合理的hash值利用缓存
# npm run dev 执行过程
- 会去package中找到script中的dev,执行dev对应的shell命令
- 执行命令会在./node_modules/.bin/目录中查找对应的执行脚本,并执行
- ./node_modules/.bin/中的执行脚本都是npm install 安装时做的软链
# EES6 module 和 CommonJS module 的区别
# AST 过程
Babel
- input => tokenizer => tokens,先对输入代码进行分词,根据最小有效语法单元,对字符串进行切割。
- tokens => parser => AST,然后进行语法分析,会涉及到读取、暂存、回溯、暂存点销毁等操作。
- AST => transformer => newAST,然后转换生成新的 AST。
- newAST => codeGenerator => output,最后根据新生成的 AST 输出目标代码。
# AST作用 babel原理和用途
ast即抽象语法树,ast将源代码以树状结构描述出来,可以帮助我们更轻松的分析代码修改代码。
babel是js的编译器,他内部是将js转换为AST,有了ast就可以将js处理转换为低版本支持的代码。 我们在开发时就可以直接使用各种方言以及新的语法特性而不需要考虑运行环境。
主要用途有
- 语法转换,将高版本js语法转换为更早版本的语法,如ts/flow转换为js
- 兼容性处理
- 代码分析 - 模块分析,treeshaking,代码压缩 linter检查等
具体的babel主要分为三步,
- 解析:通过词法分析(输入的字符标记)和语法分析(处理标记之间的关系,生成AST)将代码转换为AST
- 转换:对ast进行深度优先遍历,调用插件处理,按需转换ast节点
- 生成:将上一阶段处理后的ast转换为目标代码
# 怎么实现代码向下兼容?babel为什么没实现所有代码的向下兼容?
# babel转换代码的过程,箭头函数转普通函数的时候,是如何处理 this 的?
通过将this进行作用域绑定实现,即根据其嵌套的作用域确定var _this = this;
# webpack loader plugin的区别
- loader,它是一个转换器,将A文件进行编译成B文件,比如:将A.less转换为A.css,单纯的文件转换过程。
- plugin 是一个扩展器,它丰富了webpack本身,针对是loader结束后,webpack打包的整个过程,它并不直接操作文件,而是基于事件机制工作,会监听webpack打包过程中的某些节点,执行广泛的任务
# webpack和gulp的区别
对比 | gulp | webpack |
---|---|---|
定位 | 基于流自动化构建工具 | 万能模块打包器 |
目标 | 自动化/优化开发流程,适用站点开发 | 通用模块打包器,适用SPA |
作业方式 | 输入/转换/输出三步走 | 从入口文件扫描解析,根据依赖转译打包为js模块 |
使用方式 | js开发,编写系列任务 | 各种配置json,灵活度高 |
gulp更倾向于流程自动化, webpack侧重模块化打包,通过插件实现全能打包器
# 安全/性能
# 性能优化有哪些手段?
- 减少http请求 - 小文件合并大文件
- 静态资源使用cdn - 分发网络更快
- css写头部,js写底部(异步加载js) - 减少白屏,dom解析不阻塞,减少回流
- 字体图标代替图片图标 - 矢量/体积更小/样式可更改
- http缓存 - 固定打包产物合并打包,合理利用缓存
- 图片优化,懒加载,图片压缩,css替换实现,base64嵌入
- webpack分割代码,提取公共部分,按需载入
- 压缩js/css,启用gzip/http2
- 删除无用代码,注释
- 避免大量dom节点,以及深层嵌套- 提升解析速度
- 减少回流重绘 -
- 使用框架,减少操作dom的次数
- 元素/样式集中改变,多次操作合并;createDocumentFragment/或innerHTML 字符拼接
- 脱离文档流,绝对/固定定位,重新计算不影响其他元素
- css硬件加速,使用transform/opacity/filter属性等(过多使用会造成内存占用)
- 事件委托机制,减少事件监听
- 防抖节流优化高频事件
- transform、opacity、filter、will-change实现动画,专用合成器
- 合理使用webpack include/exclude控制模块扫描解析
- 合理的分块大小,
- DLLPlugin缓存,避免重复编译
- thread-loader开启多进程转换
- 动态导入组件,异步组件,
- 避免组件过于复杂嵌套
- 长列表优化,需求合理性,分页,虚拟列表
- keepalive
- 延时渲染
# 前端监控 SDK 技术要点
# 如何提高网站的安全性?
- xss攻击 跨站脚本攻击 - 输入验证,过滤/编码标签,设置请求头Content-Security-Policy、X-XSS-Protection,
- iframe安全
- X-Frame-Options Header头,拒绝页面被嵌套
- sandbox属性,限制最小权限原则
- 本地存储安全 - 敏感信息加密存储,减少存储
- CSRF(跨站请求伪造) - 同源检测,origin referer检测,验证码
- ClickJacking(点击劫持)
- HTTPS(HTTP严格传输安全)
- CND劫持 - https
- 中间人攻击 - https
# 什么是懒加载,图片懒加载如何实现?
- 截流监听滚动事件
- 利用rect api判断图片top与innerHeight
- data-src赋值给src 移除data-src
const imgShow = () => {
const imgs = document.querySelectorAll('img[data-src]')
if (!imgs.length) return
imgs.forEach(img => {
const rect = img.getBoundingClientRect()
if (rect.top < window.innerHeight) {
img.src = img.dataset.src
img.removeAttribute('data-src')
}
})
}
document.addEventListener('scroll', throttle(() => {
imgShow()
}, 100))
imgShow()
# 综合题
# reduce
- reduce((previousValue, currentValue, currentIndex, array) => { /* … */ }, initialValue)
# 浏览器tab之间如何通讯
- websocket
- localStorage
# 前端防重复提交有哪些方案?
- 禁用按钮等待可用时再重置状态
- 防抖函数避免重复提交
- 触发函数的js中增加状态标记,可用时再重置
- 也可以在axios中拦截相同请求
- loading层提示隔离操作按钮
# 如果一次性增加100万个用户访问项目,前端角度你会怎么优化?
- 压缩文件,缩小文件体积,减少http请求
- 使用cdn分发,减轻服务端压力
- 负载均衡,将流量分配到多个服务器,避免单点故障
- 缓存,前端资源缓存,数据缓存,信息处理缓存看业务情况
# 上线白屏一般有哪些可能,如何排查?
- 网路故障 - 别的网站能打开吗?
- 缓存未更新 - 浏览器清缓存,隐私模式打开试试?
- cdn故障 - 检查cdn资源加载是否有问题?
- 路由错误 - 检查nginx/网关/url地址是否正确?
- 代码版本问题 - 是否上线未更新成功?
- 检查权限 - 是否权限限制?
- js报错 - 检查日志或控制台有无错误,确认线上版本一致,代码有无可能隐患?
- 接口报错 - 接口是否正常返回数据,查看请求了哪些接口
- 接口数据是否有问题 - 想办法获取接口数据,对照代码查看?
# 数组去重有哪些方法?
- 借用set不重复的性质去重
- for循环去重复
- 利用map key值不能重复,单重循环即可
# 如何优雅处理前端异常?
- 用户体验增强
- 远程定位问题
- 提早发现问题
错误/异常分类
- js语法错误,代码异常
- ajax请求异常
- 资源加载异常
- promise异常
- iframe异常
- 跨域script error
- 崩溃卡顿
抓取异常
- try -catch 只能抓取同步运行时错误
- windoew.onerror -写在所有 JS 脚本的前面,否则有可能捕获不到错误;无法捕获语法错误
- promise catch 注意链式调用时处理
- 全局增加unhandledrejection可以监听Uncaught Promise Error
- VUE errorHandler
上报错误
- ajax -请求本身也有可能会发生异常,而且有可能会引发跨域问题
- img 动态链接
- 控制发送频率
# 使用setInterval请求实时数据,返回顺序不一致怎么解决
- 使用setTimeout代替setInterval,保证请求间隔
- requestAnimationFrame
- Web Worker 修复时间
- setTimeout作系统时间补偿
# rem和vw的使用场景?
- rem 根据根元素字体大小来设置大小单位,一般取固定值;其他元素字体宽高都用rem;尺寸变化时改变根元素字体变化会使页面呈现相对一致的效果;
- 适用于整体排版布局
- vw 宽度分为100份;vw可以根据屏幕尺寸适应
- 适用于元素的适配
# felx布局
- 容器属性
- flex-direction 主轴方向 - row行 column列
- flex-wrap - 如何换行 nowrap | wrap | wrap-reverse;
- flex-flow - deirection wrap的简写 如 row nowrap
- justify-content 主轴对齐方式 flex-start | flex-end | center | space-between | space-around;
- align-items 交叉轴对齐方式,flex-start | flex-end | center | baseline | stretch
- align-content 多根轴对齐方式 flex-start | flex-end | center | space-between | space-around | stretch;
- 元素属性
- order 定义项目的排列顺序。数值越小,排列越靠前,默认为0
- flex-grow 放大比例,默认为0
- flex-shrink 缩小比例,默认为1
- flex-basis 分配多余空间之前,项目占据的主轴空间
- flex flex-grow, flex-shrink 和 flex-basis的简写
- align-self 单个项目有与其他项目不一样的对齐方式 auto | flex-start | flex-end | center | baseline | stretch;
# css盒子,bfc
# CSS 清除浮动的方法?
浮动元素使得父元素无法包含他们,会出现些奇怪布局问题
- clear 清除;添加元素style="clear:both;"清除两侧浮动
- 父元素增加overflow属性
- 伪元素增加clear:both
- BFC块级格式上下文
# 左右两列定宽,中间自适应的方法?(有多种方式)
- flex布局
.container {
display: flex;
}
.main {
flex: 1;
}
- 绝对定位
- margin
.left {
float: left;
width: 200px;
}
.right {
float: right;
width: 200px;
}
.main {
margin: 0 200px;
}
- grid
.container {
display: grid;
grid-template-columns: 200px auto 200px;
}
说一下 JavaScript 的继承方法? 用过哪些排序算法?快速排序和选择排序的时间复杂度? 前端性能优化的方法?
# 如何写一个会过期的localStorage,说说想法
1.惰性删除 - 使用时,才检查过期 再删除 -可能会导致永久存储过多数据 2.定时删除 - 定时器去检查过期删除
# 我们现在需要写一个 foo 函数,这个函数返回首次调用时的 Date 对象,注意是首次。
- 普通方法 - 全局变量
- 闭包 - 缓存住标记
- 函数对象上存放变量
- 惰性函数
# 单页应用优缺点有哪些?
- 用户体验好 - ajax无刷新更新内容,更流畅,减少页面闪烁和重新加载时间
- 更快加载资源 - 只需一次加载静态资源,减少了请求资源与服务端的通讯
- 响应速度快 - 前端路由可实现客户端页面跳转与更新
- 接口可复用 - 前后端分离,利于开发职责清晰
- 减轻服务器压力 - 计算渲染逻辑可以放在浏览器
- 首次加载时间较长 - 首次需要加载所有静态资源(目前已有改观可按需加载)
- SEO不友好 - 应用数据是通过请求接口动态渲染,不利于SEO
- 首屏渲染速度相比慢一点
# 框架带来的好处和弊端
优点
- 开发效率提升 - 框架一般提供了一系列工具/模版,减少开发时间/成本
- 提高代码质量 -
# 如何实现在一张图片上的某个区域做到点击事件
- 覆盖透明div,计算好相对尺寸位置
- 事件监听,判断鼠标相对图片的便宜offsetX/Y - 基于数学计算灵活定义任意形状
- 图片热区技术,设定好区域即可 - 只能定义矩形圆形多边形
<img src="image.png" usemap="#hotspots">
<map name="hotspots">
<area shape="rect" coords="0,0,50,50" href="page1.html">
<area shape="circle" coords="100,100,50" href="page2.html">
<area shape="poly" coords="200,200,250,250,200,300" href="page3.html">
</map>
# 一万条数据怎么渲染?
- 时间分片渲染,利用setTimeout/requestAnimationFrame分批插入,如每次插入100条
- 结合createDocumentFragment文档碎片,先在js中组织节点,再分批插入dom,如,js中每次拼接100条,一次插入
- content-visibility:auto 属性,浏览器控制出现在视口范围内才被渲染 - 兼容性问题
- LazyLoad懒加载
- 虚拟列表
- 截取部分数据渲染到可视区域中,防止出现滚动过快的空白,可以优化渲染更多节点
- 监听滚动事件/IntersectionObserver 动态slice改变列表
- 固定高度的元素列表,可以通过计算起始索引得出需要渲染数据
- 不固定高度的元素,渲染后缓存真实高度(ResizeObserver),计算出索引
# 一个网页从请求到呈现花了很长时间,如何排查
- 加载慢? - network查看加载文件时间, cdn,压缩文件,拆包异步加载
- 渲染慢? - Performance性能指标测试 FP,FCP,LCP等指标;首屏优化
- 数据阻塞? - 接口速度测试
- 整体性能分析工具 - Lighthouse
# 怎么计算在一个页面上的停留时间
- websocket,前端开个长连接,后台统计长连接时间。
- ajax 轮询,隔几秒发一个查询,后台记录第一与最后一个查询间隔时间。
- 关闭窗口或者跳转的时候会触发 window.onbeforeunload 函数,可以在该函数中做处理(有兼容性问题);统计完数据记录到本地 cookies 中,一段时间后统一发送。
# 实现协同编辑,说说你认为的技术关键点
- 客户端数据与服务端数据同步
- 编辑冲突,多人编辑同一个地方
- 文档编辑的复杂度
# 如果让你实现一个计算器,都需要考虑哪些问题
# 如果想给一个对象上的所有方法在执行时加一些打点上报的功能,如何做?
使用proxy,拦截代理对象,判断如果是函数属性,则执行打点上报。能收集方法输入、输出
# 限制数量的请求并行方案?
维护调用队列和同时调用函数计数器,调用开始时计数器+1,调用结束-1;维持同时请求数量
# 原生拖拽方案及实现细节;觉得它有哪些难点?
- 目标元素设定draggable="true"
- 目标元素增加监听dragstart/dragend事件,处理拖拽开始与结束事件,开始事件可以通过dataTransfer携带数据
- 目标区域增加监听dragenter/dragover/drop,改变样式提升交互体验,需要阻止默认事件
- 最后在监听的drop事件中处理数据变化,如接收传输的数据,增删dom节点
# 如何实现一个可编辑的可以无限延伸的表格?
# 小数计算有误差怎么办?
计算机存储无限循环的数字只能存近似值,取出来计算的时候就会出现进度问题。
- 整数方式存储小数计算
- 使用高精度计算库
- 使用近似值
# 前端要加载一个图片有哪些方式, icon 是怎么实现的?
- 正常加载 src指定地址
- 懒加载,Intersection Observer
# base64 是怎么实现的,有什么缺点?
- base64是一种编码方式,使用64 个可打印字符来表示二进制数据的编码方式;计算机中所有内容都是二进制形式存储的,所以所有内容(包括文本、影音、图片等)都可以用 base64 来表示
- 缺点是,,编码后体积会变大,编码解码也需要额外工作
- 优点:方便传输,相当于简单加密(肉眼安全)
# 浏览器保存用户账号密码的原理是什么?
# 现在要你完成一个Dialog 组件,说说你设计的思路?它应该有什么功能?
# 说说白屏优化的方式有哪些?优化首屏渲染的方式有哪几种?
- 代码分割按需加载
- cdn加速,通用库剥离利用缓存
- gzip压缩
- 异步组件
- ssr
- 多入口
- 骨架屏
- 预请求,预加载
# 搜索框优化,防抖节流,还有?
体验优化,
- 及时验证
- 清晰反馈
- 必要的说明
- 自动完成,减少用户输入
- 标注键盘类型(移动端)
# js 代码压缩,问:有哪些东西不会被压缩?
- 压缩多余的空格换行以及注释
- 变量名称变短
- 方法名称变短
不能压缩的:
- 原始值 - 字符串,布尔,数字,null undefined
- 全局变量,window,math document
- 属性名称
- 关键字保留字 - var return if for
# cookie支持跨域吗?怎么从一个页面去另一个不同源的页面带上cookie?
默认情况下不支持,可以设置cookie的domain属性实现; 根域名相同才可以实现(仅关注域名)
# sticky可以吸顶,怎么实现一个吸顶?
.nav {
position: -webkit-sticky;
position: sticky;
top: 0;
}
function compute() {
let ele = document.getElementById('nav');
if (ele.getBoundingClientRect().top < 0) {
ele.style.position = "fixed";
ele.style.top = '30px';
} else {
}
}
window.addEventListener('scroll', throde(compute, 100));
# sessionStorage同一个浏览器同源在不同的tab页里数据共享吗
- 页面会话在浏览器打开期间一直保持,并且重新加载或恢复页面仍会保持原来的页面会话。
- 在新标签或窗口打开一个页面时会复制顶级浏览会话的上下文作为新会话的上下文, 这点和 session cookies 的运行方式不同。
- 打开多个相同的 URL 的 Tabs 页面,会创建各自的 sessionStorage。
- 关闭对应浏览器标签或窗口,会清除对应的 sessionStorage。
取决于新tab的打开方式,当前页面使用a标签或者window.open新开页面就会携带session,新开tab则不会共享
# 发送http请求的时候都会自动带上cookie吗?那有什么方法可以发请求的时候不发送cookie?你知道cookie里面的SameSite属性吗
- cookie domian需要根域名相同
- 协议需要相同,或者secure为false
- 请求路径必须是一致或者子域名 满足要求才回携带cookie
- SameSite属性用于限制第三方 cookie 的使用
- Strict 最为严格,完全禁止第三方 Cookie,跨站点不会发送
- Lax 稍微宽松, 导航到目标网址的 GET 请求,只包括三种情况:链接,预加载请求,GET 表单
- None 关闭属性
# iconfont原理;css里面的iconfont加载会阻塞render吗,js执行会阻塞render吗
类似字体库,每个图标都是一个字符,他和字体类似
- 由于是矢量,大小改变不会失真
- 比图片小,加载快
- 可以改变颜色(只能单色/css渐变色)
CSS不会阻塞DOM解析,但是会阻塞DOM渲染,严谨一点则是CSS会阻塞render tree的生成,进而会阻塞DOM的渲染 js会阻塞dom解析 字体加载三步
- 阻塞 - 加载完成前默认使文字不可见,超时则交换
- 交换 - 完成前默认采用后备字体渲染文字,超时则失败
- 失败 - 还未加载完则直接使用后备渲染
js执行会阻塞render,JS线程负责执行js,渲染线程负责渲染两者是互斥的;
# 生产环境和测试环境的代码怎么保持一致
# 用过gulp的哪些任务
promise什么时候会执行.then, .catch
let var const区别
js的变量基本类型有哪些,引用类型有哪些,分别有什么特点
事件流 解释闭包,优点和缺点
# 块级元素和行内元素的水平垂直居中分别怎么实现
- 行内元素 - 与其他行内公用一行,不能设置宽高,没有水平方向的margin和padding;span img input a
- 块级 -占据整行,有宽高,ul p
# rem, em区别
浏览器存储,cookie,localstorage,sessionstorage的区别
# 项目/管理
# 如何带领团队的?
- 统一规范,包括流程规范,代码书写,提交信息,项目结构,code review
- 共同进步,培训分享
- 及时沟通,了解需求,合理满足
- 解决问题攻坚难点带教新人
# 代码 review 的目标
- 找出代码缺陷
- 提升代码质量
- 是否满足功能需求,有没有多做,有没有少做,有没有潜在的bug
- 是否满足非功能需求。性能(高频调用的函数、核心算法)?可用性(异常处理是否完善)?可读性?
- 代码规范/质量
# 个人/规划
# 做个简单自我介绍
我有着8年前端开发经验,2年管理经验,具备良好的自学能力和出色的检索能力。我关注新兴技术并能够审慎地将其落地,曾参与多个完整的大型项目,包括移动端、公众号、后台管理和可视化等均有涉及。
# 对自己的评价?
# 最近有关注什么新技术吗?
- css原子框架 unocss
# 说一下项目中比较困难的事情有哪些?
# 说一个你做过印象最深刻的项目
# 项目难点亮点-你做过的项目有什么亮点吗
# 项目中遇到最复杂的是什么?最有技术难度的是什么?
# 技术选型如何做?
- 社区是否成熟,解决问题更容易
- 团队成员学习成本和意愿
# 有什么想问我的吗
- 岗位具体负责的工作内容、对接的部门有哪些,岗位的晋升机制、领导对自己这个岗位的期望。
- 可以问本职岗位工作要求、职责,内容。
- 可以问公司、公司的业务、体系、行业、客户。
- 可以问什么时候面试结果通知。
# 入职之后如何开展工作?
- 熟悉产品业务
- 融入团队
# 你能为公司带来什么?你希望公司给你什么?
# 在工作之外有哪些学习技术的方式?
# 与领导意见不统一时应该怎么办?
# 你觉得目前自己的技术在什么位置,觉得自己哪一块能力需要加强?
# 算法/手写题
时间复杂度
- O(1) 单次
- O(n) 单层for
- O(n^2) - 双层for
- O(logn) - 二份
- O(nlogn) - 单层for嵌套二分
- O(2^n) - 递归斐波那伽(未优化)
# 实现一个LRU缓存算法
LRU - Least Recently Used,是一种常见的缓存淘汰策略
class LRU {
private len: number
private map: Map<any, any> = new Map()
constructor(len: number) {
if (len < 0) throw new Error('缓存长度不能小于0')
this.len = len
}
set(key: any, val: any) {
if (this.map.has(key)) {
this.map.delete(key)
}
this.map.set(key, val)
if (this.map.size > this.len) {
const k = this.map.keys().next().value
this.map.delete(k)
}
}
get(key: any): any {
if (!this.map.has(key)) return null
const val = this.map.get(key)
this.map.delete(key)
this.map.set(key, val)
return val
}
}
# 手写数组转树/树转数组?
# 数组扁平化
export const flatArr = (arr) => {
if (!Array.isArray(arr)) return []
return arr.reduce((arr, current) => {
return arr.concat(Array.isArray(current)
? flatArr(current)
: current)
}, [])
}
export const flat = (arr) => {
let res = []
if (!Array.isArray(arr)) return []
arr.forEach(x => {
res = res.concat(Array.isArray(x)
? flat(x)
: x)
})
return res
}
# 驼峰下划线互转
// 下划线转换驼峰
function toHump(name) {
return name.replace(/\_(\w)/g, function (all, letter) {
return letter.toUpperCase();
});
}
// 驼峰转换下划线
function toLine(name) {
return name.replace(/([A-Z])/g, "_$1").toLowerCase();
}
# promise.retry
Promise.retry = function (promiseFn, times = 3) {
return new Promise(async (resolve, reject) => {
while (times--) {
try {
var ret = await promiseFn();
resolve(ret);
break;
} catch (error) {
if (!times) reject(error);
}
}
});
};
function getProm() {
const n = Math.random();
return new Promise((resolve, reject) => {
setTimeout(() => n > 0.9 ? resolve(n) : reject(n), 1000);
});
}
Promise.retry(getProm);
# 十大排序算法
- 冒泡 O(n²)
- 选择排序-O(n²) 找到未排序中最小的,放到排序完中的末尾
- 插入排序 O(n²) 扫描剩余未排序的,依次将元素插入到排序好的中合适位置
- 希尔排序 O(nlogn) 取一半长度为增量分组进行插入排序,缩小增量到1为止;
- 归并排序 O(nlogn) 分治思想,二分排序;再依次合并有序子序列
- 快速排序 O(nlogn) 取基准值,将数组分为两部分,递归再分直到有序
- 桶排序 - 类似快排,一次性分配到多个桶中,桶中再拍,再合并有序序列
# 数组旋转K步
输入 [1,2,3,4,5,6,7] k =3 输出 [5,6,7,1,2,3,4]
// O(n^2)
export const rotate = (arr: number[], k: number): number[] => {
if (!k || arr.length === 0) return arr
const step = Math.abs(k % arr.length)
if (step === 0) return arr
for (let i = 0; i < step; i++) {
arr.unshift(arr.pop() as number)
}
return arr
}
O(1)
export const rotate1 = (arr: number[], k: number): number[] => {
const len = arr.length
if (!k || len === 0) return arr
const step = Math.abs(k % len)
if (step === 0) return arr
return arr.slice(-step).concat(arr.slice(0, len - step))
}
# 字符串括号匹配
// O(n)
export const isValidBrackets = (str: string): boolean => {
const map: { [propName: string]: string; } = {
'[': ']',
'{': '}',
'(': ')',
}
const len = str.length
if (len === 0) return true
if (len % 2 !== 0) return false
const res = []
for (let i = 0; i < len; i++) {
if (map[res[res.length - 1]] === str[i]) {
res.pop()
} else {
res.push(str[i])
}
}
return res.length === 0
}
# 有序数组查找和
有序则考虑二分;优化嵌套考虑双指针
export const findPair = (arr: number[], val: number): number[] | null => {
if (arr.length < 2) return null
let res = null, l = 0, r = arr.length - 1;
for (let i = 0; i < arr.length; i++) {
const sum = arr[l] + arr[r]
if (sum === val) {
return [arr[l], arr[r]]
}
if (sum < val) {
l++
}
if (sum > val) {
r--
}
}
return res
}
# 优化斐波那契
const feb = (n: number): number => {
if (n < 2) return 1
return n * feb(n - 1)
}
export const feb1 = (n: number): number => {
if (n === 0) return 0
let n1 = 1
let n2 = 1
let temp;
for (let i = 3; i <= n; i++) {
temp = n1 + n2
n1 = n2;
n2 = temp
}
return n2
}
export const feb2 = (x: number): number => {
const cache = [0, 1, 1]
function _feb(n: number): number {
if (cache[n] !== undefined) return cache[n]
cache[n] = _feb(n - 1) + _feb(n - 2)
return cache[n]
}
return _feb(x)
}
export const feb3 = (x: number): number => {
const arr = [0, 1, 1]
for (let i = 3; i <= x; i++) {
arr[i] = arr[i - 1] + arr[i - 2]
}
return arr[x]
}
# 小数位分割
- Number('10000000000').toLocaleString() // '10,000,000,000'
- '10000000000'.replace(/\B(?=(\d{3})+(?!\d))/g, '.') // 寻找字符空隙加 .
- '10000000000'.replace(/(\d)(?=(\d{3})+\b)/g, '$1.') //寻找数字并在其后面加 .
# 移动数组中的0到末尾
- 非原数组操作 - 循环收集两个数组合并
- 循环遍历,遇到0就和后一个数交换