乐趣区

关于前端:2023前端vue面试题汇总

Vuex 有哪几种属性?

有五种,别离是 State、Getter、Mutation、Action、Module

  • state => 根本数据(数据源寄存地)
  • getters => 从根本数据派生进去的数据
  • mutations => 提交更改数据的办法,同步
  • actions => 像一个装璜器,包裹 mutations,使之能够异步。
  • modules => 模块化 Vuex

Vue 中修饰符.sync 与 v -model 的区别

sync的作用

  • .sync修饰符能够实现父子组件之间的双向绑定,并且能够实现子组件同步批改父组件的值,相比拟与 v-model 来说,sync修饰符就简略很多了
  • 一个组件上能够有多个 .sync 修饰符
<!-- 失常父传子 -->
<Son :a="num" :b="num2" />

<!-- 加上 sync 之后的父传子 -->
<Son :a.sync="num" :b.sync="num2" />

<!-- 它等价于 -->
<Son 
  :a="num" 
  :b="num2" 
  @update:a="val=>num=val" 
  @update:b="val=>num2=val" 
/>

<!-- 相当于多了一个事件监听,事件名是 update:a, -->
<!-- 回调函数中,会把接管到的值赋值给属性绑定的数据项中。-->

v-model的工作原理

<com1 v-model="num"></com1>
<!-- 等价于 -->
<com1 :value="num" @input="(val)=>num=val"></com1>
  • 相同点

    • 都是语法糖,都能够实现父子组件中的数据的双向通信
  • 区别点

    • 格局不同:v-model="num", :num.sync="num"
    • v-model: @input + value
    • :num.sync: @update:num
    • v-model只能用一次;.sync能够有多个

Vue 路由的钩子函数

首页能够管制导航跳转,beforeEachafterEach等,个别用于页面 title 的批改。一些须要登录能力调整页面的重定向性能。

  • beforeEach次要有 3 个参数tofromnext
  • toroute行将进入的指标路由对象。
  • fromroute以后导航正要来到的路由。
  • nextfunction肯定要调用该办法 resolve 这个钩子。执行成果依赖 n ext办法的调用参数。能够管制网页的跳转

对 Vue SSR 的了解

Vue.js 是构建客户端应用程序的框架。默认状况下,能够在浏览器中输入 Vue 组件,进行生成 DOM 和操作 DOM。然而,也能够将同一个组件渲染为服务端的 HTML 字符串,将它们间接发送到浏览器,最初将这些动态标记 ” 激活 ” 为客户端上齐全可交互的应用程序。

SSR也就是服务端渲染,也就是将 Vue 在客户端把标签渲染成 HTML 的工作放在服务端实现,而后再把 html 间接返回给客户端

  • 长处SSR 有着更好的 SEO、并且首屏加载速度更快

    • 因为 SPA 页面的内容是通过 Ajax 获取,而搜索引擎爬取工具并不会期待 Ajax 异步实现后再抓取页面内容,所以在 SPA 中是抓取不到页面通过 Ajax获取到的内容;而 SSR 是间接由服务端返回曾经渲染好的页面(数据曾经蕴含在页面中),所以搜索引擎爬取工具能够抓取渲染好的页面
    • 更快的内容达到工夫(首屏加载更快):SPA 会期待所有 Vue 编译后的 js 文件都下载实现后,才开始进行页面的渲染,文件下载等须要肯定的工夫等,所以首屏渲染须要肯定的工夫;SSR 间接由服务端渲染好页面间接返回显示,无需期待下载 js 文件及再去渲染等,所以 SSR 有更快的内容达到工夫
  • 毛病:开发条件会受到限制,服务器端渲染只反对 beforeCreatecreated 两个钩子,当咱们须要一些内部扩大库时须要非凡解决,服务端渲染应用程序也须要处于 Node.js 的运行环境。服务器会有更大的负载需要

    • 在 Node.js 中渲染残缺的应用程序,显然会比仅仅提供动态文件的 server 更加大量占用 CPU 资源 (CPU-intensive – CPU 密集),因而如果你意料在高流量环境 (high traffic) 下应用,请筹备相应的服务器负载,并明智地采纳缓存策略

其根本实现原理

  • app.js 作为客户端与服务端的专用入口,导出 Vue 根实例,供客户端 entry 与服务端 entry 应用。客户端 entry 次要作用挂载到 DOM 上,服务端 entry 除了创立和返回实例,还进行路由匹配与数据预获取。
  • webpack 为客服端打包一个 Client Bundle,为服务端打包一个 Server Bundle
  • 服务器接管申请时,会依据 url,加载相应组件,获取和解析异步数据,创立一个读取 Server BundleBundleRenderer,而后生成 html 发送给客户端。
  • 客户端混合,客户端收到从服务端传来的 DOM 与本人的生成的 DOM 进行比照,把不雷同的 DOM 激活,使其能够可能响应后续变动,这个过程称为客户端激活。为确保混合胜利,客户端与服务器端须要共享同一套数据。在服务端,能够在渲染之前获取数据,填充到 stroe 里,这样,在客户端挂载到 DOM 之前,能够间接从 store里取数据。首屏的动态数据通过 window.__INITIAL_STATE__发送到客户端

