【直接收藏】前端 VUE 高阶面试题(三)

            '^/api': ''    //重写接口,去掉/api, 在代理过程中是否替换掉/api/路径 
      }
    }
  }

}
}


## 90.v-bind是用来干什么的


**回答:**


  v-bind指令是把**标签的属性处理成动态**的。分别可以把**属性名**和**属性值**处理成vue里的属性,常间的是属性值处理成动态的。


**格式如下:**


1、**属性值**动态绑定:v-bind:html属性="数据" 简写 :html属性="数据"`


示例:



new Vue({
data:{
imgstr:‘./imgs/1.jpg’
}
})


2、 **属性名**动态绑定:v-bind:[属性名]="数据"


此时,属性值也是动态的


示例:



new Vue({
el: “#app”,
data:{
attr:“class”,
idname:“div01”
}
})


## 91.说说对插槽的理解


**回答:**


**1、插槽的作用:**


  插槽是用来处理组件的内容的。插槽决定了组件的内容放在组件模板的何处。插槽使用的是vue官方提供的组件<slot>来完成的。


**2、vue中的插槽分为:**


**1)、单个插槽**


  在组件中只有一个插槽时,插槽不用起名字。默认的名字是:default


示例:



//1、定义组件
let book = {
template: <div> <p>我是上p</p> <!--这是插槽,该组件的内容将会渲染在此处 --> <slot></slot> <p>我是下p</p> </div> ,
}

//2、父组件的模板

```

2)、具名插槽

但组件中的插槽多于一个时,就需要给组件起名字,用名字来区分不同的插槽。用官方组件slot的name属性给插槽起名字

格式:

<slot name="插槽的名字"></slot>

示例:

//1、组件:
    let book = {
        template: `
            <div>     
               <p>我是上p</p>
        <!--具名插槽,该插槽起名为s1-->
               <slot name="s1"></slot> 
               <p>我是中p</p> 
        <!--具名插槽,该插槽起名为s2-->
               <slot name="s2"></slot>
               <p>我是下p</p>
            </div>
        `,
    }


//2、父组件模板里:


<div id="box">
    <book>
      <!--以下内容插在name为s1的插槽处-->
        <template v-slot:s1>
            <img src="imgs/2.jpg" />
        </template>
      <!--以下内容插在name为s2的插槽处-->
        <template v-slot:s2>
            <div>我是div</div>
        </template>
  </book>
</div>

92.$nextTick理解 和定时器有什么区别 都是延时执行

回答:

1、$nextTick理解:

vue更新DOM时,使用的是异步更新队列。目的是提高性能,避免无效的重复的DOM更新。即:vue中更新数据后,并不会立即更新DOM,而是把数据引起的DOM更新放入到异步更新队列里。等待下次事件循环(tick),并在两个tick之间进行UI渲染。

按照这个思路,程序员就不能在更改数据后,立即获取更新后的DOM,也不知道什么时候DOM能够更新。基于此,vue提供了 n e x t T i c k 函数。程序员只需要把 ∗ ∗ ” ∗ ∗ 操作更新后 D O M 的代码 ∗ ∗ “ ∗ ∗ 放入到 nextTick函数。程序员只需要把 **”**操作更新后DOM的代码**“** 放入到 nextTick函数。程序员只需要把操作更新后DOM的代码放入到nextTick的回调函数里。由$nextTick内部,在更新完DOM后,调用回调函数。

示例:



this.msg = "hello"


this.$nextTick(()=>{
    
     操作“this.msg影响的DOM”的代码。
     
})

2、$nextTick理解和定时器有什么区别

**相同:**都是延迟加载,都使用事件队列

不同:

1)、定时器是下一个队列的队首。

2)、 n e x t T i c k ( ) 是放在当前队列的最后一个。 nextTick()是放在当前队列的最后一个。 nextTick()是放在当前队列的最后一个。nextTick()的回调函数执行要先于定时器。

93.event-bus是怎么用?

event-bus是事件总线,是借助一个全局的vue对象,来完成事件的绑定和事件的触发。

**当:**我们需要把A组件的数据传给B组件时,在A、B两个组件里都引入全局的vue对象。然后,在B组件里绑定事件,在A组件里触发事件,就可以把A组件的数据传给B组件了。

示例:

//1、全局的vue对象: bus.js
export 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 作为事件。

