86.说说vue生命周期,发送请求在生命周期的哪个阶段,为什么不可以是beforeMount,mounted中
回答:
1、vue的生命周期
1)、生命周期是什么? Vue 实例有一个完整的生命周期,也就是从开始创建、初始化数据、编译模版、挂载 Dom -> 渲染、更新 -> 渲染、卸载等一系列过程,我们称这是 Vue 的生命周期。
2)、各个生命周期阶段及其钩子函数
vue的生命周期核心经历了四个阶段,在四个阶段的前后分别有两个钩子函数。
第一阶段:数据挂载阶段:把配置项data中的属性,赋给了vue对象本身,并做了数据劫持。
该阶段前后的两个钩子函数:beforeCreate和created
第二阶段:模板渲染阶段:把vue对象的数据渲染到模板上。
该阶段前后的两个钩子函数:beforeMount和mounted
第三阶段:组件更新阶段:当数据发送变化时,组件会进行重新渲染,所以,准确的说是,组件重新渲染阶段。
该阶段前后的两个钩子函数:beforeUpdate和updated
第四阶段:组件销毁阶段:组件销毁。
该阶段前后的两个钩子函数:beforeDestroy和destroyed
视情况可以补充:
当使用keep-alive包裹组件时,会有组件激活和停用之说,这两个钩子函数分别是:activited和deactivated
2、发送请求在生命周期的哪个阶段,为什么不可以是beforeMount,mounted中。
(如果组件的初始数据来自后端)发送请求建议在钩子函数created里,这个钩子函数里是最快,也能有助于一致性。
为什么?
1)、为什么不能放在beforeCreate里?
因为:
一般来说,数据从后端返回回来后,都会赋给vue中的属性(data挂载的),在beforeCreate钩子函数里,data的数据还没有挂载到vue对象本身上,所以,不能在这个钩子函数里用。而created钩子函数里,数据已经挂载到vue对象了。
2)、为什么不可以是beforeMount,mounted中
因为:
第一,在这两个钩子函数里,发送请求有点晚,会增加页面loading的时间;
第二,vue的SSR不支持beforeMount 、mounted 钩子函数,所以,放在 created 中有助于一致性
87.兄弟和父子组件,在不能使用vuex的情况下有多少种方案,请列举出来?
回答:
1、父子之间传值:
1)、父到子:props,子到父:$emit
2)、$ref、$parent
3)、事件总线(event-bus)
4)、集中管理($root)
2、兄弟之间传值:
1)、事件总线(event-bus)
2)、集中管理($root)
3)、先 子到父(用事件),再 父到子(用props)
88.v-if和v-for可以同时使用吗
回答:
可以使用,但是,在循环时,通过v-if只能拿到少部分数据时,建议不要使用。
原因: v-for比v-if优先级高,如果遍历的数组元素个数比较多,但是满足v-if条件比较少的情况下。会浪费性能。而且,每次刷新页面时,都会执行这样性能不高的代码。
解决:可以在computed里循环数组,通过filter的方式,过滤出需要的数据。v-for直接循环计算属性的结果(不用v-if)。而computed是有缓存的,所以,在原始数据没有变化时,不会多次过滤数据,这样,就提高了效率。
89.vue-cli怎么配置跨域
回答:
使用反向代理,vue-cli3+的项目里,新建(编辑)vue.config.js文件,(增加)配置代码如下:
module.exports = {devServer:{//设置代理proxy: { //代理是从指定的target后面开始匹配的,不是任意位置;配置pathRewrite可以做替换'/api': { //这个是自定义的,当axios访问以/api开头的请求时,会代理到 target + /api target: 'http://localhost:3001', //最终要访问的服务器地址changeOrigin: true, //创建虚拟服务器pathRewrite: {'^/api': ''//重写接口,去掉/api, 在代理过程中是否替换掉/api/路径 }}} }}
90.v-bind是用来干什么的
回答:
v-bind指令是把标签的属性处理成动态的。分别可以把属性名和属性值处理成vue里的属性,常间的是属性值处理成动态的。
格式如下:
1、属性值动态绑定:v-bind:html属性=”数据” 简写 :html属性=”数据”`
示例:
new Vue({data:{imgstr:'./imgs/1.jpg'}})
2、 属性名动态绑定:v-bind:[属性名]=”数据”
此时,属性值也是动态的
示例:
我是divnew Vue({el: "#app",data:{attr:"class",idname:"div01"}})
91.说说对插槽的理解
回答:
1、插槽的作用:
插槽是用来处理组件的内容的。插槽决定了组件的内容放在组件模板的何处。插槽使用的是vue官方提供的组件来完成的。
2、vue中的插槽分为:
1)、单个插槽
在组件中只有一个插槽时,插槽不用起名字。默认的名字是:default
示例:
//1、定义组件let book = {template: `我是上p
我是下p
`,}//2、父组件的模板 <!--放在组件book标签的内容,最终会渲染在book标签的处。-->
2)、具名插槽
但组件中的插槽多于一个时,就需要给组件起名字,用名字来区分不同的插槽。用官方组件slot的name属性给插槽起名字
格式:
示例:
//1、组件:let book = {template: `我是上p
我是中p
我是下p
`,}//2、父组件模板里:我是div
92.$nextTick理解 和定时器有什么区别 都是延时执行
回答:
1、$nextTick理解:
vue更新DOM时,使用的是异步更新队列。目的是提高性能,避免无效的重复的DOM更新。即:vue中更新数据后,并不会立即更新DOM,而是把数据引起的DOM更新放入到异步更新队列里。等待下次事件循环(tick),并在两个tick之间进行UI渲染。
按照这个思路,程序员就不能在更改数据后,立即获取更新后的DOM,也不知道什么时候DOM能够更新。基于此,vue提供了$nextTick函数。程序员只需要把 ”操作更新后DOM的代码“ 放入到$nextTick的回调函数里。由$nextTick内部,在更新完DOM后,调用回调函数。
示例:
this.msg = "hello"this.$nextTick(()=>{ 操作“this.msg影响的DOM”的代码。 })
2、$nextTick理解和定时器有什么区别
相同:都是延迟加载,都使用事件队列
不同:
1)、定时器是下一个队列的队首。
2)、$nextTick()是放在当前队列的最后一个。$nextTick()的回调函数执行要先于定时器。
93.event-bus是怎么用?
event-bus是事件总线,是借助一个全局的vue对象,来完成事件的绑定和事件的触发。
当:我们需要把A组件的数据传给B组件时,在A、B两个组件里都引入全局的vue对象。然后,在B组件里绑定事件,在A组件里触发事件,就可以把A组件的数据传给B组件了。
示例:
//1、全局的vue对象: bus.jsexport default new Vue();//vue 对象具有 $on和 $emit方法//2、B组件的代码import bus from "./bus.js"export default {………………data(){return {bookmsg:""}},created(){// 绑定事件(用全局变量bus绑定一个事件)bus.$on("eclick",str=>{ this.bookmsg = str;})}}//3、A组件的代码import bus from "./bus.js"export default {………………data(){return { msg:"要传给B组件的数据"}},methods:{chuan(){// 触发事件(用全部变量bus触发事件)bus.$emit("eclick",this.msg);}} }
94.mounted与created的区别
回答:
mounted和created都是vue对象生命周期的钩子函数,执行时机不同。
1、created 是在 data配置项的数据挂载到vue对象本身后,会调用的钩子函数。
此时,
1)、用 this. 的方式可以拿到data里的数据。
2)、但是数据还没有渲染到模板上。所以,访问dom时,内容还是原始的模板内容。
2、mounted是在组件的模板初次渲染完毕后会调用的钩子函数。
此时,
data里的数据已经渲染到模板上了。所以,访问dom时,已经和页面上看到的效果一样了。
95.v-model 原理 是什么
回答:
1、v-model指令的作用
vue中的v-model指令是完成双向绑定的,用在表单元素上。双向绑定就是 M会影响V。V也会影响M。即:能将页面上输入的值同步更新到相关绑定的data属性,也会在更新data绑定属性时候,更新页面上输入控件的值。
2、v-model的原理
v-model指令是一个语法糖,是属性绑定和事件的语法糖。vue会根据不同的表单元素使用不同的属性和事件。
如下:
- text 和 textarea 元素使用 value property 和 input 事件;
- checkbox 和 radio 使用 checked property 和 change 事件;
- select 字段将 value 作为 prop 并将 change 作为事件。
以文本框为例剖析原理,以下是代码:
//M:let vm = new Vue({el: "#app",data: {msg:"hi"},methods: {changeMsg(e){this.msg = e.target.value; }}})
而,使用v-model来完成以上功能的代码如下:
// M:modellet vm = new Vue({el: "#app",data: {msg:"hi"}})
96.vue 的组件中的 data 配置为什么必须是函数(每个组件都是 vue 的实例)
vue 为了保证每个实例上的 data 数据的独立性,规定了必须使用函数,而不是对象。因为使用对象的话,每个实例(组件)上使用的 data 数据是相互影响的,这当然就不是我们想要的了。对象是对于内存地址的引用,直接定义个对象的话组件之间都会使用这个对象,这样会造成组件之间数据相互影响。而函数具有内部作用域,可以解决这个问题。
97.vue 中 computed 和 watch 的区别
- watch 和 computed 都是以 Vue 的依赖追踪机制为基础的,当某一个依赖型数据(依赖型数据:简单理解即放在 data 等对象下的实例数据)发生变化的时候,所有依赖这个数据的相关数据会自动发生变化,即自动调用相关的函数,来实现数据的变动。当依赖的值变化时,在 watch 中,是可以做一些复杂的操作的,而 computed 中的依赖,仅仅是一个值依赖于另一个值,是值上的依赖。
- 应用场景:computed:用于处理复杂的逻辑运算;一个数据受一个或多个数据影响;用来处理 watch 和 methods 无法处理的,或处理起来不方便的情况。例如处理模板中的复杂表达式、购物车里面的商品数量和总金额之间的变化关系等。 watch:用来处理当一个属性发生变化时,需要执行某些具体的业务逻辑操作,或要在数据变化时执行异步或开销较大的操作;一个数据改变影响多个数据。例如用来监控路由、inpurt 输入框值的特殊处理等。
- 区别:
- computed
- 初始化显示或者相关的 data、props 等属性数据发生变化的时候调用;
- 计算属性不在 data 中,它是基于 data 或 props 中的数据通过计算得到的一个新值,这个新值根据已知值的变化而变化;
- 在 computed 属性对象中定义计算属性的方法,和取 data 对象里的数据属性一样,以属性访问的形式调用;
- 如果 computed 属性值是函数,那么默认会走 get 方法,必须要有一个返回值,函数的返回值就是属性的属性值;
- computed 属性值默认会缓存计算结果,在重复的调用中,只要依赖数据不变,直接取缓存中的计算结果,只有依赖型数据发生改变,computed 才会重新计算;
- 在 computed 中的,属性都有一个 get 和一个 set 方法,当数据变化时,调用 set 方法。
- watch
- 主要用来监听某些特定数据的变化,从而进行某些具体的业务逻辑操作,可以看作是 computed 和 methods 的结合体;
- 可以监听的数据来源:data,props,computed 内的数据;
- watch 支持异步;
- 不支持缓存,监听的数据改变,直接会触发相应的操作;
- 监听函数有两个参数,第一个参数是最新的值,第二个参数是输入之前的值,顺序一定是新值,旧值。
98.vue 的生命周期?网络请求为什么要挂载在 mounted 中?
- vue2 的生命周期
- 初始化阶段:
- beforeCreate
- created
- 挂载阶段
- beforeMount
- mounted
- 更新阶段
- beforeUpdate
- updated
- 卸载阶段
- beforeDestroy
- destroyed
- 缓存组件相关
- activated
- deactivated
- 处理错误相关
- errorCaptured
- vue3 的生命周期在 vue2 的基础上新增了:
- renderTracked:跟踪虚拟 DOM 重新渲染时调用。钩子接收 debugger event 作为参数。此事件告诉你哪个操作跟踪了组件以及该操作的目标对象和键。
- renderTriggered:当虚拟 DOM 重新渲染被触发时调用。和 renderTracked 类似,接收 debugger event 作为参数。此事件告诉你是什么操作触发了重新渲染,以及该操作的目标对象和键。一共 13 个
- vue3 的组合 api 的生命周期移除了 beforeCreate 和 created,因为创建时的事件可以在 setup 里面直接调用。其他的 11 个生命周期前面全部加上 on比如:mounted -> onMounted, beforeDestroy -> onDeforeDestroy
- 网络请求为什么要挂载在 mounted 中?在 Created 生命周期里 Data 才生成,而请求返回的数据需要挂载在 data 上,所以 Created 里是可以初始化请求的,但是 Created 的这时候 DOM 还没有初始化;Mounted 生命周期里 DOM 才初始化渲染完成。然而,请求是异步的,所以不会堵塞页面渲染的主线程。所以请求放在 created 和 mounted 里面都是可行的。如果我们的请求不需要获取/借助/依赖/改变 DOM,这时请求可以放在 Created。反之则可以放在 Mounted 里。这样做会更加的安全,也能保证页面不会闪烁。
99.vue 的指令,在项目中封装了那些常用指令?
在 vue 中我们可以使用 Vue.directive()方法注册全局指令。也可以只用 directives 选项注册局部指令。
- 输入框防抖指令 v-debounce
const debounce = {inserted: function (el, binding) {let timer;el.addEventListener("keyup", () => {if (timer) {clearTimeout(timer);}timer = setTimeout(() => {binding.value();}, 1000);});},};export default debounce;
- 复制粘贴指令 v-copy
const copy = {bind(el, { value }) {el.$value = value;el.handler = () => {if (!el.$value) {// 值为空的时候,给出提示。可根据项目UI仔细设计console.log("无复制内容");return;}// 动态创建 textarea 标签const textarea = document.createElement("textarea");// 将该 textarea 设为 readonly 防止 iOS 下自动唤起键盘,同时将 textarea 移出可视区域textarea.readOnly = "readonly";textarea.style.position = "absolute";textarea.style.left = "-9999px";// 将要 copy 的值赋给 textarea 标签的 value 属性textarea.value = el.$value;// 将 textarea 插入到 body 中document.body.appendChild(textarea);// 选中值并复制textarea.select();const result = document.execCommand("Copy");if (result) {console.log("复制成功"); // 可根据项目UI仔细设计}document.body.removeChild(textarea);};// 绑定点击事件,就是所谓的一键 copy 啦el.addEventListener("click", el.handler);},// 当传进来的值更新的时候触发componentUpdated(el, { value }) {el.$value = value;},// 指令与元素解绑的时候,移除事件绑定unbind(el) {el.removeEventListener("click", el.handler);},};export default copy;
- 长按指令 v-longpress
const longpress = {bind: function (el, binding, vNode) {if (typeof binding.value !== "function") {throw "callback must be a function";}// 定义变量let pressTimer = null;// 创建计时器( 2秒后执行函数 )let start = (e) => {if (e.type === "click" && e.button !== 0) {return;}if (pressTimer === null) {pressTimer = setTimeout(() => {handler();}, 2000);}};// 取消计时器let cancel = (e) => {if (pressTimer !== null) {clearTimeout(pressTimer);pressTimer = null;}};// 运行函数const handler = (e) => {binding.value(e);};// 添加事件监听器el.addEventListener("mousedown", start);el.addEventListener("touchstart", start);// 取消计时器el.addEventListener("click", cancel);el.addEventListener("mouseout", cancel);el.addEventListener("touchend", cancel);el.addEventListener("touchcancel", cancel);},// 当传进来的值更新的时候触发componentUpdated(el, { value }) {el.$value = value;},// 指令与元素解绑的时候,移除事件绑定unbind(el) {el.removeEventListener("click", el.handler);},};export default longpress;
- 禁止表情及特殊字符 v-emoji
- let findEle = (parent, type) => {
return parent.tagName.toLowerCase() === type
” /> e.initEvent(type, true, true);
el.dispatchEvent(e);
};const emoji = {
bind: function (el, binding, vnode) {
// 正则规则可根据需求自定义
var regRule = /[^u4E00-u9FA5|d|a-zA-Z|rns,.?!,。?!…—&$=()-+/*{}[]]|s/g;
let $inp = findEle(el, “input”);
el.$inp = $inp;
$inp.handle = function () {
let val = $inp.value;
$inp.value = val.replace(regRule, “”);trigger($inp, “input”);
};
$inp.addEventListener(“keyup”, $inp.handle);
},
unbind: function (el) {
el.$inp.removeEventListener(“keyup”, el.$inp.handle);
},
};export default emoji;
- 图片懒加载 v-LazyLoad
const LazyLoad = {// install方法install(Vue, options) {const defaultSrc = options.default;Vue.directive("lazy", {bind(el, binding) {LazyLoad.init(el, binding.value, defaultSrc);},inserted(el) {if (IntersectionObserver) {LazyLoad.observe(el);} else {LazyLoad.listenerScroll(el);}},});},// 初始化init(el, val, def) {el.setAttribute("data-src", val);el.setAttribute("src", def);},// 利用IntersectionObserver监听elobserve(el) {var io = new IntersectionObserver((entries) => {const realSrc = el.dataset.src;if (entries[0].isIntersecting) {if (realSrc) {el.src = realSrc;el.removeAttribute("data-src");}}});io.observe(el);},// 监听scroll事件listenerScroll(el) {const handler = LazyLoad.throttle(LazyLoad.load, 300);LazyLoad.load(el);window.addEventListener("scroll", () => {handler(el);});},// 加载真实图片load(el) {const windowHeight = document.documentElement.clientHeight;const elTop = el.getBoundingClientRect().top;const elBtm = el.getBoundingClientRect().bottom;const realSrc = el.dataset.src;if (elTop - windowHeight 0) {if (realSrc) {el.src = realSrc;el.removeAttribute("data-src");}}},// 节流throttle(fn, delay) {let timer;let prevTime;return function (...args) {const currTime = Date.now();const context = this;if (!prevTime) prevTime = currTime;clearTimeout(timer);if (currTime - prevTime > delay) {prevTime = currTime;fn.apply(context, args);clearTimeout(timer);return;}timer = setTimeout(function () {prevTime = Date.now();timer = null;fn.apply(context, args);}, delay);};},};export default LazyLoad;
- 权限校验指令 v-premission
- function checkArray(key) {
let arr = [“1”, “2”, “3”, “4”];
let index = arr.indexOf(key);
if (index > -1) {
return true; // 有权限
} else {
return false; // 无权限
}
}const permission = {
inserted: function (el, binding) {
let permission = binding.value; // 获取到 v-permission的值
if (permission) {
let hasPermission = checkArray(permission);
if (!hasPermission) {
// 没有权限 移除Dom元素
el.parentNode && el.parentNode.removeChild(el);
}
}
},
};export default permission;
- 实现页面水印 v-waterMarker
function addWaterMarker(str, parentNode, font, textColor) {// 水印文字,父元素,字体,文字颜色var can = document.createElement("canvas");parentNode.appendChild(can);can.width = 200;can.height = 150;can.style.display = "none";var cans = can.getContext("2d");cans.rotate((-20 * Math.PI) / 180);cans.font = font || "16px Microsoft JhengHei";cans.fillStyle = textColor || "rgba(180, 180, 180, 0.3)";cans.textAlign = "left";cans.textBaseline = "Middle";cans.fillText(str, can.width / 10, can.height / 2);parentNode.style.backgroundImage = "url(" + can.toDataURL("image/png") + ")";}const waterMarker = {bind: function (el, binding) {addWaterMarker(binding.value.text,el,binding.value.font,binding.value.textColor);},};export default waterMarker;
- 拖拽指令 v-draggable
const draggable = {inserted: function (el) {el.style.cursor = "move";el.onmousedown = function (e) {let disx = e.pageX - el.offsetLeft;let disy = e.pageY - el.offsetTop;document.onmousemove = function (e) {let x = e.pageX - disx;let y = e.pageY - disy;let maxX =document.body.clientWidth -parseInt(window.getComputedStyle(el).width);let maxY =document.body.clientHeight -parseInt(window.getComputedStyle(el).height);if (x maxX) {x = maxX;}if (y maxY) {y = maxY;}el.style.left = x + "px";el.style.top = y + "px";};document.onmouseup = function () {document.onmousemove = document.onmouseup = null;};};},};export default draggable;
100.vue 的移动端适配怎么做的,rem 怎么用的
vue 的移动端适配我们可以参考 vant-ui 组件库给我们提供的方案。使用 amfe-flexible(用于自动定义跟字体大小)插件和 postcss-pxtorem(用于将 px 自动转成 rem)插件在 main.ts 里面 import “amfe-flexible”在根目录新建 .postcssrc.js 文件
module.exports = {plugins: {"postcss-pxtorem": {rootValue: 37.5,propList: ["*"],},},};
rem 是相对于跟字体的倍数,如果我们整个项目都是用 rem 作为单位,那么当我们做移动端的响应式的时候只需要去改变跟字体的大小就能做到适配。
101.后台管理系统用户验证权限
- 登录用户填写完账号和密码后向服务端验证是否正确,登录成功后,服务端会返回一个 token(该 token 的是一个能唯一标示用户身份的一个 key),之后我们将 token 存储在本地 localstorage 之中,这样下次打开页面或者刷新页面的时候能记住用户的登录状态,不用再去登录页面重新登录了。为了保证安全性,后台所有 token 有效期(Expires/Max-Age)都是 Session,就是当浏览器关闭了就丢失了。重新打开浏览器都需要重新登录验证,后端也会在每周固定一个时间点重新刷新 token,让后台用户全部重新登录一次,确保后台用户不会因为电脑遗失或者其它原因被人随意使用账号。
- 拦截路由进行判断页面会先从 localstorage 中查看是否存有 token,没有,就走一遍上一部分的流程重新登录,如果有 token,就会把这个 token 返给后端去拉取 user_info,保证用户信息是最新的。当然如果是做了单点登录得的的话,用户信息存储在本地也是可以的。当你一台电脑登录时,另一台会被提下线,所以总会重新登录获取最新的内容。
- 权限控制前端会有一份路由表,它表示了每一个路由可访问的权限。当用户登录之后,通过 token 获取用户的 role ,动态根据用户的 role 算出其对应有权限的路由,再通过 router.addRoutes 动态挂载路由。但这些控制都只是页面级的,说白了前端再怎么做权限控制都不是绝对安全的,后端的权限验证是逃不掉的。前端控制页面级的权限,不同权限的用户显示不同的侧边栏和限制其所能进入的页面(也做了少许按钮级别的权限控制),后端则会验证每一个涉及请求的操作,验证其是否有该操作的权限,每一个后台的请求不管是 get 还是 post 都会让前端在请求 header 里面携带用户的 token,后端会根据该 token 来验证用户是否有权限执行该操作。若没有权限则抛出一个对应的状态码,前端检测到该状态码,做出相对应的操作。
- 利用 vuex 管理路由表,根据 vuex 中可访问的路由渲染侧边栏组件。
- 创建 vue 实例的时候将 vue-router 挂载,但这个时候 vue-router 挂载一些登录或者不用权限的公用的页面。
- 当用户登录后,获取用 role,将 role 和路由表每个页面的需要的权限作比较,生成最终用户可访问的路由表。
- 调用 router.addRoutes(store.getters.addRouters)添加用户可访问的路由。
- 使用 vuex 管理路由表,根据 vuex 中可访问的路由渲染侧边栏组件。
102.vuex 做数据请求刷新页面,数据可能会发生丢失这个问题怎么解决
因为 store 里的数据是保存在运行内存中的,当页面刷新时,页面会重新加载 vue 实例,store 里面的数据就会被重新赋值初始化。所以我们可以在修改 store 的数据同时将数据再存一份到本地存储(localStorage 或者 sessionStorage),本地存储的内容是存在浏览器里面的,不会因为刷新而丢失。我们也可以用过比如 vuex-persistedstate 这样的第三方包来帮助我们做 vuex 的持久化数据。
103.vue2 和 vue3 两者的具体的区别有哪些,请一一例举出来
- 源码使用 ts 重写现如今 typescript 异常火爆,它的崛起是有原因的,因为对于规模很大的项目,没有类型声明,后期维护和代码的阅读都是头疼的事情,所以广大码农迫切的需要 vue 能完美支持 ts。vue2 用的是 Facebook 的 Flow 做类型检查,但是因为某些情况下推断有问题,所以 vue3 使用 typescript 进行了源码的重写。一个是为了更好的类型检查,另一个是拥抱 ts。
- 使用 proxy 代替 defineProperty我们知道 vue2.x 双向绑定的核心是 Object.defineProperty(),所以导致 vue 对数组对象的深层监听无法实现。所以 vue3 使用 proxy 对双向绑定进行了重写。Proxy 可以对整体进行监听,不需要关心里面有什么属性,而且 Proxy 的配置项有 13 种,可以做更细致的事情,这是之前的 defineProperty 无法达到的。
- Diff 算法的提升vue3 在 vue2 的 diff 算法的基础上增加了静态标记,元素提升和事件缓存等优化。使得速度更快。
- 打包体积变化vue2 官方说的运行时打包师 23k,但这只是没安装依赖的时候,随着依赖包和框架特性的增多,有时候不必要的,未使用的代码文件都被打包了进去,所以后期项目大了,打包文件会特别多还很大。在 Vue 3 中,我们通过将大多数全局 API 和内部帮助程序移动到 Javascript 的 module.exports 属性上实现这一点。这允许现代模式下的 module bundler 能够静态地分析模块依赖关系,并删除与未使用的 module.exports 属性相关的代码。模板编译器还生成了对树抖动友好的代码,只有在模板中实际使用某个特性时,该代码才导入该特性的帮助程序。尽管增加了许多新特性,但 Vue 3 被压缩后的基线大小约为 10 KB,不到 Vue 2 的一半。
- 其他 Api 和功能的改动
- Global API
- 模板指令
- 组件
- 渲染函数
- vue-cli 从 v4.5.0 开始提供 Vue 3 预设
- Vue Router 4.0 提供了 Vue 3 支持,并有许多突破性的变化
- Vuex 4.0 提供了 Vue 3 支持,其 API 与 2.x 基本相同
- 组件基本结构
在创建组件的时候我们不必在写唯一的根元素。移除了 vue2 中的 filters。
- 生命周期的区别
新增了 renderTracked 和 renderTriggered 两个生命周期。
- 增加了组合 api
我们可以使用 setup 函数来使用类似 react 的自定义 hooks 的功能,主要解决逻辑关注点分离的问题。
104.vue 操作虚拟 DOM 有什么优异的地方?他不是还多做了一层虚拟 DOM,为什么比原生操作 DOM 还快
我们有必要先了解下模板转换成视图的过程整个过程:
- Vue.js 通过编译将 template 模板转换成渲染函数(render ) ,执行渲染函数就可以得到一个虚拟节点树。
- 在对 Model 进行操作的时候,会触发对应 Dep 中的 Watcher 对象。Watcher 对象会调用对应的 update 来修改视图。这个过程主要是将新旧虚拟节点进行差异对比,然后根据对比结果进行 DOM 操作来更新视图。
简单点讲,在 Vue 的底层实现上,Vue 将模板编译成虚拟 DOM 渲染函数。结合 Vue 自带的响应系统,在状态改变时,Vue 能够智能地计算出重新渲染组件的最小代价并应到 DOM 操作上。
那么 vue 操作虚拟 DOM 有什么优异的地方呢?
- 具备跨平台的优势由于 Virtual DOM 是以 JavaScript 对象为基础而不依赖真实平台环境,所以使它具有了跨平台的能力,比如说浏览器平台、Weex、Node 等。
- 操作 DOM 慢,js 运行效率高。我们可以将 DOM 对比操作放在 JS 层,提高效率。因为 DOM 操作的执行速度远不如 Javascript 的运算速度快,因此,把大量的 DOM 操作搬运到 Javascript 中,运用 patching 算法来计算出真正需要更新的节点,最大限度地减少 DOM 操作,从而显著提高性能。Virtual DOM 本质上就是在 JS 和 DOM 之间做了一个缓存。可以类比 CPU 和硬盘,既然硬盘这么慢,我们就在它们之间加个缓存:既然 DOM 这么慢,我们就在它们 JS 和 DOM 之间加个缓存。CPU(JS)只操作内存(Virtual DOM),最后的时候再把变更写入硬盘(DOM)
- 提升渲染性能Virtual DOM 的优势不在于单次的操作,而是在大量、频繁的数据更新下,能够对视图进行合理、高效的更新。为了实现高效的 DOM 操作,一套高效的虚拟 DOM diff 算法显得很有必要。我们通过 patch 的核心—-diff 算法,找出本次 DOM 需要更新的节点来更新,其他的不更新。比如修改某个 model 100 次,从 1 加到 100,那么有了 Virtual DOM 的缓存之后,只会把最后一次修改 patch 到 view 上。那 diff 算法的实现过程是怎样的?
那么为什么比原生操作 DOM 还快呢?首先我们每次操作 dom 的时候,都会去执行浏览器的那 5 个步骤,尤其是当大量循环的时候,每次循环完都不知道后面还要不要修改,所以每次都要去重复这个过程,引发不必要的渲染。但是在实际开发过程中,我们会发现虚拟 dom 并没有比真实 dom 更快。这个问题尤雨溪在知乎上面有过回答:这是一个性能 vs. 可维护性的取舍。框架的意义在于为你掩盖底层的 DOM 操作,让你用更声明式的方式来描述你的目的,从而让你的代码更容易维护。没有任何框架可以比纯手动的优化 DOM 操作更快,因为框架的 DOM 操作层需要应对任何上层 API 可能产生的操作,它的实现必须是普适的。针对任何一个 benchmark,我都可以写出比任何框架更快的手动优化,但是那有什么意义呢?在构建一个实际应用的时候,你难道为每一个地方都去做手动优化吗?出于可维护性的考虑,这显然不可能。框架给你的保证是,你在不需要手动优化的情况下,我依然可以给你提供过得去的性能。
105.token 过期你是如何来进行处理,有没有弄过 token 续期
在开发中,我们经常会遇到使用 token,token 的作用是要验证用户是否处于登录状态,所以要请求一些只有登录状态才能查看的资源的时候,我们需要携带 token。
一般的后端接口设置的 token 是有时效的,超时后就会失效,失效之后的处理策略一般会做两种处理:
- 一种是直接跳转到登录页面,重新登录。
- 另外一种如果返回 token 失效的信息,自动去刷新 token,然后继续完成未完成的请求操作。
106.vue底层实现原理
- 使用 Object.defineProperty 劫持 data上的数据。
- Vue2.0通过设定对象属性的 setter/getter 方法来监听数据的变化,通过getter进行依赖收集,而每个setter方法就是一个观察者,在数据变更的时候通知订阅者更新视图。
107.Vue的生命周期,created与mounted的区别
1、created
表示组件实例已经完全创建,data数据已经被 Object.defineProperty 劫持完成,属性也绑定成功,但真实dom还没有生成,$el还不可用。
2、mounted
el选项所对应的视图节点已经被新创建的 vm.$el 替换,并挂载到实例上去了。此时响应式数据都已经完成了渲染。
108.用vue写了商城,从列表页点进详情页,从详情页退出来的时候,怎么保持进入详情页之前的页面卷动值。
使用 对列表页面进行包裹,被包裹的列表页面就有了activated、deactivated这两个生命周期。在离开列表页面时,在deactivated中记录页面的滚动条位置。再次进入列表页面时,在activated中使用 this.$el.scrollTop 把页面滚动到离开时所记录的位置。
109.说说你对vue的理解
- vue是数据驱动的MVVM框架,相比传统的DOM库,vue有一层虚拟DOM。每当数据发生更新时,会触发虚拟DOM进行diff运算,找出最小的变化节点,大大地节省了DOM操作性能。
- vue是组件化的,在单页面应用程序中,每一个组件相当于是一块积木,搭建起庞大的应用系统。组件,可以理解成更大粒度的“HTML元素”,有利于快速开发、组件的高效复用。
- vue有一整套指令,大大地降低了开发者的开发难度,提升了开发效率。
- 虽然vue有诸多优点,但仍然有一些缺陷,比如复杂的业务页面通常过长,data、methods、computed、watch对应的数据和逻辑交织在一起,难以维护。
110.说说对虚拟DOM的理解
- 在vue中,虚拟DOM本质上就是一个固定格式的JSON数据,它用于描述真实的DOM结构,并使用各种不同flag标记出动态的DOM节点。
- 虚拟DOM数据保存在应用程序对应的内存结构中,拥有更快的数据交换速度。
- 每当有数据发生变化时,就会生成新的虚拟DOM,进一步发生diff运算,找出最小脏节点,减少不必要的DOM开销,这也是vue拥有更好的性能的根本原因。
111.说说provide的用法
- 在父级组件中,使用provide选项向vue组件树中“提供数据”,其语法是:provide:{a: 1}
- 在后代子级组件中,使用 inject选项从vue组件中“取出数据”,其语法是:inject: [‘a’]
112.说一下element ui遇到过的坑
- 表单设置触发事件为blur,但是ctrl+A全选以后再删除时又触发了change事件,并提示一个原始报错
- 解决方案:trigger设置成trigger: [‘blur’, ‘change’]
- 使用el-dialog 遮罩层把显示内容遮住了
- 原因:Dialog 的外层布局的 position 值为 fixed, absolute, relative 三者之一时,就会出现被蒙板遮住的情况。
- 解决方法:v-bind:modal-append-to-body=”false”
- 使用el-select 不能继承父元素的宽度
- 原因:el-select 本身是 inline-block
- 解决办法:手动设置el-select的宽度
113.怎么修改element ui动态组件的样式
要修改elementUI组件的样式,可以采用以下两种方式
1.全局样式
通过选择权重覆盖elementUI组件的样式,如修改复选框为圆角:
.edit-item .el-checkbox__inner { border-radius: 50%;}
但这种方式为全局样式,会影响页面中所有复选框,如果不希望影响其它页面的样式,可以采用第二中方式
2.局部样式
.edit-item .el-checkbox__inner { border-radius: 50%;}
但如果仅仅是设置了scoped属性,样式无法生效,原因是以上样式会被编译成属性选择器,而elementUI组件内部的结构却无法添加该html属性,以上样式被编译成如下代码:
.edit-item[data-v-6558bc58] .el-checkbox__inner[data-v-6558bc58] { border-radius: 50%;}
解决方案也很简单,只需在选择器中要添加>>>即可
.edit-item >>> .el-checkbox__inner { border-radius: 50%;}
如果是sass或less编写的样式,还可以使用/deep/
.edit-item /deep/ .el-checkbox__inner { border-radius: 50%;}
以上写法样式都会编译成以下样式:
.edit-item[data-v-6558bc58] .el-checkbox__inner{}
- 所以elementUI中的样式就能成功覆盖
114.vue和react中的key值主要用来干什么
key是虚拟DOM对象的标识,在更新显示时key起着极其重要的作用,vue和react都是采用diff算法来对比新旧虚拟节点,而key的作用是为了在执行 diff算法 的时候,更快更准确地找到对应的虚拟节点,从而提高diff速度。
115.route和router区别
route 和 router 是vue-router中经常会操作的两个对象, route表示当前的路由信息对象,包含了当前 URL 解析得到的信息,包含当前的路径、参数、query对象等,一般用于获取跳转时传入的参数。router对象是全局路由的实例,是router构造方法的实例,一般用户路由跳转,如router.push()、router.replace() 等方法
116.vue和react相对于传统的有什么好处,性能优点
- 组件化开发,开发效率更高
- React与Vue都鼓励使用组件化开发。这本质上是建议你将你的应用分拆成一个个功能明确的模块,每个模块之间可以通过特定的方式进行关联。这样可以更好的管理功能模块与复用,在团队项目中也能更好的分工协作
- VirtualDOM,性能更高
- 对真实DOM的操作很慢,所以Vue和React都实现了虚拟DOM,用户对数据的修改首先反映到虚拟DOM上,而不是直接操作真实DOM,然后在虚拟DOM环节进行优化,比如只修改差异项,对虚拟 DOM 进行频繁修改时进行合并,然后一次性修改真实 DOM 中需要改的部分,最后在真实 DOM 中进行排版与重绘,减少过多DOM节点排版与重绘损耗等
- 数据和结构的分离
- 双向数据绑定
- Vue可以通过v-model指令实现,react可通过单向绑定+事件来实现
- 强大的周边生态
- Vue和React都有着强大的生态,比如路由、状态管理、脚手架等,用户可以根据需求自行选择,不需要重复造轮子
117.虚拟DOM实现原理
我们先来看看浏览器渲染一个页面的过和,大概需要以下5个步骤
- 解析HTML元素,构建DOM树
- 解析CSS,生成页面CSS规则树(Style Rules)
- 将DOM树和CSS规则树进行关联,生成render树
- 布局(layout/reflow):浏览器设定Render树中的每个节点在屏幕上的位置与尺寸
- 绘制Render树:绘制页面像素信息到屏幕上
众所周知,一个页面在浏览器中最大的开销就是DOM节点操作,页面的性能问题大多数是DOM操作引起的,当我们用原生js 或jquery这样的库去操作DOM时,浏览器会从构建DOM树开始执行完以上整个流程,所以频繁操作DOM会引起不需要的计算、重排与重绘,从而导致页面卡顿,影响用户体验
所以减少DOM的操作能达到性能优化的目的,事实上,虚拟DOM就是这么做的,虚拟DOM(VirtualDOM) 的实现原理主要包括以下 3 部分:
- 用 JavaScript 对象模拟真实 DOM 树,对真实 DOM 进行抽象
- diff 算法 — 比较新旧两个虚拟DOM,得到差异对象
- pach 算法 — 将差异对象应用到真实 DOM 树
Virtual DOM 本质上就是一个javascript对象,数据的修改会生成一个新的虚拟DOM(一个新的javascript对象),然后与旧的虚拟DOM进行对比,得到两个对象的差异项,最后只更新差异对象中的内容到真实DOM,这样能做到最少限度地修改真实DOM,从而实现性能优化
118.如何实现角色权限分配
在开发中后台应用过程中或多或少都会涉及到一个问题:权限,简单地说就是让不同的用户在系统中拥有不同的操作能力。
但在实际应用中我们一般不直接将权限赋予在用户身上,因为这样操作对有大量用户的系统来说过于繁琐,所以我们一般基于RBAC(Role-Based Access Control)权限模型,引入角色的概念,通过角色的媒介过渡,先将权限赋予在角色上,再关联相应的用户,对应的用户就继承了角色的权限
用户与角色,角色与权限都是多对多的关系
引入角色媒介的优点:
- 实现了用户与权限的解耦
- 提高了权限配置的效率
- 降低了后期维护的成本
119.双向数据绑定和单向数据流的优缺点
所谓数据绑定,就是指View层和Model层之间的映射关系。
- 单向数据绑定:Model的更新会触发View的更新,而View的更新不会触发Model的更新,它们的作用是单向的。
- 优点:所有状态变化都可以被记录、跟踪,状态变化通过手动调用触发,源头易追溯。
- 缺点:会有很多类似的样板代码,代码量会相应的上升。
- 双向数据绑定:Model的更新会触发View的更新,View的更新也会触发Model的更新,它们的作用是相互的。
- 优点:在操作表单时使用v-model方便简单,可以省略繁琐或重复的onChange事件去处理每个表单数据的变化(减少代码量)。
- 缺点:属于暗箱操作,无法很好的追踪双向绑定的数据的变化。
120.Vue是如何实现双向绑定的” />${this.$slots.default[0].text} ` return} }
但是极少数的 VUE项目用JSX语法 JSX语法 根植在react上面 在vue上还是 template舒服!