Vue SSR 的实现,次要就是把 Vue 的组件输入成一个残缺 HTML, vue-server-renderer 就是干这事的

Vue SSR须要做的事多点(输入残缺 HTML),除了complier -> vnode,还需如数据获取填充至 HTML、客户端混合(hydration)、缓存等等。相比于其余模板引擎(ejs, jade 等),最终要实现的目标是一样的,性能上可能要差点

Vue 组件之间通信形式有哪些

Vue 组件间通信是面试常考的知识点之一,这题有点相似于凋谢题,你答复出越多办法当然越加分,表明你对 Vue 把握的越纯熟。Vue 组件间通信只有指以下 3 类通信 父子组件通信 隔代组件通信 兄弟组件通信,上面咱们别离介绍每种通信形式且会阐明此种办法可实用于哪类组件间通信

组件传参的各种形式

组件通信罕用形式有以下几种

  • props / $emit 实用 父子组件通信

    • 父组件向子组件传递数据是通过 prop 传递的,子组件传递数据给父组件是通过$emit 触发事件来做到的
  • ref$parent / $children(vue3 废除) 实用 父子组件通信

    • ref:如果在一般的 DOM 元素上应用,援用指向的就是 DOM 元素;如果用在子组件上,援用就指向组件实例
    • $parent / $children:拜访拜访父组件的属性或办法 / 拜访子组件的属性或办法
  • EventBus($emit / $on) 实用于 父子、隔代、兄弟组件通信

    • 这种办法通过一个空的 Vue 实例作为地方事件总线(事件核心),用它来触发事件和监听事件,从而实现任何组件间的通信,包含父子、隔代、兄弟组件
  • $attrs / $listeners(vue3 废除) 实用于 隔代组件通信

    • $attrs:蕴含了父作用域中不被 prop 所辨认 (且获取) 的个性绑定 (classstyle 除外 )。当一个组件没有申明任何 prop时,这里会蕴含所有父作用域的绑定 (classstyle 除外 ),并且能够通过 v-bind="$attrs" 传入外部组件。通常配合 inheritAttrs 选项一起应用
    • $listeners:蕴含了父作用域中的 (不含 .native 润饰器的) v-on 事件监听器。它能够通过 v-on="$listeners" 传入外部组件
  • provide / inject 实用于 隔代组件通信

    • 先人组件中通过 provider 来提供变量,而后在子孙组件中通过 inject 来注入变量。provide / inject API 次要解决了跨级组件间的通信问题,不过它的应用场景,次要是子组件获取下级组件的状态,跨级组件间建设了一种被动提供与依赖注入的关系
  • $root 实用于 隔代组件通信 拜访根组件中的属性或办法,是根组件,不是父组件。$root 只对根组件有用
  • Vuex 实用于 父子、隔代、兄弟组件通信

    • Vuex 是一个专为 Vue.js 利用程序开发的状态管理模式。每一个 Vuex 利用的外围就是 store(仓库)。“store”基本上就是一个容器,它蕴含着你的利用中大部分的状态 (state )
    • Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地失去高效更新。
    • 扭转 store 中的状态的惟一路径就是显式地提交 (commit) mutation。这样使得咱们能够不便地跟踪每一个状态的变动。

依据组件之间关系探讨组件通信最为清晰无效

  • 父子组件:props/$emit/$parent/ref
  • 兄弟组件:$parent/eventbus/vuex
  • 跨层级关系:eventbus/vuex/provide+inject/$attrs + $listeners/$root

上面演示组件之间通信三种状况: 父传子、子传父、兄弟组件之间的通信

1. 父子组件通信

应用 props,父组件能够应用props 向子组件传递数据。

父组件 vue 模板father.vue:

<template>
  <child :msg="message"></child>
</template>

<script>
import child from './child.vue';
export default {
  components: {child},
  data () {
    return {message: 'father message';}
  }
}
</script>

子组件 vue 模板child.vue:

<template>
    <div>{{msg}}</div>
</template>

<script>
export default {
  props: {
    msg: {
      type: String,
      required: true
    }
  }
}
</script>

回调函数(callBack)

父传子:将父组件里定义的 method 作为 props 传入子组件

// 父组件 Parent.vue:<Child :changeMsgFn="changeMessage">
methods: {changeMessage(){this.message = 'test'}
}
// 子组件 Child.vue:<button @click="changeMsgFn">
props:['changeMsgFn']

子组件向父组件通信

父组件向子组件传递事件办法,子组件通过 $emit 触发事件,回调给父组件

