style.left和offsetLeft 面试题_学习笔记

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

.说说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 中有助于一致性

.兄弟和父子组件,在不能使用vuex的情况下有多少种方案,请列举出来?

回答:

1、父子之间传值:

  1)、父到子:props,子到父:$emit

  2)、$ref、$parent

  3)、事件总线(event-bus)

  4)、集中管理($root)


2、兄弟之间传值:

  1)、事件总线(event-bus)

  2)、集中管理($root)

 3)、先 子到父(用事件),再 父到子(用props)

.v-if和v-for可以同时使用吗

回答:

  可以使用,但是,在循环时,通过v-if只能拿到少部分数据时,建议不要使用。

  原因: v-for比v-if优先级高,如果遍历的数组元素个数比较多,但是满足v-if条件比较少的情况下。会浪费性能。而且,每次刷新页面时,都会执行这样性能不高的代码。

 解决:可以在computed里循环数组,通过filter的方式,过滤出需要的数据。v-for直接循环计算属性的结果(不用v-if)。而computed是有缓存的,所以,在原始数据没有变化时,不会多次过滤数据,这样,就提高了效率。

.vue-cli怎么配置跨域

回答:

使用反向代理,vue-cli3+的项目里,新建(编辑)vue.config.js文件,(增加)配置代码如下:

module.exports = {

  devServer:{
    //设置代理
    proxy: { //代理是从指定的target后面开始匹配的,不是任意位置;配置pathRewrite可以做替换
      '/api': { //这个是自定义的,当axios访问以/api开头的请求时,会代理到 target + /api
             target: 'http://localhost:', //最终要访问的服务器地址
            changeOrigin: true, //创建虚拟服务器
             pathRewrite: {
                '^/api': ''    //重写接口,去掉/api, 在代理过程中是否替换掉/api/路径
          }
        }
      }
   }
}

.v-bind是用来干什么的

回答:

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

格式如下:

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

示例:

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

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

此时,属性值也是动态的

示例:

我是div
new Vue({ el: "#app", data:{ attr:"class", idname:"div01" } })

.说说对插槽的理解

回答:

1、插槽的作用:

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

2、vue中的插槽分为:

1)、单个插槽

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

示例:

//1、定义组件
let book = {
        template: `
            

我是上p

我是下p

`, } //2、父组件的模板

2)、具名插槽

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

格式:

示例:

//1、组件:
    let book = {
        template: `
            

我是上p

我是中p

我是下p

`, } //2、父组件模板里:

.$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()的回调函数执行要先于定时器。

.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);
        }
    }
}

.mounted与created的区别

回答:

mounted和created都是vue对象生命周期的钩子函数,执行时机不同。

1、created 是在 data配置项的数据挂载到vue对象本身,会调用的钩子函数。

此时,

 1)、用 this. 的方式可以拿到data里的数据。

  2)、但是数据还没有渲染到模板上。所以,访问dom时,内容还是原始的模板内容。

2、mounted是在组件的模板初次渲染完毕后会调用的钩子函数。

此时,

  data里的数据已经渲染到模板上了。所以,访问dom时,已经和页面上看到的效果一样了。

.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:model let vm = new Vue({ el: "#app", data: { msg:"hi" } })


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

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

.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 支持异步;
    • 不支持缓存,监听的数据改变,直接会触发相应的操作;
    • 监听函数有两个参数,第一个参数是最新的值,第二个参数是输入之前的值,顺序一定是新值,旧值。

.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 作为参数。此事件告诉你是什么操作触发了重新渲染,以及该操作的目标对象和键。一共 个
  1. vue3 的组合 api 的生命周期移除了 beforeCreate 和 created,因为创建时的事件可以在 setup 里面直接调用。其他的 个生命周期前面全部加上 on比如:mounted -> onMounted, beforeDestroy -> onDeforeDestroy
  2. 网络请求为什么要挂载在 mounted 中?在 Created 生命周期里 Data 才生成,而请求返回的数据需要挂载在 data 上,所以 Created 里是可以初始化请求的,但是 Created 的这时候 DOM 还没有初始化;Mounted 生命周期里 DOM 才初始化渲染完成。然而,请求是异步的,所以不会堵塞页面渲染的主线程。所以请求放在 created 和 mounted 里面都是可行的。如果我们的请求不需要获取/借助/依赖/改变 DOM,这时请求可以放在 Created。反之则可以放在 Mounted 里。这样做会更加的安全,也能保证页面不会闪烁。

