1.对前端监控的理解?
异常监控(监控前端页面的报错)==> try / catch 、window.onerror、window.addEventListener、Vue.config.errorHandle
- JS 代码运行错误、语法错误等;
- AJAX 请求错误;
- 静态资源加载错误;
- Promise 异步函数错误;
性能监控(监控页面性能)==> window.performance
- 不同用户和不同设备下的首屏加载时间,包括白屏时间;
- HTTP 接口的响应时间;
- 静态资源、包括图片的下载时间;
用户行为监控
- 用户的来源;
- 用户的页面操作行为;
- 用户在每个页面的停留时间;
- 页面的浏览次数;
- 访问站点的次数;
2. 解释什么是 FOUC(无样式内容闪烁)?如何来避免 FOUC?
FOUC(无样式内容闪烁)
:在页面加载过程中,HTML 内容已经加载完成,但 CSS 样式尚未加载,导致网页内容以无样式的状态展示给用户,随后当CSS 样式加载完成后再重新渲染页面,从而产生的一种闪烁效果。
造成 FOUC的操作
:
- 外部样式表加载较慢
- javaScript 操作 DOM
- 使用自定义字体:使用自定义字体会导致字体文件加载较慢,可能引发 FOUC;
避免 FOUC
:
- 样式表前置:将 CSS 样式表放在 head 标签里,确保在 HTML 内容开始加载前,样式表已经被加载和解析;
- 使用 link 标签而非@import:使用 link 标签引入外部样式表可以在 HTML 页面加载的同时就开始下载 CSS 文件,使用@import 则会在整个 HTML页面下载完成后才去下载 ;
- 优化 CSS 文件可以减少页面加载时间,从而减少 FOUC 发生的可能性;
- 使用内联样式:使用内联样式,可以确保样式总是随 HTML 内容一起加载,减少 FOUC 的可能性;
3.img 标签的 title 属性与 alt 属性的区别是什么?
title:
是鼠标移动到元素上的文本提示;
alt:
是图片不能正常显示时出现的文本提示;
4.localstorage、sessionStorage、Cookie 的区别?
储存时间
:localStorage 数据永久储存,sessionStorage 数据仅在会话期间有效,Cookie 有效期可以设置;
储存大小
:localStorage 和 sessionStorage 的储存空间达到 5MB,Cookie 储存空间不超过 4KB;
与服务器的通信
:Cookie 在每次 HTTP 请求时都会发送到服务器,localStorage 和 sessionStorage 不会自动发送到服务器;
数据共享
:localStorage 和 Cookie 在所有同源窗口中共享,sessionStorage 只能在一个页面中共享;
5.说一下 link 与 @import 的区别和用法?
用途和语义
:link 属于 HTML 标签,不仅可以加载 css,还可以定义 RSS、rel 连接属性;@import 属于 css 提供的语法,只能导入样式表;
加载顺序
:加载页面时,link 引入的 CSS 被同时加载;@import 引入的 CSS 将在页面加载完成后被加载;
兼容性
:link 几乎支持所有浏览器,@import 是 CSS2.1 才有的语法, 在一些较旧的浏览器中可能不被支持;
DOM 操作
: link 创建的外部 CSS 文件可以通过 JavaScript 动态操作 DOM 来改变样式,而@import 引入的 CSS 不可以通过 JavaScript 动态操作DOM 来改变样式;
6.rgba 和 opacity 的透明效果有什么不同?
作用范围
:rgba 只作用于元素的颜色或背景色;opacity 作用于整个元素及其内容;
透明度继承
:rgba 设置的透明度不会影响子元素,opacity 的透明度会被子元素继承;
应用场景
:rgba 适合需要精确控制颜色透明度的场景,opacity 适用于调整元素透明度,如滤镜或模态框;
7.CSS3 选择器及其优先级?
ID 选择器(100)>类选择器、属性选择器、伪类选择器(10)> 标签选择器、伪元素选择器(1)> 相邻兄弟选择器、子选择器、后代选择器、通用符选择器(0)
8.CSS3 盒子模型:标准盒模型、怪异盒模型?
标准盒模型
:总宽度 = width(content) + padding + margin + border;
怪异盒模型
: 总宽度 = width(content + padding + border ) + margin;
盒模型转换
:
- box-sizing: content-box 时,盒模型表现为
标准盒模型
; - box-sizing: border-box 时,盒模型表现为
怪异盒模型
;
9.BFC 是什么?
BFC
:是 CSS 布局中的一个独立渲染区域,用于隔离内部元素与外部元素的布局。在 BFC 中,元素按照特定的规则进行布局,不会影响到其他 BFC 或外部元素;
创建 BFC:
- 根元素(HTML 元素);
- 元素浮动(float 不为 none);
- overflow 属性不为 visible 的块元素等;
- 元素定位(position 为 absolute 或 fixed);
- 元素转换类型(如 display 为 inline-block、table-cell、table-caption);
规则:
- 内部的 Box 会在垂直方向,一个接一个的放置;
- Box 垂直方向的距离由 margin 决定,属于同一个 BFC 的两个相邻 Box 的 margin 会发生重叠;
- BFC 的区域不会与 float box 重叠;
- BFC 就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素;
应用:
- 清除浮动、防止 margin 重叠;
- 确保内部元素与外部元素的布局互不干扰;
10.伪元素和伪类的区别和作用?
区别:
伪元素
用于选择器无法直接选择元素的某个部分;伪类
用于选择特点状态或行为的元素;伪元素
创建的是虚拟元素;伪类
描述的是元素的实际状态;
作用:
伪元素
:- 插入装饰性的小图标或文本;
- 用于修饰文本的首行或首字母的样式;
伪类
:- 用于响应式设计,如鼠标悬停时改变元素的样式;
- 用于基于用户交互的样式变化,如点击或聚焦事件;
11.进程与线程的概念?
进程
:是操作系统进行资源调度和分配的基本单位,它代表了系统运行时的程序实体。每个进程至少包含一个线程,并且可以运行多个线程;
线程
:是进程的子任务,是 CPU 调度和分派的基本单位,是操作系统可识别的最小执行和调度单位,每个线程都独自占用一个虚拟处理器,拥有自己的寄存器组、指令计数器和处理器状态。
区别:
独立性
:进程是操作系统资源分配的基本单位,线程则是处理器任务调度和执行的基本单位;内存管理
:每个进程都有自己的内存空间,线程则共享同一进程的内存空间;并发性
:进程可以包含多个线程,并且可以实现并发执行;线程则可以实现进程内部的并发执行;创建和销毁
:进程的创建和销毁通常比线程更为复杂和耗时;
12.延时加载 js 的方式有哪些?
defer 属性
:在 script 标签中添加 defer 属性;async 属性
:在 script 标签中添加 async 属性;动态加载模块
:动态使用 import()方法来延迟加载模块;动态创建 script 标签元素
:通过 JavaScript 动态创建 script 标签元素,并将其添加到 DOM 中;SetTimeout 延迟加载
:使用 SetTimeout 函数延迟执行一段代码或加载 JavaScript 文件;将 JavaScript 文件放在 HTML 文档底部
;
13.null 和 undefined 的区别?
语义和用途
:null 表示一个对象预期存在实际上是空的;undefined 表示一个变量或属性已声明但未定义或被赋值;类型转换
: 当 null 转换为数值时,值为 0;而 undefined 转换为数值时,值为 NaN;检测
:在比较 null 和 undefined 时,应该使用===
,使用==
时,null 和 undefined 会互相等价;
14.==
和===
有什么不同?
==(非严格等于)
:在进行比较时,会先检查比较的对象是否为同一类型,如果是的话,则比较它们的值;如果不是,则会进行类型转换再比较它们的值;
===(严格等于)
:在进行比较时,不仅会检查比较是否为同一类型,还会比较它们的值是否相等,不会进行类型转换再比较;
15.js 数组去重
- Set 对象:[...new Set(arr)];
- filter + indexOf 方法;
- 循环 + indexOf 方法;
- 循环 + includes 方法;
- 双重循环去重;
- map 对象结合 filter 方法;
16.new 操作符具体做了什么?
- 创建一个新的空对象,这个新对象将成为函数的实例;
- 将这个空对象的 proto 指向构造函数的原型,新对象可以访问构造函数原型对象中定义的方法和属性;
- 将构造函数的 this 指向这个新对象,通过 this 可以引用构造函数中的属性和方法;
- 执行构造函数中的代码,构造函数中的代码将用于初始化新对象的属性;
- 如果构造函数中没有返回其他对象,那么 new 操作符将返回新创建的对象实例。否则,返回构造函数中返回的对象;
实现:
js
function myNew(Con, ...args) {
let obj = {};
Object.setPrototypeOf(obj, Con.prototype);
let res = Con.apply(obj, args);
return res instanceof Object ? res : obj;
}
17.什么是闭包?
闭包
就是函数嵌套函数,内部函数被外部函数返回并保存下来,就会产生闭包;
特点:
- 函数嵌套函数
- 函数内部可以引用外包的参数和变量;
- 参数和变量不会被垃圾回收机制回收;
优点:
- 保护函数内变量的安全;
- 方便调用访问上下文的局部变量;
- 可以用来定义私有属性和私有方法;
- 可以重复使用变量,并且不会造成变量污染;
缺点:
- 闭包较多会消耗内存导致性能下降;
- 导致内存泄漏
使用场景:防抖、节流、迭代器、柯里化、记忆化函数
18.什么是暂时性死区?
暂时性死区
:变量声明到声明完成的区域,这个区域是一个封闭的作用域,直到声明完成;如果在变量声明之前使用该变量,那么该变量是不可用的,也就被称为暂时性死区;
19.什么是原型,什么是原型链?
原型
:prototype 允许我们为对象类型定义属性和方法,当创建一个新的对象实例时,这个实例会内部链接到这个 prototype 对象,从而访问到其身上的属性和方法。
原型链
:本质是一个链表,当使用一个构造函数时,就会返回一个实例,在这个实例上找某个属性没找到时,会顺着 proto 属性指向它的原型,去原型上找,如果原型上也没找到,会顺着原型的原型找,一直到最终 Object 的原型,也就是 Object.prototype
为 null。
应用场景:
- JQ 源码:所有方法放在$原型上,以便每个文件能使用;
- Vue2 数组双向绑定:通过原型的继承属性,把数组 push 等数组方法都改变成支持数组的双向绑定;
20.什么是回调函数?回调函数有什么缺点?如何既然居然回调地狱问题?
回调函数:
定义
: 回调函数是一种特殊的函数,它作为参数传递给另一个函数,并在特定时间被调用执行;
作用
:
- 处理异步操作:
- 响应事件:如用户点击按钮、定时器到期;
- 简化代码结构: 通过使用回调函数,将某些逻辑从主函数中分离出来进行封装,是代码更加模块化和可维护;
缺点
:
- 回调地狱: 多个异步操作依赖时,嵌套的回调函数可能导致代码难以维护;
- 错误处理困难: 在多层嵌套的回调函数中,错误处理变得更加复杂;
- 可读性差:回调函数的嵌套使用,可能使代码难以阅读;
回调地狱:
定义
:回调函数的层层嵌套,就是回调地狱;造成代码复用性不强,可阅读性差,可维护性差和扩展性差;
本质
:
- 嵌套函数存在严重的耦合,牵一发而动全身
- 错误处理比较困难,不能使用 try catch 和不能直接 return;
避免回调地狱
:
- 保持代码简短简洁;
- 模块化;
- 使用 Promse 提供的链式调用和错误处理都得机制,减少回调函数的嵌套;
- 使用 async/await 允许在异步代码中使用同步的写法,使得异步操作更加直观;
21.js 的继承有哪些?
- 原型链继承:通过设置子类的原型为父类的实例来实现继承;
- 借用构造函数继承: 通过复制父类的实例属性给子类来实现继承;
- 实例继承: 通过设置子类的原型为父类的实例来实现继承;
- 组合式继承:通过混合原型链继承和借用构造函数继承的继承方式;
- 寄生组合继承:通过设置子类的原型为父类的实例,然后在这个实例上添加新的属性和方法;
22.什么是事件委托?
事件委托
:利用事件冒泡的机制实现,也就是把子元素的事件绑定到父元素的身上;
好处:
- 提高性能,减少事件的绑定;
- 减少内存占用;
应用场景:
- 给列表下的子元素绑定事假;
- 表单中,多个输入框的输入事件;
- 拖拽排序,处理拖拽开始、拖拽移动和拖拽结束等多个时间;
23.说说对作用域和作用域链的理解?
作用域:保住我们更好的管理和使用变量和函数,提高代码的效率和可维护性;
全局作用域
:能够在全局使用,可以在代码的任何地方被调用;
函数作用域
:如果一个变量在函数内部声明的,它就在一个函数作用域下面。这些变量只能在函数内部访问,不能在函数以外去访问;
块级作用域
:在代码块内定义的变量和函数,只能在该代码块内被访问,超出该代码块无法访问;
作用域链:当访问有个变量时,首先会在当前作用域下查找,如果当前作用域下没有查找到,则返回上一级作用域进行查找,直到找到全局作用域,这个查找过程形成的链条就叫做作用域链;
24.说说 call、apply、bind 的区别?如何实现一个 bind?
call、apply 和 bind 都是 Function 原型中的方法,都是可以改变函数体内 this 的指向的;
区别:
执行方式不:
call
和apply
是立刻执行的;
bind
是不会立即执行的,因为 bind 返回的是一个函数,需要加括号调用才会执行。
传参方式不同:
call
:接受一个 this 指向和一个参数列表;
bind
:接受一个 this 指向和一些参数,返回一个新的函数;
apply
:接受一个 this 指向和一个数组;
使用场景:
call
:实现继承属性和方法,多重继承;
bind
:定时器和事件监听器、异步函数(保持 this 的指向),多重继承;
apply
:实现继承属性和方法,多重继承;
25.this 的指向?
- 普通函数调用: this 通常指向全局对象,浏览器环境中是 window 对象;
- 对象方法调用: 当函数作为对象的一个方法被调用时,this 指向该对象;
- 构造函数: 在构造函数中,this 指向正在被创建的新实例对象;
- 时间处理函数: 在时间处理函数中,this 指向触发事件的 DOM 元素;
- 箭头函数:箭头函数有自己的 this 词法作用域,它继承自箭头函数被定义的上下文;
- call、apply 和 bind:这三个函数可以设置函数调用时的 this;
- return: return{},this 指向的是返回的对象,return 对象以外的值,this 是指向实例的;
总结:
函数 A 中有 this,A 没有上一级,this 指向 Window 对象;
函数 B 中有 this,B 上还有 A,this 指向 A;
函数 C 中有 this,C 上还有 B,B 上还有 A,this 指向 B;
26.如何获得对象非原型链上的属性?
hasOwnProperty()
方法检查一个对象是否是该对象自有的属性。
Object.getOwnPropertyNames()
方法获取一个对象上所有的自有属性(非原型链上的属性)的名字数组。
js
var obj = {name:'张山',age:18}
console.log(obj.hasOwnProperty('name')) // true
console.log(Object.getOwnPropertyNames(obj)) // name age
27.JS 中什么是垃圾回收机制?
垃圾回收机制
:是一种自动内存管理机制,它可以自动地识别不再使用的变量和对象,并将它们从内存中清除,以释放内存空间;
原理:
标记-清除算法
:垃圾回收器会从根对象开始遍历内存中所有对象,标记被引用的对象为活动对象,然后清除未被标记的对象,释放它们的内存空间;引用计数算法
:垃圾回收器会为每个对象维护一个引用计数器,记录当前对象被引用次数。当一个对象的引用被释放时,引用器减一。当引用计数器为零时,表示该对象不再被引用,即为垃圾对象,垃圾回收器会立即回收并释放其占用的内存空间;
28.深拷贝和浅拷贝?
浅拷贝
: 复制的只是原数据的内存地址,两个数据指针指向了相同的地址,其中任意一个数据元素发生变化,会影响另一个;如:假设 B 复制了 A,当修改A 时,B 发生了变化,就是浅拷贝;
浅拷贝实现方式
:
- Object.assign();
- 扩展运算符;
- Array.prototype.slice()/Arr.prototype.concat();
深拷贝
:两个数据指向了不同的地址,数据元素发生变化时互不影响;如:假设 B 复制了 A,当修改 A 时,B 没有变化,就是深拷贝;
深拷贝实现方式
:
- 循环递归
js
function deepClone(obj) {
let objClone = Array.isArray(obj) ? [] : {};
if (obj && typeof obj === "object") {
for (key in obj) {
if (obj.hasOwnProperty(key)) {
// 判断obj子元素是否为对象,如果是,递归复制
if (obj[key] && typeof obj[key] === "object") {
objClone[key] = deepClone(obj[key]);
} else {
// 如果不是,简单复制
objClone[key] = obj[key];
}
}
}
}
return objClone;
}
- JSON.stringify()
29.浏览器事件循环机制,宏任务和微任务?
JavaScript 是一个单线程的非阻塞的脚本语言。为了防止代码阻塞,把代码分为了同步和异步。同步的代码会放入 JS 引擎中执行,异步的代码会放入宿主环境中执行。同步代码放入执行栈中,异步的代码等到时机成熟再放到任务队列排队。执行栈中的同步代码执行完毕,会去任务队列查看是否有异步任务,有就送到执行栈中执行,反复循环查看执行,这个过程就是事件循环;
总结
:同步任务,放入运行栈;异步任务,放入任务队列
- 宏任务:ajax、setTimeout、setInterval、DOM 监听等;
- 微任务: Promise 的 then()、catch()和 finally()方法;
执行顺序:同步代码 ==> process.nextTick ==> 微任务 ==> 宏任务 ==> setImmediate
30.Vue 的核心是什么?
核心:数据驱动(数据的双向绑定)+ 组件化;
数据驱动
:Vue 遍历 data 中对象的所有属性,并使用 Object.difinePropety()把这些属性全部转为 getter 和 setter,以此能够追踪依赖并且在数据发生变化是通知相关组件。当属性的值被访问时,Vue 会通过 getter 收集依赖于该属性的订阅者。当属性的值被修改时,setter 会触发,setter会通知所有依赖该属性的Watcher对象进行更新;
组件化
:扩展 HTML 元素,封装可重复用的代码;
核心:模板 + 初始数据 + 接收外部参数 + 生命周期钩子函数 + 私有资源(自定义指令、过滤器、组件)
31.Vue的自定义指令?
全局注册自定义指令
javascript
Vue.directive('my-directive', {
bind: function (el, binding, vnode) {
// 输入法绑定逻辑
},
inserted: function (el) {
// 元素已插入到DOM中
},
update: function (el, binding, vnode, oldVnode) {
// 组件更新
}
})
局部注册自定义指令
javascript
new Vue({
el: '#app',
directives: {
'my-directive': {
// 同样的钩子函数
inserted: function (el) {
// 逻辑代码
},
update: function (el) {
// 逻辑代码
}
}
}
})
32.v-model 双向绑定的原理?
如何实现:
v-model是v-on和v-bind的语法糖,v-on用于事件的绑定,给当前组件绑定一个input事件,v-bind用于属性绑定,给当前组件绑定一个value属性;v-model双向绑定的实现,是在父组件内给子组件添加v-model,相当于给组件添加了个value属性,传递的值就是绑定的值。和绑定了一个此绑定值的input事件。子组件中使用prop属性接收这个value值,名字必须是value。子组件内部更改value值的时候,必须通过$emit事件派发一个input事件,并携带最新的值。v-model绑定的input事件会自动监听这个input事件,把接收到的最近新的值赋值给v-model绑定的变量;
原理:
采用数据劫持 + 发布者-订阅者模式
的方式,通过 Object.difinePropety()
方法劫持各个属性的 getter
和 setter
。在数据变动的时候,发布消息给订阅者,触发相应的监听回调;
33.v-if 和 v-show 有什么区别?分别使用在什么场景?
控制手段不同
:v-show 隐藏是通过为元素添加 display:none,DOM 元素依旧在。v-if 的隐藏是将 DOM 整个删除;
编译过程不同
: v-show 只是简单的基于 css 的切换,不会触发组件的生命周期;v-if 切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件。
编译条件不同
:v-show 在任何条件下都被编译,然后被缓存,而且 DOM 元素保留;v-if 只有在真的时候才会开始局部编译
性能消耗不同
: v-show 性能开销较小,特别适合频繁的切换场景;v-if 由于涉及到 DOM 的添加删除操作,在切换时有较大的性能开销;
使用场景
:频繁切换用 v-show,很少变化的使用 v-if;
34.v-if 和 v-for 为什么不建议混合使用?
由于 v-for 的优先级高于 v-if,Vue 会先执行 v-for,然后对每个循环结果进行 v-if 判断。即便是 v-if 为 false 时,v-for 仍然会执行,这会会造成不必要的计算和 DOM 操作,从而影响性能;
35.Vue 项目中为什么要在列表组件中写 key,其作用?
性能优化
:key 的作用是给每一个虚拟 DOM 节点的唯一 id,通过 key 的值,可以更准确,更快的拿到虚拟 DOM,主要依赖于 Diff 算法。在修改节点的时候,Diff 算法可以对其进行优化,通过真实节点和虚拟节点的 key 对比,相同就复用,不相同就删除旧的创建新的,从而优化更新过程,减少不必要的重新渲染。
状态维护
:key 可以帮助 Vue 在数据更新时保持每个列表项的状态。如果没有 key,Vue 可能无法准确的将旧状态与新的列表项对应起来,导致状态丢失或混乱;
36.Vue 中使用 v-for 时,不能用 index 作为 key?
因为 index 不具有唯一性,如果在一个列表中删除了一个子项所有的 index 都会变化,那么 Diff 算法会计算出后面子项的 key-index 映射都发生了变化,就会全部渲染,从而产生不必要的性能开销。如果结构中包含输入类放入 dom 时,就会产生错误的 DOM 更新;
37.为什么 Vue 是单向数据流?
数据只能有父组件向子组件单向流动,可以轻松的追踪数据的流动,了解数据之间的传递,极大地简化了代码的测试和维护过程。父组件传递的数据,只有父组件可以改变数据,子组件只能接收不能修改,这种设计可以让我们更好的控制数据的变化,使得代码更容易维护,从而不需要担心子组件修改数据导致出现错误。
38.Vue 中 data 为什么是一个函数?
Vue中data必须是一个函数,是为了保证组件得独立性和避免数据污染
。因为 Vue 组件是可以被复用的,一个组件可能会被用在多个地方,每个组件实例都需要拥有自己的独立管理的数据。如果 data 不是一个函数,而是一个对象,那么所有的组件实例都共享一个对象的应用。当改变一个组件实例数据,其他所有组件实例的数据也会跟着改变,这会导致组件之间的数据边界变得模糊,难以维护。
39.Vue 的生命周期函数?
beforCreate: DOM(无),data(无);
created: DOM(无),data(有);
beforMount:DOM(无),data(有);
mounted:DOM(有),data(有);
activated:组件激活,使用 keep-alive 时会执行;
deactivated:组件停用,使用 keep-alive 时会执行;
beforUpdata:
updated:
beforDestory:
destoryed:
_父子组件生命周期执行顺序_
:
beforCreate(父
) ==> created(父
) ==> beforMount(父
) ==> beforCreate(子) ==> created(子) ==> beforMount(子) ==> mounted(子) ==> mounted(父
)
_更新过程_
:
beforUpdata(父
) ==> beforUpdata(子) ==> updata(子) ==> updata(父
)
_销毁过程_
:
beforeDestroy(父
) ==> beforeDestroy(子) ==> destroyed(子) ==> destroyed(父
)
40.说一说 computed 和 watch 的区别?
watch 和 computed 都是以 Vue 的依赖追踪机制为基础的,当某一个依赖型数据发生变化时,所有依赖这个数据的相关数据会自动发生变化,自动调用相关的函数,来实现数据的变动。
不同:
1.实现机制不同
- watch 监听的是一个具体的数据,当数据发生变化之后会执行相关的操作;
- computed 计算属性,基于 data 和 props 中的数据通过计算得到一个新的值,这个值根据已知的值变化而变化。而数据发生变化时不会立即执行相关操作,而是等待下一次任务队列的更新时机;
2.缓存方面不同
- watch 不支持缓存,监听的数据发生变化时,会直接触发相应的操作;
- computed 函数支持缓存只有在依赖数据发生改变时,才会重新进行计算;
3.调用 return 不同
- watch 可以没有 return 返回;
- computed 必须有 return 返回;
4.对异步操作支持不同
- watch 支持异步操作
- computed 函数不支持异步,当 computed 内有异步操作时无效;
5.擅长处理的场景不同
- watch 适合当一条数据影响多条数据的时候使用;
- computed 适合当一个属性受到多个属性影响的时候使用
41.说一说 Vuex 的原理、核心及使用 ?
原理:
在于集中式存储机制,用于管理应用程序的所有组件状态。它的设计受到 flux 和 redux 的影响,遵循特定的规则以确保状态一种可预测的方式发生变化;
核心:
- state:全局共享属性,用于存储应用程序的数据;
- getter:相当于计算属性,针对 state 数据进行过滤或计算
- mutation:用于更改 state 状态的唯一方法,是同步的;
- action:可以存放异步方法,并且用于提交 mutation,而不是直接改变状态;
- moudule:把 Vuex 再次进行模块之间的划分;
使用:
获取 state 值
:
- this.$store.state.xxx
- mapState 辅助函数
修改 state 值
:
- 同步操作(mutation):this.$store.commit('mutation 方法名',值);
- 异步操作(action 提交 mutation):this.$store.dispatch('mutation 方法名',值);
42.Vue 组件之间的通信方式?
父传子:
props
、this.$parent
、provide/inject
、this.$refs
;
子传父:
this.$emit
、this.$children
、this.$refs
;
兄弟互传:
事件总线(Event Bus)
、$attrs
爷孙传值:
provide/inject
43.Vue 如何检测数组变化的?
使用函数劫持的方式,重写了数组的方法(push,pop,shift,unshift,splice 和 reverse)。然后将 data 中的数组,进行了原型链重写,通过原型链指向了自定义的数组原型方法,地方调用数组 api 时,可以通知依赖更新,如果数组中包含着引用类型,会对数据中的引用类型再次进行监控。
44.vue 的router和route 区别是什么?
router
: 是 Vue Router 的实例,是一个全局唯一的路由器对象。包含了所有路由规则和方法,用于实现页面的跳转、拦截、导航和历史记录等功能;
route
:代表当前路由的对象,包含了当前路由器的各种信息。通过$route 对象,可以访问当前路由的参数、路径、名称、元信息等属性。
跳转路由
:
- this.$router.push()
- this.$router.replace()
路由传参
- 在路由路径后面添加查询参数;this.$router.push({path:'跳转路径',query:{id:data}})
- 通过在路由配置中定义参数占位符
- 通过在路由器中添加 props 属性来实现
45.如何使用 Vue-router 实现懒加载的方式?
vue-router 配置路由,使用 vue 的异步组件技术,可以实现懒加载
使用 import()函数
来进行组件的动态导入。这种方式实现在路由被访问时才加载对应的组件,而不是一开始就加载所有组件;
使用webpack的require.ensure()函数
来达到懒加载效果
46.vue-router 路由钩子函数是什么?执行顺序是什么?
全局路由钩子
:
- beforEach:在路由改变之前调用 (进行权限校验、数据加载等)
- beforResolve:在路由改变之前,在守卫和组件内守卫之后调用
- afterEach:在路由改变之后调用 (记录页面访问日志、清理操作等)
路由独享钩子
:
- beforeEach:在路由独享的守卫中,为路由单独配置守卫
组件内钩子
:
- beforRouteEnter:在进入路由前调用,此时组件实例还未创建,不能访问 this;
- beforRouteUpdata:在路由改变之前,但是该组件被复用是调用,可以用来更新数据或执行一些其他操作;
- beforRouteLeave: 在离开路由前调用 (保存数据、提示用户)
执行顺序
:
- 导航被触发;
- 在失活的组件里调用 beforRouteLeave 守卫;
- 调用全局的 beforEach 守卫;
- 在重复的组件里调用 beforRouterUpdata 守卫;
- 在路由配置里调用 beforEnter;
- 解析异步路由组件;
- 在被激活的组件里调用 beforRouterEnter;
- 调用全局的 beforResolve 守卫;
- 导航被确认;
- 调用全局的 afterEach 钩子;
- 触发 DOM 更新;
- 调用 beforRouteEnter 守卫中传给 next 的回调函数,穿件好的组件实例会作为回调函数的参数传入;
47.keep-alive 的作用?
主要作用:
keep-alive 是 Vue 中的一个组件,主要用于缓存不活跃的组件实例,以便在需要时能够快速地重新渲染,而不会重新创建新的组件实例。这种缓存极大地提高了页面加载速度和响应速度,尤其再移动端运行的效果更加明显。
原理:
keep-alive 组件利用其两个生命周期函数:activated
和deactivated
来实现缓存。deactivated
函数会在组件被从页面上移除时调用,此时keep-alive 组件将当前的组件实例保存在缓存中,但不会销毁该实例。activated
函数会在组件被渲染到页面上之后调用,此时 keep-alive 组件会将之前缓存的组件重新渲染到页面上,而不会重新创建实例。keep-alive 组件还使用了 LRU 算法来管理缓存的组件实例,当缓存的组件数量超过一定的阈值时,较早的组件会被销毁,释放内存的空间。
应用场景:
-
缓存动态组件:如新闻网站中,文章详情和文章列表直接经常切换,可以将详情页的组件使用 keep-alive 进行缓存,当再次进入详情页时,可以直接从缓存中取出组件实例,而不需要重新创建。
-
缓存路由:如后台管理系统,需要显示一个侧边栏,当用户切换菜单时,对应的路由组件需要重新加载,我们希望在切回原来菜单时保留之前的状态,而不需要重新加载。
48.动态组件
动态组件是一种可以动态切换组件内容和结构的技术,允许我们在不刷新整个页面的情况下,动态加载不同的组件,并在同一个位置上切换;它的核心概念是 keep-alive,缓存被包裹的动态组件的状态和实例,避免在切换组件得时候销毁和重新渲染;可以借助 component 标签和 is 特性,实现动态加载组件;
49.Vue 中的组件和插件有什么区别?
组件:构成 APP 的业务模块。它的目标是 APP.vue;组件可以扩展 HTML 元素,封装可重用的代码。
插件:对 Vue 的功能的增强,常用来为 Vue 添加全局功能,它的目标是 Vue 本身;
注册方式
:
- 组将:Vue 实例中注册/Vue.component()全局注册
- 插件:Vue.use()方法注册;
使用场景
:
- 组件:构成业务模块,封装可复用的 UI 组件;
- 插件: 增强 Vue 功能,添加全局方法或属性;
可复用性
:
- 组件:高,可在不同实例之间复用;
- 插件:低,通常针对特定 Vue 实例;
生命周期
:
- 组件:拥有完整的生命周期;
- 插件:通常不涉及到生命周期;
状态管理
:
- 组件:有自己的状态,通常通过 props 和 emit 事件与父组件通信;
- 插件:通常不涉及状态管理;
全局功能
:
- 组件:不提供全局功能;
- 插件:提供全局功能,如自定义指令,过滤器、过渡等;
适用范围
:
- 组件:适用于构建用户界面;
- 插件:适用于扩展 Vue 的核心功能;
50.Vue 中的模板编译原理?
原理:
解析
: Vue 将模板字符串转换成抽象语法树。在这个过程中,Vue 会识别模板中的各种元素,包括普通 HTML 标签、指令、插值表达式,并将它们转换成抽象语法树;优化
:Vue 会对抽象语法树进行优化,比如静态节点提升、静态属性提升等,以减少运行时的开销;代码生成
:最后 Vue 会遍历抽象语法树,并根据抽象语法树生成一个渲染函数。这个函数会接收一个数据对象作为参数,并返回一个虚拟 DOM。Vue运行时,会调用这个函数,传入当前的数据对象,从而生成对应的虚拟 DOM 节点;
51.Vue 为什么要用虚拟 Dom?
提高性能
:直接操作真实 DOM 需要频繁地进行重绘和重排,而虚拟 DOM 可以批量更新差异,减少对真实 DOM 的操作,从而提高页面渲染效率;简化开发
:开发者只需要关注数据的变动,而不需要关心具体的 DOM 操作。Vue 会自动处理这些细节,将数据变化映射到 DOM 的变化上;跨平台支持
: 因为虚拟 DOM 是一个独立于平台的 JavaScript 对象,所以可以在不同平台上使用;
52.Vue 实例挂载过程?
创建 Vue 实例
:通过构造函数,创建一个 Vue 实例,并传入配置对象;初始化
:在初始化阶段,Vue 会进行一系列的初始化操作,包括合并配置项、初始化生命周期钩子函数、初始化事件系统、初始化响应数据等;模板编译
:如果配置中存在 template 选项,Vue 会将模板编译成渲染函数。渲染函数是 Vue 中用于生成虚拟 DOM 的函数;创建虚拟 DOM
: 通过调用渲染函数,Vue 会根据模板生成虚拟 DOM。执行挂载函数
:Vue 会调用 mount 函数,将虚拟 DOM 渲染成真实 DOM,并将其插入到页面中指定的挂载点上;数据响应式
:在挂载完成后,Vue 会对数据进行响应式处理。当数据放生变化时,Vue 会自动更新相关的试图;完成挂载
:Vue 实例挂载完成后,会触发 Mounted 生命周期钩子函数函数。在这个阶段,可以进行一些初始化的异步操作,或者与外部库进行交互。
53.简述 Vue.set 方法原理?
原理 :
Vue.set 方法首先会检查目标对象是否是响应式的,也就是有没有_ob_
属性;如果是响应式的,Vue.set 会调用 Object. definedProperty 方法来定义一个新的属性,并为其设置 getter 和 setter。当这些属性被访问或修改时,就可以获取或设置它们的值,同时也可以触发相关的依赖更新;在定义完新属性后,Vue.set 方法会将给定的值赋给这个新属性;最后,Vue.set 方法会触发视图更新,以反映新属性的变化;
54.$nextTick 的作用是什么?它的实现原理是什么?
作用:
$nextTick
是 Vue.js 中的一个方法,作用是就是将里面的回调函数延迟下次 DOM 更新结束之后执行,在修改数据之后立即使用这个方法,获取更新后的
DOM。
原理:
$nextTick
返回了一个 promise.then()方法,确保函数在 DOM 更新之后执行,从而保证在视图更新完成后执行特定操作需求;
55.Vue scoped 能做样式隔离的原理?
本质是基于 HTML 和 css 的属性选择器,就是分别给 HTML 标签和 CSS 选择器添加 data-v-hash 来实现的。具体来说是通过 vue-loader 实现的。首先Vue-loader 会解析.vue 组件,提取出 template、script、style 相对应的代码块。然后构造组件实例,在租件实例的选项上绑定 scopedId。然后对style 的 css 代码进行编译转化,应用 ScopedId 生成选择器的属性;
56.为什么 Vue 采用异步渲染及异步渲染的原理?
Vue 采用异步渲染主要原因是为了提升应用的性能。如果不采用异步渲染,那么每次更新数据都会对当前组件进行重新渲染,这样可能就会大量 DOM 重排或者重绘,所以为了性能考虑,减少浏览器在 Vue 每次更新数据后会出现 DOM 的开销,Vue 会在本轮数据更新后,再去异步更新视图;
原理
:主要是基于 JavaScript 的事件循环机制。通过 Pomise、MutationObserver 或者是 setTimeout 来实现。Vue 会在内部使用一个队列来收集同一事件循环中的数据变化,在下一个事件循环开始时,才会执行 DOM 更新;
57.说说对 Vue 的 diff 算法的理解?
Vue 的 diff 算法是虚拟 DOM 实现的核心部分,主要用于比较新旧虚拟 DOM 数的差异,并找出最小的 DOM 操作方式来更新 DOM 算法。从而实现提高性能,减少不必要的 DOM 更新,从而加快页面的渲染速度。
Vue 的 diff 算法主要策略:
深度优先遍历
: Vue.js 使用深度优先遍历的方式对虚拟 DOM 树进行节点比较。这意味它会逐层向下遍历节点,直到找到需要更新的节点为止。key值唯一性
: 在 Vue.js 中每个节点都有唯一的 key 值。当比较两个节点时,Vue.js 首先会检查它们的 key 值是否相同。如果 key 不同,那么这两个节点肯定不同需要重新渲染。如果 key 值相同,就会比较它们的子节点。节点复用
:Vue.js 的 diff 算法会尽可能的复用已有的 DOM 节点。当新旧两颗虚拟 DOM 树中存在相同 key 值的节点,Vue.js 就会尝试复用这些节点,而不是重新创建它们。这样可以减少不必要的 DOM 操作,提高性能;同层比较
: 在同一层的节点中,Vue.js 会按照节点的顺序进行比较。这意味着它们会从左到右依次比较节点,直到找到需要更新的节点为止;patch 策略
:Vue.js 的 diff 算法再比较节点时,会采用四种 patch 策略:创建新节点、替换旧节点、移动节点和更新节点。根据新旧节点的差异,Vue.js会选择最合适的 patch 策略来更新视图;
58.vue 通过数据劫持可以精准的探测数据变化,为什么还要进行 diff 检测差异?
简洁回答:
是 Vue 内部设计缺陷导致的,Vue 设计的是每个组件一个 watcher,没有采用一个属性对应 watcher。这样导致大量的 watcher 的产生而且浪费内存,如果粒度过低也无法精准的检测到变化。所以采用了 diif 算法+组件级 watcher;
详细回答:
目前前端框架变化侦测的方式主要是 pull 和 push。pull 的代表是 react。Vue 的响应式系统侦测变化的方式是 push,vue 程序初始化时就会对数据 data进行依赖收集,一旦数据发生变化,响应式系统就会立刻知道,因此 vue 在刚开始就知道哪里发生了变化。但是会产生一个问题,通过响应式绑定的数据都会有一个watcher,一旦我们绑定细粒度过高,就会产生大量的 watcher,这会带来内存和依赖追踪上的开销;如果细粒度过低又无法精准的侦测到变化,所以 vue 的设计选择中等绑定细粒度的方案,在组件级别进行 push 侦测的方式。通常在一开始我们就知道发生变化的是哪个组件,然后虚拟 DOM Diff 进行具体差异的检测,因为虚拟DOM Diff 是 pull 操作,所以 Vue 采用的是 push+pull 的方式侦测变化。
59.Vue2 和 Vue3 有什么不同?
根节点不同
- Vue2 必须要有根节点
- Vue3 不需要根节点
组合式 API 和选项式 API
- Vue2 用的是组合式 API
- Vue3 用的是选项式 API
生命周期不同
- Vue2: beforeCreate/Created ==> beforMount/mounted ==>BeforUpDate/ Updated ==> beforDestroy/destroyed
- Vue3: setup ==> onBeforeMount/onMouted ==> onBeforUpdate/onUpdate ==> onBeforUnMount/onUnmounted
v-for 和 v-if 的优先级
- vue2 v-for 的优先级高于 v-if
- Vue3 v-for 的优先级低于 v-if
响应式原理不同
- vue2 采用的是 Object.definedProperty()劫持各个属性的 getter 和 setter
- vue3 采用的是 proxy 代理,不需要去遍历各个属性
Vue3的优势
: 1.加载速度更快 2.打包体积更小 3.对 typeScript 的支持更友好
60.Vue 的响应式原理中 Object.defineProperty 有什么缺陷?为什么用 Proxy,对 Proxy 了解多少?
Object.defineProperty 的缺陷:
- 无法监控数组下标的变化:如通过数组下边添加元素,不能实时响应;
- 只能劫持对象的属性:因此需要对每个对象的属性进行劫持,因此需要对每个对象的每个属性进行遍历。如果属性值是对象,还需要深度遍历,这增加了复杂性和开销;
Proxy:ES6 中一个新的特性,覆盖了对象的读取、写入、删除和属性遍历等操作;
- 可以监听到数组的变化:无需像 Object.defineProperty 那样去额外的处理;
- 可以劫持整个对象:病房会一个新的对象,不需要像 Object.defineProperty 那样遍历对象属性直接修改;
61.简述浏览器缓存机制?
浏览器缓存是一种存储机制,它允许浏览器在本地存储网页及其资源,以便在后续访问时可以快速加载。浏览器缓存机制主要分为:强缓存
和协商缓存
;
强缓存
:浏览器直接从本地缓存中读取数据,不需要向服务器发送请求。控制强制缓存的字段是Expires和Cache-Modified。
- Cache-Control字段用于指定缓存策略,其中max-age指令告诉浏览器资源在缓存中的最长时间。只要资源在缓存中的时间没有超过这个时长,浏览器就会直接使用缓存中的资源,而不是向服务器发送请求;
- Expires字段是一个绝对时间,表示资源过期的具体日期和时间。浏览器会对比当前时间和Expires字段的值,如果当前时间早于过期时间,则使用缓存资源
协商缓存
:使用缓存之前,浏览器需要向服务器发起网络请求,以此根据服务器返回的响应头中的ETag和Last-Modified字段来判断该资源是否更新,进而决定是否使用本地缓存;
ETag
字段是资源唯一的标识符,用于判断资源是否发生变化;Last-Modified
字段便是资源最后修改时间。当浏览器请求资源时,会带上这个字段的值,与服务器的资源进行对比,如果服务器上的资源没有变化,则返回304状态码,告诉浏览器继续使用缓存中的资源。
62.浏览器渲染原理,回流、重绘的概念和原理?
回流
:渲染树中部分或全部元素的尺寸、结构、布局等发生改变时,浏览器就需要重新渲染部分或全部文档的过程。如更改某个元素的尺寸、位置或是添加或删除了可见的DOM元素时,都会触发回流;
重绘
:当渲染树中的某些元素样式的改变不影响其在页面布局中的位置和大小,仅仅影响元素自身的视觉效果时,浏览器只需要对这些元素进行重绘就行。如改变颜色、背景色、透明度等;
63.http 和 https 有何区别?如何灵活使用?
http:是一种广泛使用的网络传输协议,它可以使浏览器更高效、使网络传输减少;
https:是 http 的加密版。是以安全为目标的 http,在 http 中加入了 SSL 安全基础。https 的协议有两种,一种是建立安全信息通道,保证数据传输的安全性;另一种是确认网站的真实性;
区别:
- https 需要用到 SSL 证书;http 不需要;
- http 是明文传输协议,https 是加密传输协议;
- http 标准端口号 80,https 标准端口号 443;
- http 的连接是无状态的,https 协议是由 SSL+http 协议构建的加密传输、身份认证的网络协议,比 http 协议安全;
64.从浏览器输入 URL,到最终展示页面,都发生了什么?
- 浏览器的地址栏输入 URL 并按下回车键;
- 浏览器查找当前 URL 是否存在缓存,并比较缓存是否过期;
- DNS 解析 URL 对应的 IP;
- 根据 IP 建立 TCP 连接;
- HTTP 发起请求;
- 服务器处理请求,浏览器接收 HTTP 响应;
- 关闭 TCP 连接;
- 渲染页面,构建 DOM 树;
65.谈谈你对 TCP 三次握手和四次挥手的理解?
三次握手:
第一次
:客户端发送一个 SYN 包到服务器,表明客户端想要建立一个新的连接;
第二次
:服务器收到到客户到发送的 SYN 包,回复一个 SYN+ACK 包,表示接受客户端的连接请求,并且准备好进行连接;
第三次
:客户端收到服务器的 SYN+ACK 包,在发送一个 ACK 包到服务器,表示连接建立成功;
四次挥手:
第一次
: 客户端发向服务器发送一个 FIN 包,表示客户端不再发送数据,请求关闭双方连接;
第二次
: 服务器收到客户端的 FIN 包,会回复一个 ACK 包,表示确认收到客户端的断开请求,但服务器可能还会有数据需要发送;
第三次
:服务端向客户端发送一个 FIN 包,请求关闭双方连接。
第四次
:客户端收到服务器的 FIN 包,回复一个 ACK 报,表示收到服务器的断开请求,至此四次挥手完成,客户端和服务器之间的 TCP 连接正式断开;
66.简述 MVVM 和 MVC 的原理以及区别?
MVVM和MVC都是一种软件架构模式,都是把软件中的数据、展示和逻辑分离出来,以达到易于维护、扩展和测试等优点;
区别:
数据绑定
- MVVM模式中,数据绑定是通过数据绑定器来实现的,View和和ViewModel之间没有直接关联;
- MVC模式中,需要controller通过View和Model来传递数据;
视图状态
- MVVM模式中,View状态被ViewModel监控和管理,View只负责呈现状态,不会直接修改状态;
- MVC模式中,需要controller和View共同管理视图状态;
可测试性
- MVVM模式中,ViewModel可以通过数据绑定和模拟数据来方便地进行单元测试;
- MVC模式中,Controller和View都需要准备模拟数据才能进行单元测试;
原理:
- MVVM的原理是将View和Model都得通信转化为View和ViewModel的通信,ViewModel主要负责View中的用户输入和展示逻辑,并将更新的数据通知给View,从而实现数据和展示逻辑的分离;
- MVC的原理是将应用程序分为Model、View和Controller三部分,Model提供数据操作,View负责展示,Controller作为中间人调度Model和View的交互;
67.AMD 与 CMD 的区别?
-
AMD
是 RequireJS 推广的模块定义规范,主张依赖前置。这意味着在定义模块时就需要声明其依赖的其他模块,并在模块执行前确保这些依赖已经加载并可用。AMD 的优点在于能够提前解决依赖关系,确保模块在运行时不会因缺少依赖而出错。然而,这也可能导致一些不必要的加载开销,特别是在大型项目中。 -
CMD
是 SeaJS 推广的模块定义规范,它推崇依赖就近原则和延迟执行。这意味着模块只在需要时才声明和执行依赖,这有助于减少初始加载时的开销,提高页面加载速度。CMD 的 API 设计严格区分,每个 API 都简单纯粹,职责单一,这有助于保持代码的清晰和可维护性。
总的来说
,AMD 和 CMD 都是解决 JavaScript 模块化问题的有效方案,它们的选择取决于项目的具体需求、开发团队的偏好以及目标运行环境。在一些需要预先解决依赖关系或确保模块运行稳定的场景中,AMD 可能更为合适;而在追求页面加载速度和代码清晰度的项目中,CMD 可能更具优势。无论选择哪种规范,都应该以提高代码的可维护性、可复用性和性能为目标。
68.require.Js 与 seaJs 的异同是什么?
相同之处:
- 都是JS模块加载器,旨在实现JS的模块化开发,提高代码的可维护和复用性;
- 都支持异步加载JS模块,从而避免阻塞页面的渲染和其他脚本的执行;
不同之处:
- 遵循规范不同:
- RequireJS遵循是AMD规范,是一个异步模块定义的规范,它允许模块在加载完成之前就进行异步操作,不阻塞页面的渲染和其他脚本执行;
- SeaJS遵循的是CMD规范,是JS模块定义规范,更加简单直观,注重代码的依赖声明和模块的按需加载;
- 插件机制
- RequireJS具有较为完善的插件机制,可以通过插件来扩展其功能,如文本、JSON、CSS等插件;
- SeaJS同样具有插件机制,但相比之下可能不如RequireJS丰富;
-
API和使用不同
由于遵循的规范不同,RequireJS和SeaJS在API和使用方式上有所差异。开发者需要根据项目的具体需求和团队的变成习惯来选择合适的框架;
-
对第三方类库的支持
- RequireJS试图让第三方类库修改自身以支持RequireJS,但这一推广理念目前只有少数社区采纳;
- SeaJS更加开放和灵活,能够更好地支持各种第三方类库;
- 调试支持
- RequireJS在调试支持方面可能没有SeaJS那么明显;
- SeaJS非常关注代码的开发调试,提供了诸如nocache、debug等用于调试的插件;
69.简述什么叫优雅降级和渐进增强?
优雅降级
:一开始就构建完整的功能,然后针对低版本浏览器进行兼容;
渐进增强
:一开始正对低版本浏览器构建页面,保证基本的功能,然后在针对高级浏览器进行效果、交互等改进和追加功能达到更好的用户体验;
70.哪些常见操作会前端造成内存泄漏?
- 无线增长的数据结构,没有删除或释放其中的元素;
- 两个或多个对象互相引用,而没有被其他对象引用,这个对象就无法被垃圾回收机制回收;
- 定时器或时间监听器没有被正确清除;
- 资源没有被正确释放(文件、网络连接、数据库连接)
- 缓存没有被正确管理;
- 强制类型转换不当;
71.简述 export、export default 和 module.export 的区别是什么?
export、export default和module.exports是在JavaScript中导出模块属性和方法的三种不同方式;
区别:
语法和来源
- module.exports和exports是CommonJS模块的规范,主要用于Node.js环境;
- export和export default是ES6模块的规范,适用于现代浏览器和Node.js环境;
导出方式
- module.exports用于导出单个对象或函数
- exports实际上是module.exports的一个引用。
- export 用于导出模块的多个变量、函数、对象或类;
- export default用于导出模块的默认值;
导入方式
- 使用module.exports导出的模块,在Node.js中通常使用require函数导入;
- 使用export和export default导出的模块,在支持ES6的环境,使用import语句来导入;
值的引用与拷贝
- CommonJS模块输出的是值的拷贝;
- ES6模块输出是值的引用;
加载时机
- CommonJS模块是运行加载;
- ES6模块是编译时输出接口;
72.简述下 npm 模块安装机制?为什么输入 npm install 就可以自动安装对应的模块?
读取依赖关系
:当你输入npm install命令时,npm会自动读取当前目录下的package.json文件。这个文件包含了项目的所有依赖关系信息,如依赖的模块名称、版本号等;连接npm注册表
:npm会连接到npm注册表,这是一个全球性的公共仓库,包含了大量的开源JS模块。npm会在其中搜索和获取所需的模块;下载并安装模块
:npm会根据package.json文件中的依赖关系,下载满足条件的模块。这些模块通常以压缩包的形式存在,npm会下载这些压缩包,并存放在根目录下的.npm目录中。然后,npm会解压这些压缩包到当前项目的node_modules目录中,按照模块的层级结构进行组织;执行安装脚本
:如果模块定义了安装脚本(如postinstall脚本),npm会在安装过程中执行这些脚本;处理依赖关系
:如果安装的模块本身也有依赖其他模块,npm会递归处理这些依赖关系,重复上述的下载和安装步骤,直到所有的依赖模块都被安装;
为什么输入npm install就可以自动安装对应模块,这主要归功于npm的智能处理机制:
自动化管理
: npm通过读取package.json文件,自动获取项目的依赖关系信息,从而实现自动化安装;全局注册表
:npm拥有一个全球性的公共注册表,可以很方便的获取和分享代码片段;递归处理
:npm能够递归处理模块的依赖关系,确保所有的依赖项都被正确安装,无需用户手动干预;
73.Javascript 模块化是否等同于异步模块化?
JS模块化并不等同于异步模块化
;
JavaScript模块化
:是指将JavaScript代码划分为多个独立的部分或模块,每个模块具有特定的功能和作用域。异步模块化
:是指模块加载和执行的过程是异步的,不会阻塞线程;
区别:
- 异步模块化是JavaScript模块化的一种实现方式,但JavaScript模块化本身并不等同于异步模块化。
- JavaScript模块化是一个更广泛的概念,它涵盖了模块的定义、导出、导入和使用,而不一定涉及异步加载。同步模块化也是一种有效的JavaScript模块化方式。
74.简述Webpack打包流程?
读取配置文件
:Webpack首先会读取项目中的webpack.config.js文件,解析其中的配置信息,以便后续的打包过程可以按照这些配置来进行。这些配置包括入口文件、输出文件、模块处理等;找到入口文件
:解析完配置文件后,Webpack会根据配置中的入口文件来寻找项目的起始点。入口文件是一个JavaScript文件,Webpack会从这个文件开始递归地解析项目中的所有依赖关系;解析依赖模块
:在找到入口文件之后,Webpack 会递归地解析项目中的所有依赖模块。这包括 JavaScript 文件、CSS 文件、图片文件等各种类型的文件。Webpack 使用不同的加载器(loader)来解析不同类型的文件,以便将它们转换为浏览器可以理解的格式。编译模块
:在解析完依赖模块后,Webpack 会使用相应的 loader 来编译这些模块。这包括语法转换、代码优化等步骤,以确保代码能够在浏览器中正确运行。合并模块
:编译完成后,Webpack 会将所有模块合并成一个或多个包(bundle)。这个包包含了项目运行所需的所有代码和资源,可以在浏览器中直接加载执行。
75.说说 WebPack 的核心原理?
Webpack是一个静态模块打包器,通过分析模块之间的依赖关系,将所有模块打包成一份或者多份代码包。
Webpack核心:
入口(Entry)
: Webpack以那个文件为入口起点开始打包,分析构建内部关系依赖图;输出(Output)
:Webpack打包后有很多bundle,输出到哪里,以及如何命名;加载器(loader)
:Webpack只认识js、json文件,Loader用于处理那些非js的东西;插件(Plugins)
:插件的范围从打包优化和压缩到一直到重新定义环境的变量;模块(Mode)
:分两种,development能让代码在本地环境运行,开发模式配置的webpack更简单一点。production能够让代码优化上线的环境,要求性能,插件比开发环境的多;
76.怎么提高 WabPack 打包速度?
- 使用最新版本的 Webpack;
- 减少文件数量:按需加载、代码分割、动态导入
- 优化 loader 和插件:选择性能良好的 loader 和插件,并通过配置参数和选项来提高其性能;
77.简述webpack热更新原理,是如何做到在不刷新浏览器的前提下更新页面的?
原理:
- 在Webpack配置中,设置devServer.hot为true,以启用热模块替换功能;
- 启动Webpack Dev Server时,创建一个Socket服务器,用于与浏览器建立WebSocket连接;
- 在浏览器中访问应用程序时,Webpack Dev Server会将一个运行脚本注入到页面中。这个脚本会建立与Webpack Dev Server的WebSocket连接,以便实时接收来自服务器的更新通知;
- 修改源代码并保存,Webpack会监听文件系统的变化,并编译修改后的模块;
- 编译完成后,Webpack会将更新的模块信息发给Webpack Dev Server;
78.简述 WebPack 中 loader 的作用 ?
文件类型转换
:WebPack默认只处理JS文件,但通过loader,可以让WebPack处理其他类型的文件,如CSS、图片、JSON等。文件优化
: loader可以对文件进行压缩、合并等优化操作,以减少文件大小和提高页面加载速度。文件处理
:loader可以对文件进行各种处理,比如替换文本、提取公共代码、转换ES6语法等;文件依赖管理
:loader可以处理文件之间的依赖关系,确保在构建过程中正确地引用和包含所有文件的依赖;
常用loader:
css-loader
: 加载CSS,支持模块化、压缩、文件导入等特性;style-loader
: 将解析后的css,用于style标签挂载到页面的head中;less-loader
: 将LESS代码转换成CSS;sass-loader
: 将SCSS/SASS代码转换成CSS;postcss-loader
: 扩展CSS语法,使用下一代CSS,可以配合autoprefixer插件自动补齐CSS3前缀;raw-loader
: 在webpack中通过import方式导入文件内容,该loader并不是内置的;babel-loader
: 把ES6转换成ES5;eslint-loader
: 通过ESLint检查JS代码;html-minify-loader
: 压缩HTML;image-loader
: 加载并压缩图片文件;file-loader
: 把文件输出到一个文件夹,在代码中通过相对URL去引用输出的文件(处理图片和字体);url-loader
: 与file-loader类似,区别是用户可以设置一个阈值,大于阈值会交给file-loader处理,小于阈值以base64的方式把文件内容注入到代码中去(处理图片和字体);source-map-loader
:加载额外的Source Map文件,以方便断点调试;json-loader
: 用于加载JSON数据;html-loader
: 处理HTML文件,可以将HTML文件中的图片和其他资源作为模块导入到JS中;
79.简述 Webpack 中 plugins 和 loader 有什么区别?
作用不同:
Loader
是用于对模块的源代码进行转换的工具,它可以将一些非 JavaScript 文件(如 CSS、图片、字体等)转换成 JavaScript 模块;Plugins
是用于扩展 Webpack 功能的工具,它可以在 Webpack 运行期间执行一些任务,比如生成 HTML 文件、压缩代码、提取公共代码等。
实现方式不同:
Loader
只专注于转化文件(transform)这一个领域,完成压缩、打包、语言翻译等;而 Plugins 不仅只局限在打包、资源的加载上,还可以打包优化和压缩、重新定义环境变量等。Plugins
是通过 Webpack 的事件机制来实现的,可以在 Webpack 的不同阶段注册不同的事件来实现不同的功能。
80.描述 WebPack 如何切换开发环境和生产环境?
-
配置文件分离
:首先,你需要为开发环境和生产环境分别创建 Webpack 配置文件。这些文件通常命名为webpack.dev.config.js和webpack.prod.config.js,分别对应开发环境和生产环境。在这些配置文件中,你可以定义各自的环境特定设置,比如入口点、加载器、插件和输出目录等。 -
设置环境变量
:使用 Webpack 时,你可以通过环境变量来区分不同的环境。这通常是通过在命令行中设置NODE_ENV
变量来实现的,比如NODE_ENV=development
或NODE_ENV=production
。在 Webpack 配置文件中,你可以通过process.env.NODE_ENV
来访问这个变量,并根据它的值来应用不同的配置。 -
使用 cross-env
:为了跨平台地设置环境变量,你可能会使用cross-env这个工具。cross-env能够在 Windows、Linux 和 macOS 等多个平台上设置环境变量,避免了在不同平台下设置环境变量的差异。 -
修改 package.json
:在package.json文件中的scripts部分,你可以添加两个脚本命令,分别对应开发环境和生产环境的构建。比如,你可以添加start命令用于开发环境,build命令用于生产环境。这些命令会运行不同的 Webpack 配置文件,比如webpack --config webpack.dev.config.js和webpack --config webpack.prod.config.js。 -
运行构建命令
:最后,通过运行相应的 npm 脚本命令,Webpack 会根据你的配置自动切换环境。比如,运行npm start将会启动开发环境的构建,而运行npm run build则会启动生产环境的构建。
81.针对 CSS ,如何优化性能?
减少选择器的数量
:选择器的数量直接影响到页面的加载速度;避免使用复杂的 CSS 属性
:如 border-radius、box-shadow、filter、:nth-child 等,需要浏览器进行较多的计算;使用压缩工具
:使用 CSS 压缩工具,使用 purgeCSS 和 UglifyCSS,移除未使用的 CSS 代码,以减少 CSS 文件体积,提升加载速度;使用缓存
:合理使用缓存可以将常用的 CSS 文件存储在用户的本地设备上,这样下次进来可以直接从本地下载,就不要再次请求服务器,可以极大提高加载速度;避免重绘与重排
:会极大地消耗页面性能;
82.针对 HTML,如何优化性能?
优化图片
- 压缩图片,减少文件大小,提高加载速度;
- 选择正确的图片格式
- 懒加载图片,避免一次性过多加载图片
减少HTTP请求
- 合并 css 和 Javascript 文件,减少 HTTP 请求;
- 避免重复加载资源,
缓存策略
- 通过 HTTP 设置缓存头,让浏览器缓存静态资源,减少重复请求;
- 利用 CDN 来缓存和分发静态资源,提高加载速度;
代码优化
- 精简 CSS 和 HTML 代码,删除不必要的空格和注释,减少文件大小;
- 避免过度嵌套和冗余代码;
异步加载和延迟加载
- 将非关键资源异步加载,如脚本、样式表等,避免阻塞页面渲染;
- 实现延迟加载,如滚动到特定的位置在进行加载;
优化DNS查找
- 减少 DNS 查找次数:通过优化和合并 DNS 查找,减少页面加载的 DNS 延迟;
减少重绘和回流
- 避免频繁的操作 DOM:频繁的操作 DOM 会导致页面的重绘和重排,会消耗大量的性能;
- 尽量使用 CSS3 动画和过渡,避免直接操作 DOM;
避免使用内联样式
- 使用内联样式会增加 HTML 文件的大小,可能会导致缓存失效;
使用语义化HTML标签
- 使用语义化标签,可以提高代码的可读性、和可访问性,有利于提高页面性能和 SEO;
使用性能分析工具
- 使用 Chorome Devtools、lighthouse 等性能分析工具来识别和优化性能瓶颈;
83.针对 Javascript,如何优化性能?
减少DOM操作
:频繁的 DOM 操作会导致页面的重绘和重排,会消耗大量的性能;使用事件缓存
:对于频繁触发的事件,可以使用事件缓存来减少事件处理函数的调用次数;避免全局搜素
:尽量使用局部变量,不要使用全局变量,以减少作用域链的查询时间;使用事件委托
: 将事件监听器绑定在父元素,利用事件冒泡机制来处理子元素事件,减少事件处理函数的数量,提高性能;;使用节流和防抖
:处理一些高频触发的事件时,使用防抖和节流可以限制事件处理函数的执行次数,提高性能;减少HTTP请求
: 合并和压缩 CSS、Javascript 和图片文件,减少 HTTP 请求的数量和大小。使用 HTTP 缓存来存储和重用之前请求过的资源;使用WebWorker
:将大量计算或将耗时的任务移至后台线程中执行,以释放主线程进行其他操作;避免内存泄漏
:及时清理不再使用的变量和对象,避免它们占用内存;代码优化和压缩
:使用压缩工具和代码优化工具对 Javascript 代码进行优化和压缩,去除不必要的空格、注释和代码块,缩小文件体积,提升加载速度;代码拆分和懒加载
:将大型代码库拆分为较小的模块,并使用懒加载来按需加载这些模块;利用硬件加速
: 使用 CSS3 的转换和动画来代替 Javascript 动画,以利用 GPU 加速。避免使用导致页面重绘和重排;使用性能分析工具
: 使用 Chorome DevTools、Lighthouse 等性能分析工具来识别和优化性能瓶颈;
84.简述前端如何优化脚本的执行?
使用缓存和延迟加载
:可以减少页面的加载时间。在需要频繁更新的内容中使用 ajax 技术,只更新需要变化的部分,减少脚本的重复加载;合并和压缩脚本
:合并和压缩 js 文件可以减少 HTTP 请求次数和文件大小,加快文件下载速度,提高网站的整体性能;避免使用全局变量
: 使用全局变量会降低脚本的执行效率,因为在变量的搜素中需要查找整个作用域;避免使用eval()函数
:使用 eval()函数会影响脚本的性能和安全性;避免频繁的DOM操作
:频繁的 DOM 操作影响脚本的性能;避免不必要的重绘和回流
:可以通过改变元素时使用元素的 class 属性来引用样式,而不是直接操作 DOM 的行间样式,减少 DOM 操作;编写高效的代码
:减少重复计算,使用合适的算法和数据结构;
85.请简述 Vue 的性能优化可以从哪几个方面去思考设计?
减少重新渲染次数
:
- 合理使用 v-if 和 v-show,避免不必要的组件渲染;
- 使用 key 管理组件或元素的重用,避免不必要的 DOM 重绘;
- 使用计算属性(computed)或者监听器(watch)来避免无效的渲染
- 合理使用 shouldComponentUpadta 生命周期钩子函数或 Vue3 中的 markRaw 和 shallowRef 来控制组件是否需要更新
优化组件性能
- 将复杂的组件拆分成更小的子组件,避免组件过于庞大;
- 使用函数式组件或 memo/Vue.memo 来避免不必要的渲染;
- 合理使用 slot 和 scoped slot,避免不必要的嵌套和渲染;
合理使用列表渲染
- 使用 v-for 渲染列表时,尽量避免同时操作列表数据,例如对列表进行排序、过滤等操作,可以先对原始数据进行处理,在进行渲染;
- 使用 v-for 遍历列表时,尽量避免同时渲染大量 DOM 元素,可以使用分页、虚拟滚动等技术进行优化;
优化网络请求
- 合理使用缓存策略,避免频繁请求相同的数据;
- 使用懒加载和预加载技术,延迟加载或提前加载页面所需的资源;
- 使用 CDN 加速静态资源的加载,减少网络请求的延迟
资源压缩和代码拆分
- 使用工具对 Javascript、CSS 和图片进行性能分析,找出性能瓶颈,并进行相应的优化;
- 使用代码拆分技术,将代码拆分成多个模块,按需加载,减少页面首次加载时的资源压力。
优化路由
- 使用懒加载技术,按需加载路由组件,减少首屏加载时间;
- 使用路由懒加载可以减少初始加载时的资源压力,提升用户体验;
性能监控和调优
- 使用性能监控工具对页面进行分析,找出性能瓶颈,并进行相应的优化;
- 定期进行性能测试和调优,保持页面的稳定和可靠;
86.Vue 首屏白屏如何解决?
路由懒加载
:可以避免在首次加载时加载所有路由,只加载当前需要显示的路由对应的组件;预渲染
:可以将页面部分内容预先渲染出来,减少首次加载时的处理时间;静态资源和CND
: 将静态资源如 css、js 文件放在 CND 上,可以提高加载速度,减少白屏现象;优化打包文件
:减少打包文件的体积,使用 Gzip 压缩,减少文件大小,加快加载速度;骨架屏
: 在页面加载过程中,先显示一个类似页面结构的占位符,待页面内容加载完成后,在替换为实际的内容;loading动画
: 在加载过程中,显示一个 loading 动画,给用户反馈再整加载的信息,提升用户体验感;
87.Vue监听不到data中的数组变化的情况?
- 直接改变数组的length;
- 通过索引改变数组或通过键改变对象的值;
- 初始渲染时data中的数组或对象没有初始化某个键值,但是后来动态的添加了,数据能检测到但不能热更新;
88.按钮级别的权限控制
权限指令
:通过自定义指令实现按钮权限控制,通过在按钮上添加自定义指令,并传达相应的权限参数,显示权限控制;权限服务
: 可以使用 Vuex 来创建一个全局状态管理器,定义一个权限管理模块,在 getter 里面定义一个方法用于判断用户是否具有某个权限。通过$store.这个方法来判断用户是否有权限;后端集中
:从后端获取用户的角色和权限信息;
89.怎么解决跨域问题?
JSONP(JSON with Padding)
:利用script标签的跨域特性,通过动态创建script标签,请求一个带有回调函数的接口,服务器返回的数据会作为回调函数的参数传入,从而实现跨域请求。CORS(Cross-Origin Resource Sharing)
:通过服务器设置响应头,允许指定的源(域名、协议、端口)进行跨域请求。反向代理
:在服务器端设置一个代理服务器,将前端的请求发送到目标服务器,并将目标服务器的响应返回给前端,从而实现跨域请求。WebSocket
:使用WebSocket协议进行跨域通信,WebSocket协议默认支持跨域请求。postMessage
:通过window.postMessage方法在不同窗口之间进行跨域通信,可以实现跨域数据传递和消息通知。WebSocket + CORS
:结合WebSocket和CORS,使用WebSocket建立跨域连接,然后通过CORS发送HTTP请求。Nginx反向代理
:通过Nginx服务器设置反向代理,将前端的请求转发到目标服务器,从而实现跨域请求。WebRTC
:使用WebRTC技术进行跨域通信,可以实现点对点的音视频传输和数据传输。
90.服务端渲染和客户端渲染的区别是什么?
- 服务端渲染是在服务端生成dom树,客户端渲染是在客户端生成dom树;
- 服务端渲染会加快页面的响应速度,客户端渲染页面的响应速度慢;
- 服务端渲染因为是多个页面,更有利于爬虫爬取信息,客户端渲染不利于seo优化;
- 服务端渲染逻辑分离的不好,不利于前后端分离,开发效率低,客户端渲染是采用前后端分离的方式开发,效率更高。