父组件 vue 模板father.vue:

<template>
    <child @msgFunc="func"></child>
</template>

<script>
import child from './child.vue';
export default {
    components: {child},
    methods: {func (msg) {console.log(msg);
        }
    }
}
</script>

子组件 vue 模板child.vue:

<template>
    <button @click="handleClick"> 点我 </button>
</template>

<script>
export default {
    props: {
        msg: {
            type: String,
            required: true
        }
    },
    methods () {handleClick () {
          //........
          this.$emit('msgFunc');
        }
    }
}
</script>

2. provide / inject 跨级拜访先人组件的数据

父组件通过应用 provide(){return{}} 提供须要传递的数据

export default {data() {
    return {
      title: '我是父组件',
      name: 'poetry'
    }
  },
  methods: {say() {alert(1)
    }
  },
  // provide 属性 可能为前面的后辈组件 / 嵌套的组件提供所须要的变量和办法
  provide() {
    return {
      message: '我是先人组件提供的数据',
      name: this.name, // 传递属性
      say: this.say
    }
  }
}

子组件通过应用 inject:[“参数 1”,”参数 2”,…] 接管父组件传递的参数

<template>
  <p> 曾孙组件 </p>
  <p>{{message}}</p>
</template>
<script>
export default {
  // inject 注入 / 接管先人组件传递的所须要的数据即可 
  // 接管到的数据 变量 跟 data 外面的变量一样 能够间接绑定到页面 {{}}
  inject: ["message","say"],
  mounted() {this.say();
  },
};
</script>

3. $parent + $children 获取父组件实例和子组件实例的汇合

  • this.$parent 能够间接拜访该组件的父实例或组件
  • 父组件也能够通过 this.$children 拜访它所有的子组件;须要留神 $children 并不保障程序,也不是响应式的
<!-- parent.vue -->
<template>
<div>
  <child1></child1>   
  <child2></child2> 
  <button @click="clickChild">$children 形式获取子组件值 </button>
</div>
</template>
<script>
import child1 from './child1'
import child2 from './child2'
export default {data(){
    return {total: 108}
  },
  components: {
    child1,
    child2  
  },
  methods: {funa(e){console.log("index",e)
    },
    clickChild(){console.log(this.$children[0].msg);
      console.log(this.$children[1].msg);
    }
  }
}
</script>
<!-- child1.vue -->
<template>
  <div>
    <button @click="parentClick"> 点击拜访父组件 </button>
  </div>
</template>
<script>
export default {data(){
    return {msg:"child1"}
  },
  methods: {
    // 拜访父组件数据
    parentClick(){this.$parent.funa("xx")
      console.log(this.$parent.total);
    }
  }
}
</script>
<!-- child2.vue -->
<template>
  <div>
    child2
  </div>
</template>
<script>
export default {data(){
    return {msg: 'child2'}
  }
}
</script>

4. $attrs + $listeners 多级组件通信

$attrs 蕴含了从父组件传过来的所有 props 属性

// 父组件 Parent.vue:<Child :name="name" :age="age"/>

// 子组件 Child.vue:<GrandChild v-bind="$attrs" />

// 孙子组件 GrandChild
<p> 姓名:{{$attrs.name}}</p>
<p> 年龄:{{$attrs.age}}</p>

$listeners蕴含了父组件监听的所有事件

// 父组件 Parent.vue:<Child :name="name" :age="age" @changeNameFn="changeName"/>

// 子组件 Child.vue:<button @click="$listeners.changeNameFn"></button>

5. ref 父子组件通信

// 父组件 Parent.vue:<Child ref="childComp"/>
<button @click="changeName"></button>
changeName(){console.log(this.$refs.childComp.age);
    this.$refs.childComp.changeAge()}

// 子组件 Child.vue:data(){
    return{age:20}
},
methods(){changeAge(){this.age=15}
}

6. 非父子, 兄弟组件之间通信

vue2中废除了 broadcast 播送和散发事件的办法。父子组件中能够用 props$emit()。如何实现非父子组件间的通信,能够通过实例一个 vue 实例 Bus 作为媒介,要互相通信的兄弟组件之中,都引入 Bus,而后通过别离调用 Bus 事件触发和监听来实现通信和参数传递。Bus.js 能够是这样:

// Bus.js

// 创立一个地方工夫总线类  
class Bus {constructor() {this.callbacks = {};   // 寄存事件的名字  
  }  
  $on(name, fn) {this.callbacks[name] = this.callbacks[name] || [];  
    this.callbacks[name].push(fn);  
  }  
  $emit(name, args) {if (this.callbacks[name]) {this.callbacks[name].forEach((cb) => cb(args));  
    }  
  }  
}  

// main.js  
Vue.prototype.$bus = new Bus() // 将 $bus 挂载到 vue 实例的原型上  
// 另一种形式  
Vue.prototype.$bus = new Vue() // Vue 曾经实现了 Bus 的性能  
<template>
    <button @click="toBus"> 子组件传给兄弟组件 </button>