以文本框为例剖析原理,以下是代码:

 <!-- V -->
  <div id="app">
      <!--文本框使用value属性和input事件-->
      <input type="text" v-bind:value="msg" @input="changeMsg"  >
  </div>


 //M:


  let vm = new Vue({
        el: "#app",
        data: {
            msg:"hi"
        },
        methods: {
            changeMsg(e){
                this.msg = e.target.value; 
            }      
        }
  })

而,使用v-model来完成以上功能的代码如下:

  <!-- V-->
  <div id="app">
    <!-- 此处不需要使用value属性和input事件,直接使用v-mdoel指令即可 -->
      <input type="text" v-model="msg"  >
  </div>


  // M:model


  let vm = new Vue({
      el: "#app",
      data: {
          msg:"hi"
      }      
  })

96.vue 的组件中的 data 配置为什么必须是函数(每个组件都是 vue 的实例)

vue 为了保证每个实例上的 data 数据的独立性,规定了必须使用函数,而不是对象。因为使用对象的话,每个实例(组件)上使用的 data 数据是相互影响的,这当然就不是我们想要的了。对象是对于内存地址的引用,直接定义个对象的话组件之间都会使用这个对象,这样会造成组件之间数据相互影响。而函数具有内部作用域,可以解决这个问题。

97.vue 中 computed 和 watch 的区别

  1. watch 和 computed 都是以 Vue 的依赖追踪机制为基础的,当某一个依赖型数据(依赖型数据:简单理解即放在 data 等对象下的实例数据)发生变化的时候,所有依赖这个数据的相关数据会自动发生变化,即自动调用相关的函数,来实现数据的变动。当依赖的值变化时,在 watch 中,是可以做一些复杂的操作的,而 computed 中的依赖,仅仅是一个值依赖于另一个值,是值上的依赖。
  2. 应用场景:computed:用于处理复杂的逻辑运算;一个数据受一个或多个数据影响;用来处理 watch 和 methods 无法处理的,或处理起来不方便的情况。例如处理模板中的复杂表达式、购物车里面的商品数量和总金额之间的变化关系等。    watch:用来处理当一个属性发生变化时,需要执行某些具体的业务逻辑操作,或要在数据变化时执行异步或开销较大的操作;一个数据改变影响多个数据。例如用来监控路由、inpurt 输入框值的特殊处理等。
  3. 区别:
  • 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 中?

  1. vue2 的生命周期
  • 初始化阶段:
    • beforeCreate
    • created
  • 挂载阶段
    • beforeMount
    • mounted
  • 更新阶段
    • beforeUpdate
    • updated
  • 卸载阶段
    • beforeDestroy
    • destroyed
  • 缓存组件相关
    • activated
    • deactivated
  • 处理错误相关
    • errorCaptured
  1. vue3 的生命周期在 vue2 的基础上新增了:
  • renderTracked:跟踪虚拟 DOM 重新渲染时调用。钩子接收 debugger event 作为参数。此事件告诉你哪个操作跟踪了组件以及该操作的目标对象和键。
  • renderTriggered:当虚拟 DOM 重新渲染被触发时调用。和 renderTracked 类似,接收 debugger event 作为参数。此事件告诉你是什么操作触发了重新渲染,以及该操作的目标对象和键。一共 13 个
  1. vue3 的组合 api 的生命周期移除了 beforeCreate 和 created,因为创建时的事件可以在 setup 里面直接调用。其他的 11 个生命周期前面全部加上 on比如:mounted -> onMounted, beforeDestroy -> onDeforeDestroy
  2. 网络请求为什么要挂载在 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
    ? parent
    : parent.querySelector(type);
    };