.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();
      }, );
    });
  },
};

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();
        }, );
      }
    };
    // 取消计时器
    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 $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监听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, );
    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 = ;
  can.height = ;
  can.style.display = "none";
  var cans = can.getContext("2d");
  cans.rotate((- * Math.PI) / );
  cans.font = font || "16px Microsoft JhengHei";
  cans.fillStyle = textColor || "rgba, , , )";
  cans.textAlign = "left";
  cans.textBaseline = "Middle";
  cans.fillText(str, can.width / , 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;

.vue 的移动端适配怎么做的,rem 怎么用的

vue 的移动端适配我们可以参考 vant-ui 组件库给我们提供的方案。使用 amfe-flexible(用于自动定义跟字体大小)插件和 postcss-pxtorem(用于将 px 自动转成 rem)插件在 main.ts 里面 import "amfe-flexible"在根目录新建 .postcssrc.js 文件

module.exports = {
  plugins: {
    "postcss-pxtorem": {
      rootValue: ,
      propList: ["*"],
    },
  },
};

rem 是相对于跟字体的倍数,如果我们整个项目都是用 rem 作为单位,那么当我们做移动端的响应式的时候只需要去改变跟字体的大小就能做到适配。

.后台管理系统用户验证权限

  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 中可访问的路由渲染侧边栏组件。

.vuex 做数据请求刷新页面,数据可能会发生丢失这个问题怎么解决

因为 store 里的数据是保存在运行内存中的,当页面刷新时,页面会重新加载 vue 实例,store 里面的数据就会被重新赋值初始化。所以我们可以在修改 store 的数据同时将数据再存一份到本地存储(localStorage 或者 sessionStorage),本地存储的内容是存在浏览器里面的,不会因为刷新而丢失。我们也可以用过比如 vuex-persistedstate 这样的第三方包来帮助我们做 vuex 的持久化数据。

.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 的配置项有 种,可以做更细致的事情,这是之前的 defineProperty 无法达到的。
  3. Diff 算法的提升vue3 在 vue2 的 diff 算法的基础上增加了静态标记,元素提升和事件缓存等优化。使得速度更快。
  4. 打包体积变化vue2 官方说的运行时打包师 23k,但这只是没安装依赖的时候,随着依赖包和框架特性的增多,有时候不必要的,未使用的代码文件都被打包了进去,所以后期项目大了,打包文件会特别多还很大。在 Vue 3 中,我们通过将大多数全局 API 和内部帮助程序移动到 Javascript 的 module.exports 属性上实现这一点。这允许现代模式下的 module bundler 能够静态地分析模块依赖关系,并删除与未使用的 module.exports 属性相关的代码。模板编译器还生成了对树抖动友好的代码,只有在模板中实际使用某个特性时,该代码才导入该特性的帮助程序。尽管增加了许多新特性,但 Vue 3 被压缩后的基线大小约为 KB,不到 Vue 2 的一半。
  5. 其他 Api 和功能的改动
  • Global API
  • 模板指令
  • 组件
  • 渲染函数
  • vue-cli 从 v4. 开始提供 Vue 3 预设
  • Vue Router 提供了 Vue 3 支持,并有许多突破性的变化
  • Vuex 提供了 Vue 3 支持,其 API 与 2.x 基本相同
  1. 组件基本结构

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

  1. 生命周期的区别

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

  1. 增加了组合 api

我们可以使用 setup 函数来使用类似 react 的自定义 hooks 的功能,主要解决逻辑关注点分离的问题。

.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 次,从 1 加到 ,那么有了 Virtual DOM 的缓存之后,只会把最后一次修改 patch 到 view 上。那 diff 算法的实现过程是怎样的?

那么为什么比原生操作 DOM 还快呢?首先我们每次操作 dom 的时候,都会去执行浏览器的那 5 个步骤,尤其是当大量循环的时候,每次循环完都不知道后面还要不要修改,所以每次都要去重复这个过程,引发不必要的渲染。但是在实际开发过程中,我们会发现虚拟 dom 并没有比真实 dom 更快。这个问题尤雨溪在知乎上面有过回答:这是一个性能 vs. 可维护性的取舍。框架的意义在于为你掩盖底层的 DOM 操作,让你用更声明式的方式来描述你的目的,从而让你的代码更容易维护。没有任何框架可以比纯手动的优化 DOM 操作更快,因为框架的 DOM 操作层需要应对任何上层 API 可能产生的操作,它的实现必须是普适的。针对任何一个 benchmark,我都可以写出比任何框架更快的手动优化,但是那有什么意义呢?在构建一个实际应用的时候,你难道为每一个地方都去做手动优化吗?出于可维护性的考虑,这显然不可能。框架给你的保证是,你在不需要手动优化的情况下,我依然可以给你提供过得去的性能。

.token 过期你是如何来进行处理,有没有弄过 token 续期

在开发中,我们经常会遇到使用 token,token 的作用是要验证用户是否处于登录状态,所以要请求一些只有登录状态才能查看的资源的时候,我们需要携带 token。

一般的后端接口设置的 token 是有时效的,超时后就会失效,失效之后的处理策略一般会做两种处理:

  • 一种是直接跳转到登录页面,重新登录。
  • 另外一种如果返回 token 失效的信息,自动去刷新 token,然后继续完成未完成的请求操作。

.vue底层实现原理

  • 使用 Object.defineProperty 劫持 data上的数据。
  • Vue2.0通过设定对象属性的 setter/getter 方法来监听数据的变化,通过getter进行依赖收集,而每个setter方法就是一个观察者,在数据变更的时候通知订阅者更新视图。

.Vue的生命周期,created与mounted的区别

1、created

表示组件实例已经完全创建,data数据已经被 Object.defineProperty 劫持完成,属性也绑定成功,但真实dom还没有生成,$el还不可用。

2、mounted

el选项所对应的视图节点已经被新创建的 vm.$el 替换,并挂载到实例上去了。此时响应式数据都已经完成了渲染。

.用vue写了商城,从列表页点进详情页,从详情页退出来的时候,怎么保持进入详情页之前的页面卷动值。

使用 对列表页面进行包裹,被包裹的列表页面就有了activated、deactivated这两个生命周期。在离开列表页面时,在deactivated中记录页面的滚动条位置。再次进入列表页面时,在activated中使用 this.$el.scrollTop 把页面滚动到离开时所记录的位置。

.说说你对vue的理解

  • vue是数据驱动的MVVM框架,相比传统的DOM库,vue有一层虚拟DOM。每当数据发生更新时,会触发虚拟DOM进行diff运算,找出最小的变化节点,大大地节省了DOM操作性能。
  • vue是组件化的,在单页面应用程序中,每一个组件相当于是一块积木,搭建起庞大的应用系统。组件,可以理解成更大粒度的“HTML元素”,有利于快速开发、组件的高效复用。
  • vue有一整套指令,大大地降低了开发者的开发难度,提升了开发效率。
  • 虽然vue有诸多优点,但仍然有一些缺陷,比如复杂的业务页面通常过长,data、methods、computed、watch对应的数据和逻辑交织在一起,难以维护。

.说说对虚拟DOM的理解

  • 在vue中,虚拟DOM本质上就是一个固定格式的JSON数据,它用于描述真实的DOM结构,并使用各种不同flag标记出动态的DOM节点。
  • 虚拟DOM数据保存在应用程序对应的内存结构中,拥有更快的数据交换速度。
  • 每当有数据发生变化时,就会生成新的虚拟DOM,进一步发生diff运算,找出最小脏节点,减少不必要的DOM开销,这也是vue拥有更好的性能的根本原因。

.说说provide的用法

  • 在父级组件中,使用provide选项向vue组件树中“提供数据”,其语法是:provide:{a: 1}
  • 在后代子级组件中,使用 inject选项从vue组件中“取出数据”,其语法是:inject: ['a']

.说一下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的宽度

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

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

1.全局样式

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

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

2.局部样式

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

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

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


如果是sass或less编写的样式,还可以使用/deep/


以上写法样式都会编译成以下样式:


.edit-item[data-v-6558bc58] .el-checkbox__inner{}
  1. 所以elementUI中的样式就能成功覆盖

.vue和react中的key值主要用来干什么

key是虚拟DOM对象的标识,在更新显示时key起着极其重要的作用,vue和react都是采用diff算法来对比新旧虚拟节点,而key的作用是为了在执行 diff算法 的时候,更快更准确地找到对应的虚拟节点,从而提高diff速度。

.route和router区别

route 和 router 是vue-router中经常会操作的两个对象, route表示当前的路由信息对象,包含了当前 URL 解析得到的信息,包含当前的路径、参数、query对象等,一般用于获取跳转时传入的参数。router对象是全局路由的实例,是router构造方法的实例,一般用户路由跳转,如router.push()、router.replace() 等方法

.vue和react相对于传统的有什么好处,性能优点

  1. 组件化开发,开发效率更高
  2. React与Vue都鼓励使用组件化开发。这本质上是建议你将你的应用分拆成一个个功能明确的模块,每个模块之间可以通过特定的方式进行关联。这样可以更好的管理功能模块与复用,在团队项目中也能更好的分工协作
  3. VirtualDOM,性能更高
  4. 对真实DOM的操作很慢,所以Vue和React都实现了虚拟DOM,用户对数据的修改首先反映到虚拟DOM上,而不是直接操作真实DOM,然后在虚拟DOM环节进行优化,比如只修改差异项,对虚拟 DOM 进行频繁修改时进行合并,然后一次性修改真实 DOM 中需要改的部分,最后在真实 DOM 中进行排版与重绘,减少过多DOM节点排版与重绘损耗等
  5. 数据和结构的分离
  6. 双向数据绑定
  7. Vue可以通过v-model指令实现,react可通过单向绑定+事件来实现
  8. 强大的周边生态
  9. Vue和React都有着强大的生态,比如路由、状态管理、脚手架等,用户可以根据需求自行选择,不需要重复造轮子

.虚拟DOM实现原理

我们先来看看浏览器渲染一个页面的过和,大概需要以下5个步骤

  1. 解析HTML元素,构建DOM树
  2. 解析CSS,生成页面CSS规则树(Style Rules)
  3. 将DOM树和CSS规则树进行关联,生成render树
  4. 布局(layout/reflow):浏览器设定Render树中的每个节点在屏幕上的位置与尺寸
  5. 绘制Render树:绘制页面像素信息到屏幕上

众所周知,一个页面在浏览器中最大的开销就是DOM节点操作,页面的性能问题大多数是DOM操作引起的,当我们用原生js 或jquery这样的库去操作DOM时,浏览器会从构建DOM树开始执行完以上整个流程,所以频繁操作DOM会引起不需要的计算、重排与重绘,从而导致页面卡顿,影响用户体验

所以减少DOM的操作能达到性能优化的目的,事实上,虚拟DOM就是这么做的,虚拟DOM(VirtualDOM) 的实现原理主要包括以下 3 部分:

  1. 用 JavaScript 对象模拟真实 DOM 树,对真实 DOM 进行抽象
  2. diff 算法 — 比较新旧两个虚拟DOM,得到差异对象
  3. pach 算法 — 将差异对象应用到真实 DOM 树

Virtual DOM 本质上就是一个javascript对象,数据的修改会生成一个新的虚拟DOM(一个新的javascript对象),然后与旧的虚拟DOM进行对比,得到两个对象的差异项,最后只更新差异对象中的内容到真实DOM,这样能做到最少限度地修改真实DOM,从而实现性能优化

.如何实现角色权限分配

在开发中后台应用过程中或多或少都会涉及到一个问题:权限,简单地说就是让不同的用户在系统中拥有不同的操作能力。

但在实际应用中我们一般不直接将权限赋予在用户身上,因为这样操作对有大量用户的系统来说过于繁琐,所以我们一般基于RBAC(Role-Based Access Control)权限模型,引入角色的概念,通过角色的媒介过渡,先将权限赋予在角色上,再关联相应的用户,对应的用户就继承了角色的权限

用户与角色,角色与权限都是多对多的关系

引入角色媒介的优点:

  1. 实现了用户与权限的解耦
  2. 提高了权限配置的效率
  3. 降低了后期维护的成本

.双向数据绑定和单向数据流的优缺点

所谓数据绑定,就是指View层和Model层之间的映射关系。

  • 单向数据绑定:Model的更新会触发View的更新,而View的更新不会触发Model的更新,它们的作用是单向的。
  • 优点:所有状态变化都可以被记录、跟踪,状态变化通过手动调用触发,源头易追溯。
  • 缺点:会有很多类似的样板代码,代码量会相应的上升。


  • 双向数据绑定:Model的更新会触发View的更新,View的更新也会触发Model的更新,它们的作用是相互的。
  • 优点:在操作表单时使用v-model方便简单,可以省略繁琐或重复的onChange事件去处理每个表单数据的变化(减少代码量)。
  • 缺点:属于暗箱操作,无法很好的追踪双向绑定的数据的变化。

.Vue是如何实现双向绑定的?

Vue的双向数据绑定是通过数据劫持结合发布者订阅者模式来实现的 要实现这种双向数据绑定,必要的条件有:

  1. 实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者
  2. 实现一个指令解析器Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
  3. 实现一个Watcher,作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图
  4. MVVM入口函数,整合以上三者

补充回答

在new Vue的时候,在 Observer 中通过 Object.defineProperty() 达到数据劫持,代理所有数据的 getter 和 setter 属性,在每次触发 setter 的时候,都会通过 Dep 来通知 Watcher,Watcher 作为Observer数据监听器与Compile模板解析器之间的桥梁,当 Observer 监听到数据发生改变的时候,通过 Updater 来通知 Compile 更新视图,而 Compile 通过 Watcher 订阅对应数据,绑定更新函数,通过 Dep 来添加订阅者,达到双向绑定。

.Proxy与Object.defineProperty的优劣对比?

Proxy的优势如下

  • Proxy可以直接监听整个对象而非属性。
  • Proxy可以直接监听数组的变化。
  • Proxy有中拦截方法,如ownKeys、deleteProperty、has 等是 Object.defineProperty 不具备的。
  • Proxy返回的是一个新对象,我们可以只操作新的对象达到目的,而Object.defineProperty只能遍历对象属性直接修改;
  • Proxy做为新标准将受到浏览器产商重点持续的性能优化,也就是传说中的新标准的性能红利。

Object.defineProperty 的优势如下

  • 兼容性好,支持 IE9,而 Proxy 的存在浏览器兼容性问题,而且无法用 polyfill 磨平。

Object.defineProperty 不足在于

  • Object.defineProperty 只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。
  • Object.defineProperty不能监听数组。是通过重写数据的那7个可以改变数据的方法来对数组进行监听的。
  • Object.defineProperty 也不能对 es6 新产生的 Map,Set 这些数据结构做出监听。
  • Object.defineProperty也不能监听新增和删除操作,通过 Vue.set()和 Vue.delete来实现响应式的。

.你是如何理解Vue的响应式系统的?

就是能够自动追踪数据的变化,而不必手动触发视图更新。Vue2.X通过Object.defineProperty()做数据劫持,而Vue3通过Proxy做数据代理,从而捕捉到对数据的get和set。

  • 什么叫数据响应式?

简单说就是用户更改数据(Data)时,视图可以自动刷新,页面UI能够响应数据变化。

  • 为什么要了解Vue数据响应式?

因为这是Vue的立身之本呀,连尤雨溪都在给放文档上这样说,“Vue 最独特的特性之一,是其非侵入性的响应式系统。”

Vue就是通过getter 和setter来对数据进行操作,通过get()和set()函数可以生成一个虚拟属性,来直接调用函数操作数据。这一步是被封装起来的,我们是看不到的。

此外,还需要一个重要的媒介API,那就是Object.defineProperty,因为对象被定义完成后,想要更改或添加属性(像是get和set这样的属性),只能通过这个Object.defineProperty来添加。接着需要实现对数据属性的读写进行监控。能够监控就意味着能让vm(一般生成的实例)能够知道数据有变化,从而触发render(data)函数,页面UI就会做出响应。

.既然Vue通过数据劫持可以精准探测数据变化,为什么还需要虚拟DOM进行diff检测差异?

1.减少对真实DOM的操作

Diff 算法探讨的就是虚拟 DOM 树发生变化后,生成 DOM 树更新补丁的方式。对比新旧两株虚拟 DOM 树的变更差异,将更新补丁作用于真实 DOM,以最小成本完成视图更新。

具体流程:

  1. 真实 DOM 与虚拟 DOM 之间存在一个映射关系。这个映射关系依靠初始化时的 JSX 建立完成;
  2. 当虚拟 DOM 发生变化后,就会根据差距计算生成 patch,这个 patch 是一个结构化的数据,内容包含了增加、更新、移除等;
  3. 最后再根据 patch 去更新真实的 DOM,反馈到用户的界面上。

这样一个生成补丁、更新差异的过程统称为 diff 算法。

这里涉及3个要点:

  1. 更新时机:更新发生在setState、Hooks 调用等操作以后
  2. 遍历算法:采用深度优先遍历,从根节点出发,沿着左子树方向进行纵向遍历,直到找到叶子节点为止。然后回溯到前一个节点,进行右子树节点的遍历,直到遍历完所有可达节点
  3. 优化策略:树、组件及元素三个层面进行复杂度的优化

    1. 元素比对主要发生在同层级中,通过标记节点操作生成补丁。节点操作包含了插入、移动、删除等。其中节点重新排序同时涉及插入、移动、删除三个操作,所以效率消耗最大,此时策略三起到了至关重要的作用。通过标记 key 的方式,React 可以直接移动 DOM 节点,降低内耗
    2. 在组件比对的过程中:如果组件是同一类型则进行树比对;如果不是则直接放入补丁中。只要父组件类型不同,就会被重新渲染。这也就是为什么shouldComponentUpdate、PureComponent 及 React.memo 可以提高性能的原因
    3. 这一策略需要进行树比对,即对树进行分层比较。树比对的处理手法是非常“暴力”的,即两棵树只对同一层次的节点进行比较,如果发现节点已经不存在了,则该节点及其子节点会被完全删除掉,不会用于进一步的比较,这就提升了比对效率
    4. 忽略节点跨层级操作场景,提升比对效率。

    5. 如果组件的 class 一致,则默认为相似的树结构,否则默认为不同的树结构

    6. 同一层级的子节点,可以通过标记 key 的方式进行列表对比

通过diff算法能准确的获取到具体的节点 进行针对操作!!!

.Vue为什么没有类似于React中shouldComponentUpdate的生命周期?

因为 Vue 的响应式系统已经在初次渲染时收集了渲染依赖的数据项,通过自动的方式就能够得到相当不错的性能。不过,在一些场景下,手动控制刷新检查还是能够进一步提升渲染性能的

vue 的机制,其实就是个依赖关系管理机制。不管是计算属性,watcher,以及 renderer,站在外部看,模型都是一样的,即初始化的时候通过一些方法分析出来这个模块依赖谁,等被依赖的数据更新了,这些模块就重新运算一遍产出物(实际上不尽然,比如计算属性有惰性机制)。

具体到 renderer,哪些数据的变更会引起 dom 变更,在模板编译的时候已经确定了,并且写死在生成的代码里了。

而 react 是没有这种自动机制的,它去执行 render 唯一的原因就是你主动让他 render。那你什么时候让它 render 呢?工程上一般是使用一个数据流工具,数据有变化的时候发出一个事件,一股脑把数据推过来,不区分哪个字段有变更(你区分了也没用,renderer 根本不认)。如果这个数据流模型是多个组件共用的,那必然是在 render 之前有个 hook 给我们个机会告诉组件“这次没你的事儿”,有利于性能优化。

那么,我们有没有可能不增加代码静态分析环节,搞清楚 react renderer 到底依赖哪些数据,继而把这个判断自动做掉呢?依我看不太可能,因为我们不能保证运行期跑一遍 render,它就会一次性访问它所有可能访问的数据。

.Vue中的key到底有什么用?

1、key的作用主要是为了搞笑的更新虚拟dom,其原理是vue在patch过程中通过key可以精准判断两个节点是否是同一个,从而避免频繁更新不同元素,使得整个patch过程更加高效,减少dom操作量,提高性能。2、另外,若不设置key还可能在列表更新时候引发一些隐藏的bug。3、vue中在使用相同标签名元素的过渡切换时,也会使用到key属性,其目的也是为了让vue可以区分它们,否则vue只会替换其内部属性而不会触发过渡效果。

.不能用index做key

看了上面的介绍其实也很容易明白为什么不能用index作为key。

1、影响性能:当用index作为key的时候,删除节点后面的所有节点都会导致重新渲染,因为index变化了,可以也就变化了

有人说,当你的列表数据没有变化的时候可以用index作为key。也就是说列表不会触发更新元素,只有静态展示。这种说法你怎么看呢?之所以说到这个问题,是在vue官方群里面一群人应为这个问题讨论半天。我弱弱回复一句,任何情况下都不要用index作为key。结果遭到炮轰,哎!(除非前端写死的list,且无操作不会引起key变化,只要是后端数据,前端怎么能保证数据不变呢)。关于这个问题,我有这样三点想法:

1、代码的规范性2、类比typescript,为什么变量要加类型,除了规范,也方便定位错误3、列表的顺序稳定其实是难以保证的

.vue项目中用jsx语法

JSX就是Javascript和XML结合的一种格式。React发明了JSX,利用HTML语法来创建虚拟DOM。当遇到<,JSX就当HTML解析,遇到{就当JavaScript解析.

我为什么要在vue中用JSX?

是使用的的模板语法,Vue的模板实际上就是编译成了 render 函数,同样支持 JSX 语法。在 Vue 官网中,提供 createElement 函数中使用模板中的功能。


但是极少数的 VUE项目用JSX语法 JSX语法 根植在react上面 在vue上还是 template舒服!

原文链接:,转发请注明来源!