</template>

<script>
export default{
    methods: {toBus () {this.$bus.$emit('foo', '来自兄弟组件')
    }
  }
}
</script>

另一个组件也在钩子函数中监听 on 事件

export default {data() {
    return {message: ''}
  },
  mounted() {this.$bus.$on('foo', (msg) => {this.message = msg})
  }
}

7. $root 拜访根组件中的属性或办法

  • 作用:拜访根组件中的属性或办法
  • 留神:是根组件,不是父组件。$root只对根组件有用
var vm = new Vue({
  el: "#app",
  data() {
    return {rootInfo:"我是根元素的属性"}
  },
  methods: {alerts() {alert(111)
    }
  },
  components: {
    com1: {data() {
        return {info: "组件 1"}
      },
      template: "<p>{{info}} <com2></com2></p>",
      components: {
        com2: {
          template: "<p> 我是组件 1 的子组件 </p>",
          created() {this.$root.alerts()// 根组件办法
            console.log(this.$root.rootInfo)// 我是根元素的属性
          }
        }
      }
    }
  }
});

8. vuex

  • 实用场景: 简单关系的组件数据传递
  • Vuex 作用相当于一个用来存储共享变量的容器
  • state用来寄存共享变量的中央
  • getter,能够减少一个 getter 派生状态,(相当于 store 中的计算属性),用来取得共享变量的值
  • mutations用来寄存批改 state 的办法。
  • actions也是用来寄存批改 state 的办法,不过 action 是在 mutations 的根底上进行。罕用来做一些异步操作

小结

  • 父子关系的组件数据传递抉择 props$emit进行传递,也可抉择ref
  • 兄弟关系的组件数据传递可抉择 $bus,其次能够抉择$parent 进行传递
  • 先人与后辈组件数据传递可抉择 attrslisteners或者 ProvideInject
  • 简单关系的组件数据传递能够通过 vuex 寄存共享的变量

Vue 的事件绑定原理

原生事件绑定是通过 addEventListener 绑定给实在元素的,组件事件绑定是通过 Vue 自定义的$on 实现的。如果要在组件上应用原生事件,须要加.native 修饰符,这样就相当于在父组件中把子组件当做一般 html 标签,而后加上原生事件。

$on$emit 是基于公布订阅模式的,保护一个事件核心,on 的时候将事件按名称存在事件中心里,称之为订阅者,而后 emit 将对应的事件进行公布,去执行事件中心里的对应的监听器

EventEmitter(公布订阅模式 – 简略版)

// 手写公布订阅模式 EventEmitter
class EventEmitter {constructor() {this.events = {};
  }
  // 实现订阅
  on(type, callBack) {if (!this.events) this.events = Object.create(null);

    if (!this.events[type]) {this.events[type] = [callBack];
    } else {this.events[type].push(callBack);
    }
  }
  // 删除订阅
  off(type, callBack) {if (!this.events[type]) return;
    this.events[type] = this.events[type].filter(item => {return item !== callBack;});
  }
  // 只执行一次订阅事件
  once(type, callBack) {function fn() {callBack();
      this.off(type, fn);
    }
    this.on(type, fn);
  }
  // 触发事件
  emit(type, ...rest) {this.events[type] && this.events[type].forEach(fn => fn.apply(this, rest));
  }
}


// 应用如下
const event = new EventEmitter();

const handle = (...rest) => {console.log(rest);
};

event.on("click", handle);

event.emit("click", 1, 2, 3, 4);

event.off("click", handle);

event.emit("click", 1, 2);

event.once("dbClick", () => {console.log(123456);
});
event.emit("dbClick");
event.emit("dbClick");

源码剖析

  1. 原生 dom 的绑定
  2. Vue 在创立真是 dom 时会调用 createElm , 默认会调用 invokeCreateHooks
  3. 会遍历以后平台下绝对的属性解决代码, 其中就有 updateDOMListeners 办法, 外部会传入 add 办法
function updateDOMListeners (oldVnode: VNodeWithData, vnode: VNodeWithData) {if (isUndef(oldVnode.data.on) && isUndef(vnode.data.on)) {return}
    const on = vnode.data.on || {} 
    const oldOn = oldVnode.data.on || {} 
    target = vnode.elm normalizeEvents(on) 
    updateListeners(on, oldOn, add, remove, createOnceHandler, vnode.context) 
    target = undefined 
}
function add (name: string, handler: Function, capture: boolean, passive: boolean) {
    target.addEventListener( // 给以后的 dom 增加事件 
        name, 
        handler, 
        supportsPassive ? {capture, passive} : capture 
    ) 
}

vue 中绑定事件是间接绑定给实在 dom 元素的

  1. 组件中绑定事件