const trigger = (el, type) => {
const e = document.createEvent(“HTMLEvents”);
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 i n p = f i n d E l e ( e l , " i n p u t " ) ; e l . inp = findEle(el, "input"); el. 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”, KaTeX parse error: Expected 'EOF', got '}' at position 17: …np.handle); }̲, unbind: fu…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监听el
  observe(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 && elBtm > 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 < 0) {
          x = 0;
        } else if (x > maxX) {
          x = maxX;
        }


        if (y < 0) {
          y = 0;
        } else 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.后台管理系统用户验证权限

  1. 登录用户填写完账号和密码后向服务端验证是否正确,登录成功后,服务端会返回一个 token(该 token 的是一个能唯一标示用户身份的一个 key),之后我们将 token 存储在本地 localstorage 之中,这样下次打开页面或者刷新页面的时候能记住用户的登录状态,不用再去登录页面重新登录了。为了保证安全性,后台所有 token 有效期(Expires/Max-Age)都是 Session,就是当浏览器关闭了就丢失了。重新打开浏览器都需要重新登录验证,后端也会在每周固定一个时间点重新刷新 token,让后台用户全部重新登录一次,确保后台用户不会因为电脑遗失或者其它原因被人随意使用账号。
  2. 拦截路由进行判断页面会先从 localstorage 中查看是否存有 token,没有,就走一遍上一部分的流程重新登录,如果有 token,就会把这个 token 返给后端去拉取 user_info,保证用户信息是最新的。当然如果是做了单点登录得的的话,用户信息存储在本地也是可以的。当你一台电脑登录时,另一台会被提下线,所以总会重新登录获取最新的内容。
  3. 权限控制前端会有一份路由表,它表示了每一个路由可访问的权限。当用户登录之后,通过 token 获取用户的 role ,动态根据用户的 role 算出其对应有权限的路由,再通过 router.addRoutes 动态挂载路由。但这些控制都只是页面级的,说白了前端再怎么做权限控制都不是绝对安全的,后端的权限验证是逃不掉的。前端控制页面级的权限,不同权限的用户显示不同的侧边栏和限制其所能进入的页面(也做了少许按钮级别的权限控制),后端则会验证每一个涉及请求的操作,验证其是否有该操作的权限,每一个后台的请求不管是 get 还是 post 都会让前端在请求 header 里面携带用户的 token,后端会根据该 token 来验证用户是否有权限执行该操作。若没有权限则抛出一个对应的状态码,前端检测到该状态码,做出相对应的操作。
  4. 利用 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 两者的具体的区别有哪些,请一一例举出来

  1. 源码使用 ts 重写现如今 typescript 异常火爆,它的崛起是有原因的,因为对于规模很大的项目,没有类型声明,后期维护和代码的阅读都是头疼的事情,所以广大码农迫切的需要 vue 能完美支持 ts。vue2 用的是 Facebook 的 Flow 做类型检查,但是因为某些情况下推断有问题,所以 vue3 使用 typescript 进行了源码的重写。一个是为了更好的类型检查,另一个是拥抱 ts。
  2. 使用 proxy 代替 defineProperty我们知道 vue2.x 双向绑定的核心是 Object.defineProperty(),所以导致 vue 对数组对象的深层监听无法实现。所以 vue3 使用 proxy 对双向绑定进行了重写。Proxy 可以对整体进行监听,不需要关心里面有什么属性,而且 Proxy 的配置项有 13 种,可以做更细致的事情,这是之前的 defineProperty 无法达到的。
  3. Diff 算法的提升vue3 在 vue2 的 diff 算法的基础上增加了静态标记,元素提升和事件缓存等优化。使得速度更快。
  4. 打包体积变化vue2 官方说的运行时打包师 23k,但这只是没安装依赖的时候,随着依赖包和框架特性的增多,有时候不必要的,未使用的代码文件都被打包了进去,所以后期项目大了,打包文件会特别多还很大。在 Vue 3 中,我们通过将大多数全局 API 和内部帮助程序移动到 Javascript 的 module.exports 属性上实现这一点。这允许现代模式下的 module bundler 能够静态地分析模块依赖关系,并删除与未使用的 module.exports 属性相关的代码。模板编译器还生成了对树抖动友好的代码,只有在模板中实际使用某个特性时,该代码才导入该特性的帮助程序。尽管增加了许多新特性,但 Vue 3 被压缩后的基线大小约为 10 KB,不到 Vue 2 的一半。
  5. 其他 Api 和功能的改动
  • Global API
  • 模板指令
  • 组件
  • 渲染函数
  • vue-cli 从 v4.5.0 开始提供 Vue 3 预设
  • Vue Router 4.0 提供了 Vue 3 支持,并有许多突破性的变化
  • Vuex 4.0 提供了 Vue 3 支持,其 API 与 2.x 基本相同
  1. 组件基本结构

在创建组件的时候我们不必在写唯一的根元素。移除了 vue2 中的 filters。

  1. 生命周期的区别