export function updateComponentListeners (vm: Component, listeners: Object, oldListeners: ?Object) {target = vm updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
    target = undefined 
}
function add (event, fn) {target.$on(event, fn) 
}

组件绑定事件是通过 vue 中自定义的 $on 办法来实现的

参考 前端进阶面试题具体解答

Vue 为什么要用 vm.$set() 解决对象新增属性不能响应的问题?你能说说如下代码的实现原理么?

1)Vue 为什么要用 vm.$set() 解决对象新增属性不能响应的问题

  1. Vue 应用了 Object.defineProperty 实现双向数据绑定
  2. 在初始化实例时对属性执行 getter/setter 转化
  3. 属性必须在 data 对象上存在能力让 Vue 将它转换为响应式的(这也就造成了 Vue 无奈检测到对象属性的增加或删除)

所以 Vue 提供了 Vue.set (object, propertyName, value) / vm.$set (object, propertyName, value)

2)接下来咱们看看框架自身是如何实现的呢?

Vue 源码地位:vue/src/core/instance/index.js

export function set (target: Array<any> | Object, key: any, val: any): any {
  // target 为数组  
  if (Array.isArray(target) && isValidArrayIndex(key)) {// 批改数组的长度, 防止索引 > 数组长度导致 splcie()执行有误
    target.length = Math.max(target.length, key)
    // 利用数组的 splice 变异办法触发响应式  
    target.splice(key, 1, val)
    return val
  }
  // key 曾经存在,间接批改属性值  
  if (key in target && !(key in Object.prototype)) {target[key] = val
    return val
  }
  const ob = (target: any).__ob__
  // target 自身就不是响应式数据, 间接赋值
  if (!ob) {target[key] = val
    return val
  }
  // 对属性进行响应式解决
  defineReactive(ob.value, key, val)
  ob.dep.notify()
  return val
}

咱们浏览以上源码可知,vm.$set 的实现原理是:

  1. 如果指标是数组,间接应用数组的 splice 办法触发相应式;
  2. 如果指标是对象,会先判读属性是否存在、对象是否是响应式,
  3. 最终如果要对属性进行响应式解决,则是通过调用 defineReactive 办法进行响应式解决

defineReactive 办法就是 Vue 在初始化对象时,给对象属性采纳 Object.defineProperty 动静增加 getter 和 setter 的性能所调用的办法

对 SSR 的了解

SSR 也就是服务端渲染,也就是将 Vue 在客户端把标签渲染成 HTML 的工作放在服务端实现,而后再把 html 间接返回给客户端

SSR 的劣势:

  • 更好的 SEO
  • 首屏加载速度更快

SSR 的毛病:

  • 开发条件会受到限制,服务器端渲染只反对 beforeCreate 和 created 两个钩子;
  • 当须要一些内部扩大库时须要非凡解决,服务端渲染应用程序也须要处于 Node.js 的运行环境;
  • 更多的服务端负载。

MVVM的优缺点?

长处:

  • 拆散视图(View)和模型(Model),升高代码耦合,提⾼视图或者逻辑的重⽤性: ⽐如视图(View)能够独⽴于 Model 变动和批改,⼀个 ViewModel 能够绑定不同的 ”View” 上,当 View 变动的时候 Model 不能够不变,当 Model 变动的时候 View 也能够不变。你能够把⼀些视图逻辑放在⼀个 ViewModel ⾥⾯,让很多 view 重⽤这段视图逻辑
  • 提⾼可测试性: ViewModel 的存在能够帮忙开发者更好地编写测试代码
  • ⾃动更新 dom: 利⽤双向绑定, 数据更新后视图⾃动更新, 让开发者从繁琐的⼿动 dom 中解放

毛病:

  • Bug 很难被调试: 因为使⽤双向绑定的模式,当你看到界⾯异样了,有可能是你 View 的代码有 Bug,也可能是 Model 的代码有问题。数据绑定使得⼀个地位的 Bug 被疾速传递到别的地位,要定位原始出问题的地⽅就变得不那么容易了。另外,数据绑定的申明是指令式地写在 View 的模版当中的,这些内容是没方法去打断点 debug 的
  • ⼀个⼤的模块中 model 也会很⼤,尽管使⽤⽅便了也很容易保障了数据的⼀致性,过后⻓期持有,不开释内存就造成了破费更多的内存
  • 对于⼤型的图形应⽤程序,视图状态较多,ViewModel 的构建和保护的老本都会⽐较⾼。

Vue 组件通信有哪几种形式

  1. props 和 $emit 父组件向子组件传递数据是通过 prop 传递的,子组件传递数据给父组件是通过 $emit 触发事件来做到的
  2. $parent,$children 获取以后组件的父组件和以后组件的子组件
  3. $attrs 和 $listeners A->B->C。Vue 2.4 开始提供了 $attrs 和 $listeners 来解决这个问题
  4. 父组件中通过 provide 来提供变量,而后在子组件中通过 inject 来注入变量。(官网不举荐在理论业务中应用,然而写组件库时很罕用)
  5. $refs 获取组件实例
  6. envetBus 兄弟组件数据传递 这种状况下能够应用事件总线的形式
  7. vuex 状态治理

如何保留页面的以后的状态

既然是要放弃页面的状态(其实也就是组件的状态),那么会呈现以下两种状况:

  • 前组件会被卸载
  • 前组件不会被卸载

那么能够依照这两种状况别离失去以下办法:

组件会被卸载:

(1)将状态存储在 LocalStorage / SessionStorage

只须要在组件行将被销毁的生命周期 componentWillUnmount(react)中在 LocalStorage / SessionStorage 中把以后组件的 state 通过 JSON.stringify() 贮存下来就能够了。在这外面须要留神的是组件更新状态的机会。

比方从 B 组件跳转到 A 组件的时候,A 组件须要更新本身的状态。然而如果从别的组件跳转到 B 组件的时候,实际上是心愿 B 组件从新渲染的,也就是不要从 Storage 中读取信息。所以须要在 Storage 中的状态退出一个 flag 属性,用来管制 A 组件是否读取 Storage 中的状态。

长处:

  • 兼容性好,不须要额定库或工具。
  • 简略快捷,根本能够满足大部分需要。

毛病:

  • 状态通过 JSON 办法贮存(相当于深拷贝),如果状态中有非凡状况(比方 Date 对象、Regexp 对象等)的时候会失去字符串而不是原来的值。(具体参考用 JSON 深拷贝的毛病)
  • 如果 B 组件后退或者下一页跳转并不是前组件,那么 flag 判断会生效,导致从其余页面进入 A 组件页面时 A 组件会从新读取 Storage,会造成很奇怪的景象

(2)路由传值

通过 react-router 的 Link 组件的 prop —— to 能够实现路由间传递参数的成果。

在这里须要用到 state 参数,在 B 组件中通过 history.location.state 就能够拿到 state 值,保留它。返回 A 组件时再次携带 state 达到路由状态放弃的成果。

长处:

  • 简略快捷,不会净化 LocalStorage / SessionStorage。
  • 能够传递 Date、RegExp 等非凡对象(不必放心 JSON.stringify / parse 的有余)

毛病:

  • 如果 A 组件能够跳转至多个组件,那么在每一个跳转组件内都要写雷同的逻辑。

组件不会被卸载:

(1)单页面渲染

要切换的组件作为子组件全屏渲染,父组件中失常贮存页面状态。

长处:

  • 代码量少
  • 不须要思考状态传递过程中的谬误

毛病:

  • 减少 A 组件保护老本
  • 须要传入额定的 prop 到 B 组件
  • 无奈利用路由定位页面

除此之外,在 Vue 中,还能够是用 keep-alive 来缓存页面,当组件在 keep-alive 内被切换时组件的 activated、deactivated 这两个生命周期钩子函数会被执行
被包裹在 keep-alive 中的组件的状态将会被保留:

<keep-alive>
    <router-view v-if="$route.meta.keepAlive"></router-view>
</kepp-alive>

router.js