新增了 renderTracked 和 renderTriggered 两个生命周期。

  1. 增加了组合 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遇到过的坑

  1. 表单设置触发事件为blur,但是ctrl+A全选以后再删除时又触发了change事件,并提示一个原始报错
  2. 解决方案:trigger设置成trigger: [‘blur’, ‘change’]
  3. 使用el-dialog 遮罩层把显示内容遮住了
  4. 原因:Dialog 的外层布局的 position 值为 fixed, absolute, relative 三者之一时,就会出现被蒙板遮住的情况。
  5. 解决方法:v-bind:modal-append-to-body=“false”
  6. 使用el-select 不能继承父元素的宽度
  7. 原因:el-select 本身是 inline-block
  8. 解决办法:手动设置el-select的宽度

113.怎么修改element ui动态组件的样式

要修改elementUI组件的样式,可以采用以下两种方式

1.全局样式

通过选择权重覆盖elementUI组件的样式,如修改复选框为圆角:

    <style>
        .edit-item .el-checkbox__inner {
          border-radius: 50%;
        }
</style>

但这种方式为全局样式,会影响页面中所有复选框,如果不希望影响其它页面的样式,可以采用第二中方式

2.局部样式

    <style scoped>
        .edit-item .el-checkbox__inner {
          border-radius: 50%;
        }
</style>

但如果仅仅是设置了scoped属性,样式无法生效,原因是以上样式会被编译成属性选择器,而elementUI组件内部的结构却无法添加该html属性,以上样式被编译成如下代码:

    .edit-item[data-v-6558bc58] .el-checkbox__inner[data-v-6558bc58] {
          border-radius: 50%;
        }

解决方案也很简单,只需在选择器中要添加>>>即可



## Vue 面试题

1.Vue 双向绑定原理
2.描述下 vue 从初始化页面–修改数据–刷新页面 UI 的过程?
3.你是如何理解 Vue 的响应式系统的?
4.虚拟 DOM 实现原理
5.既然 Vue 通过数据劫持可以精准探测数据变化,为什么还需要虚拟 DOM 进行 diff 检测差异?
6.Vue 中 key 值的作用?
7.Vue 的生命周期
8.Vue 组件间通信有哪些方式?
9.watch、methods 和 computed 的区别?
10.vue 中怎么重置 data?
11.组件中写 name 选项有什么作用?
12.vue-router 有哪些钩子函数?
13.route 和 router 的区别是什么?
14.说一下 Vue 和 React 的认识,做一个简单的对比
15.Vue 的 nextTick 的原理是什么?
16.Vuex 有哪几种属性?
17.vue 首屏加载优化
18.Vue 3.0 有没有过了解?
19.vue-cli 替我们做了哪些工作?
![](https://img-blog.csdnimg.cn/img_convert/b19ad23219f92271b6d32fdba035c623.webp?x-oss-process=image/format,png)


<style>
    .edit-item .el-checkbox__inner {
      border-radius: 50%;
    }
```

但这种方式为全局样式,会影响页面中所有复选框,如果不希望影响其它页面的样式,可以采用第二中方式

2.局部样式

    <style scoped>
        .edit-item .el-checkbox__inner {
          border-radius: 50%;
        }
</style>

但如果仅仅是设置了scoped属性,样式无法生效,原因是以上样式会被编译成属性选择器,而elementUI组件内部的结构却无法添加该html属性,以上样式被编译成如下代码:

    .edit-item[data-v-6558bc58] .el-checkbox__inner[data-v-6558bc58] {
          border-radius: 50%;
        }

解决方案也很简单,只需在选择器中要添加>>>即可



## Vue 面试题

1.Vue 双向绑定原理
2.描述下 vue 从初始化页面–修改数据–刷新页面 UI 的过程?
3.你是如何理解 Vue 的响应式系统的?
4.虚拟 DOM 实现原理
5.既然 Vue 通过数据劫持可以精准探测数据变化,为什么还需要虚拟 DOM 进行 diff 检测差异?
6.Vue 中 key 值的作用?
7.Vue 的生命周期
8.Vue 组件间通信有哪些方式?
9.watch、methods 和 computed 的区别?
10.vue 中怎么重置 data?
11.组件中写 name 选项有什么作用?
12.vue-router 有哪些钩子函数?
13.route 和 router 的区别是什么?
14.说一下 Vue 和 React 的认识,做一个简单的对比
15.Vue 的 nextTick 的原理是什么?
16.Vuex 有哪几种属性?
17.vue 首屏加载优化
18.Vue 3.0 有没有过了解?
19.vue-cli 替我们做了哪些工作?
[外链图片转存中...(img-xpLBxhst-1718773031504)]

  • 24
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值