{
  path: '/',
  name: 'xxx',
  component: ()=>import('../src/views/xxx.vue'),
  meta:{keepAlive: true // 须要被缓存}
},

Vue-router 除了 router-link 怎么实现跳转

申明式导航

<router-link to="/about">Go to About</router-link>

编程式导航

// literal string path
router.push('/users/1')
​
// object with path
router.push({path: '/users/1'})
​
// named route with params to let the router build the url
router.push({name: 'user', params: { username: 'test'} })

答复范例

  • vue-router导航有两种形式:申明式导航和编程形式导航
  • 申明式导航形式应用 router-link 组件,增加 to 属性导航;编程形式导航更加灵便,可传递调用 router.push(),并传递path 字符串或者 RouteLocationRaw 对象,指定 pathnameparams 等信息
  • 如果页面中简略示意跳转链接,应用 router-link 最快捷,会渲染一个 a 标签;如果页面是个简单的内容,比方商品信息,能够增加点击事件,应用编程式导航
  • 实际上外部两者调用的导航函数是一样的

diff 算法

<details open=””><summary>答案 </summary>
<p>
</p><p> 工夫复杂度: 个树的齐全 diff 算法是一个工夫复杂度为 O(n*3),vue 进行优化转化成 O(n)。</p>
<p> 了解:</p>
<ul>
<li>
<p> 最小量更新, key 很重要。这个能够是这个节点的惟一标识,通知 diff 算法,在更改前后它们是同一个 DOM 节点 </p>
<ul>
<li> 扩大 v-for 为什么要有 key,没有 key 会暴力复用,举例子的话轻易说一个比方挪动节点或者减少节点(批改 DOM),加 key 只会挪动缩小操作 DOM。</li>
</ul>
</li>
<li>
<p> 只有是同一个虚构节点才会进行精细化比拟,否则就是暴力删除旧的,插入新的。</p>
</li>
<li>
<p> 只进行同层比拟,不会进行跨层比拟。</p>
</li>
</ul>
<p>diff 算法的优化策略:四种命中查找,四个指针 </p>
<ol>
<li>
<p> 旧前与新前(先比结尾,后插入和删除节点的这种状况)</p>
</li>
<li>
<p> 旧后与新后(比结尾,前插入或删除的状况)</p>
</li>
<li>
<p> 旧前与新后(头与尾比,此种产生了,波及挪动节点,那么新前指向的节点,挪动到旧后之后)</p>
</li>
<li>
<p> 旧后与新前(尾与头比,此种产生了,波及挪动节点,那么新前指向的节点,挪动到旧前之前)</p>
</li>
</ol>
<p></p>
</details>

— 问完下面这些如果都能很分明的话,根本 O 了 —

以下的这些简略的概念,你必定也是没有问题的啦😉

router-link 和 router-view 是如何起作用的

剖析

vue-router中两个重要组件 router-linkrouter-view,别离起到导航作用和内容渲染作用,然而答复如何失效还真有肯定难度

答复范例

  1. vue-router中两个重要组件 router-linkrouter-view,别离起到路由导航作用和组件内容渲染作用
  2. 应用中 router-link 默认生成一个 a 标签,设置 to 属性定义跳转 path。实际上也能够通过custom 和插槽自定义最终的展示模式。router-view是要显示组件的占位组件,能够嵌套,对应路由配置的嵌套关系,配合 name 能够显示具名组件,起到更强的布局作用。
  3. router-link组件外部依据 custom 属性判断如何渲染最终生成节点,外部提供导航办法 navigate,用户点击之后理论调用的是该办法,此办法最终会批改响应式的路由变量,而后从新去routes 匹配出数组后果,router-view则依据其所处深度 deep 在匹配数组后果中找到对应的路由并获取组件,最终将其渲染进去。

异步组件是什么?应用场景有哪些?

剖析

因为异步路由的存在,咱们应用异步组件的次数比拟少,因而还是有必要两者的不同。

体验

大型利用中,咱们须要宰割利用为更小的块,并且在须要组件时再加载它们

import {defineAsyncComponent} from 'vue'
// defineAsyncComponent 定义异步组件,返回一个包装组件。包装组件依据加载器的状态决定渲染什么内容
const AsyncComp = defineAsyncComponent(() => {
  // 加载函数返回 Promise
  return new Promise((resolve, reject) => {
    // ... 能够从服务器加载组件
    resolve(/* loaded component */)
  })
})
// 借助打包工具实现 ES 模块动静导入
const AsyncComp = defineAsyncComponent(() =>
  import('./components/MyComponent.vue')
)

答复范例

  1. 在大型利用中,咱们须要宰割利用为更小的块,并且在须要组件时再加载它们。
  2. 咱们不仅能够在路由切换时懒加载组件,还能够在页面组件中持续应用异步组件,从而实现更细的宰割粒度。
  3. 应用异步组件最简略的形式是间接给 defineAsyncComponent 指定一个 loader 函数,联合 ES 模块动静导入函数 import 能够疾速实现。咱们甚至能够指定 loadingComponenterrorComponent选项从而给用户一个很好的加载反馈。另外 Vue3 中还能够联合 Suspense 组件应用异步组件。
  4. 异步组件容易和路由懒加载混同,实际上不是一个货色。异步组件不能被用于定义懒加载路由上,解决它的是 vue 框架,解决路由组件加载的是vue-router。然而能够在懒加载的路由组件中应用异步组件

Proxy 与 Object.defineProperty 优劣比照

Proxy 的劣势如下:

  • Proxy 能够间接监听对象而非属性;
  • Proxy 能够间接监听数组的变动;
  • Proxy 有多达 13 种拦挡办法, 不限于 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具备的;
  • Proxy 返回的是一个新对象, 咱们能够只操作新的对象达到目标, 而 Object.defineProperty 只能遍历对象属性间接批改;

Proxy 作为新规范将受到浏览器厂商重点继续的性能优化,也就是传说中的新规范的性能红利;

Object.defineProperty 的劣势如下:

  • 兼容性好,反对 IE9,而 Proxy 的存在浏览器兼容性问题, 而且无奈用 polyfill 磨平,因而 Vue 的作者才申明须要等到下个大版本 (3.0) 能力用 Proxy 重写。

Computed 和 Methods 的区别

能够将同一函数定义为一个 method 或者一个计算属性。对于最终的后果,两种形式是雷同的

不同点:

  • computed: 计算属性是基于它们的依赖进行缓存的,只有在它的相干依赖产生扭转时才会从新求值;
  • method 调用总会执行该函数。

双向绑定的原理是什么

咱们都晓得 Vue 是数据双向绑定的框架,双向绑定由三个重要局部形成

  • 数据层(Model):利用的数据及业务逻辑
  • 视图层(View):利用的展现成果,各类 UI 组件
  • 业务逻辑层(ViewModel):框架封装的外围,它负责将数据与视图关联起来

而下面的这个分层的架构计划,能够用一个专业术语进行称说:MVVM这里的管制层的外围性能便是“数据双向绑定”。天然,咱们只需弄懂它是什么,便能够进一步理解数据绑定的原理

了解 ViewModel

它的主要职责就是:

  • 数据变动后更新视图
  • 视图变动后更新数据

当然,它还有两个次要局部组成

  • 监听器(Observer):对所有数据的属性进行监听
  • 解析器(Compiler):对每个元素节点的指令进行扫描跟解析, 依据指令模板替换数据, 以及绑定相应的更新函数

vue-loader 是什么?它有什么作用?

答复范例

  1. vue-loader是用于解决单文件组件(SFCSingle-File Component)的webpack loader
  2. 因为有了 vue-loader,咱们就能够在我的项目中编写SFC 格局的 Vue 组件,咱们能够把代码宰割为 <template><script><style>,代码会异样清晰。联合其余 loader 咱们还能够用 Pug 编写 <template>,用SASS 编写 <style>,用TS 编写 <script>。咱们的<style> 还能够独自作用以后组件
  3. webpack打包时,会以 loader 的形式调用vue-loader
  4. vue-loader被执行时,它会对 SFC 中的每个语言块用独自的 loader 链解决。最初将这些独自的块装配成最终的组件模块

原理

vue-loader会调用 @vue/compiler-sfc 模块解析 SFC 源码为一个描述符(Descriptor),而后为每个语言块生成 import 代码,返回的代码相似上面

// source.vue 被 vue-loader 解决之后返回的代码
​
// import the <template> block
import render from 'source.vue?vue&type=template'
// import the <script> block
import script from 'source.vue?vue&type=script'
export * from 'source.vue?vue&type=script'
// import <style> blocks
import 'source.vue?vue&type=style&index=1'
​
script.render = render
export default script

咱们想要 script 块中的内容被作为 js 解决(当然如果是 <script lang="ts"> 被作为 ts 理),这样咱们想要 webpack 把配置中跟 .js 匹配的规定都利用到形如 source.vue?vue&type=script 的这个申请上。例如咱们对所有 *.js 配置了babel-loader,这个规定将被克隆并利用到所在Vue SFC

import script from 'source.vue?vue&type=script

将被开展为:

import script from 'babel-loader!vue-loader!source.vue?vue&type=script'

相似的,如果咱们对 .sass 文件配置了style-loader + css-loader + sass-loader,对上面的代码

<style scoped lang="scss">

vue-loader将会返回给咱们上面后果:

import 'source.vue?vue&type=style&index=1&scoped&lang=scss'

而后 webpack 会开展如下:

import 'style-loader!css-loader!sass-loader!vue-loader!source.vue?vue&type=style&index=1&scoped&lang=scss'
  • 当解决开展申请时,vue-loader将被再次调用。这次,loader将会关注那些有查问串的申请,且仅针对特定块,它会选中特定块外部的内容并传递给前面匹配的loader
  • 对于 <script> 块,解决到这就能够了,然而 <template><style> 还有一些额定工作要做,比方

    • 须要用 Vue 模板编译器编译 template,从而失去render 函数
    • 须要对 <style scoped>中的 CSS 做后处理(post-process),该操作在 css-loader 之后但在 style-loader 之前

实现上这些附加的 loader 须要被注入到曾经开展的 loader 链上,最终的申请会像上面这样:

// <template lang="pug">
import 'vue-loader/template-loader!pug-loader!source.vue?vue&type=template'
​
// <style scoped lang="scss">
import 'style-loader!vue-loader/style-post-loader!css-loader!sass-loader!vue-loader!source.vue?vue&type=style&index=1&scoped&lang=scss'

vue-router 路由钩子函数是什么 执行程序是什么

路由钩子的执行流程, 钩子函数品种有:全局守卫 路由守卫 组件守卫

  1. 导航被触发。
  2. 在失活的组件里调用 beforeRouteLeave 守卫。
  3. 调用全局的 beforeEach 守卫。
  4. 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
  5. 在路由配置里调用 beforeEnter
  6. 解析异步路由组件。
  7. 在被激活的组件里调用 beforeRouteEnter
  8. 调用全局的 beforeResolve 守卫 (2.5+)。
  9. 导航被确认。
  10. 调用全局的 afterEach 钩子。
  11. 触发 DOM 更新。
  12. 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创立好的组件实例会作为回调函数的参数传入
退出移动版