IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> 前端面试题--vue -> 正文阅读

[JavaScript知识库]前端面试题--vue

文章目录

1. vue 生命周期

生命周期:Vue 实例从创建到销毁的过程就是生命周期,即指从创建、初始化数据、编译模板、挂载Dom到渲染、更新到渲染、销毁等一系列过程

主要 8个阶段
在这里插入图片描述
在这里插入图片描述

2. VUE 中 computed 和 watch 的区别是什么?

computed计算属性就是为了简化 template 里面模版字符串的计算复杂度、防止模版太过冗余。

它具有 缓存特性 computed 用来监控自己定义的变量,该变量不在 data 里面声明,直接在 computed 里面定义,然后就 可以在页面上进行双向数据绑定展示出结果或者用作其他处理;

watch主要用于监控 vue 实例的变化它监控的变量当然必须在 data 里面声明才可以,它可以监控一个 变量,也可以是一个对象,一般用于监控路由、input 输入框的值特殊处理等等,它比较适合的场景是 一个数据影响多个数据,它不具有缓存性

  • watch:监测的是属性值, 只要属性值发生变化,其都会触发执行回调函数来执行一系列操作。
  • computed:监测的是依赖值,依赖值不变的情况下其会直接读取缓存进行复用,变化的情况下才 会重新计算。

除此之外,有点很重要的区别是:计算属性不能执行异步任务,计算属性必须同步执行。也就是说计算 属性不能向服务器请求或者执行异步任务。如果遇到异步任务,就交给侦听属性。watch 也可以检测 computed 属性。

3. 动态绑定 类名和样式

比如有一个导航栏,里面有很多选项,需要鼠标在谁身上,谁就变色,就可以使用动态绑定的方式来实现
类名的动态绑定
在这里插入图片描述
内联样式的动态绑定
在这里插入图片描述

4. v-if 与 v-show 的区别

  1. v-if false 的元素不渲染—适用与不会经常改变显示和隐藏的元素,因为要创建DOM元素和销毁DOM元素,频繁切换的开销更大

  2. v-show 不管条件是真或假,第一次渲染的时候都会编译出来,也就说标签都会添加到DOM中。为这些元素添加内联样式 display:none 让它们不显示—适用于经常要切换显示隐藏效果的元素,因为只需要操作css样式display来控制元素的隐藏和显示
    在这里插入图片描述
    v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建, 操作的实际上是 dom 元素的创建或销毁。

v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换, 它操作的是display:none/block属性。

一般来说, v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好; 如果在运行时条件很少改变,则使用 v-if 较好。

5. v-for 列表渲染

v-for的优先级比 v-if 高,不要连用,可以将 v-if 移到到父级元素上,这样就可以实现 先判断 v-if 的情况来判断是否渲染列表了
在这里插入图片描述
在这里插入图片描述

6. vue 组件间通信

https://juejin.cn/post/6844903887162310669
方法一、props/$emit

父组件A通过props的方式向子组件B传递,B to A 通过在 B 组件中 $emit, A 组件中 v-on 的方式实现。

1.父组件向子组件传值
接下来我们通过一个例子,说明父组件如何向子组件传递值:在子组件Users.vue中如何获取父组件App.vue中的数据 users:["Henry","Bucky","Emily"]

//App.vue父组件
<template>
  <div id="app">
    <users v-bind:users="users"></users>//前者自定义名称便于子组件调用,后者要传递数据名
  </div>
</template>
<script>
import Users from "./components/Users"
export default {
  name: 'App',
  data(){
    return{
      users:["Henry","Bucky","Emily"]
    }
  },
  components:{
    "users":Users
  }
}
//users子组件
<template>
  <div class="hello">
    <ul>
      <li v-for="user in users">{{user}}</li>//遍历传递过来的值,然后呈现到页面
    </ul>
  </div>
</template>
<script>
export default {
  name: 'HelloWorld',
  props:{
    users:{           //这个就是父组件中子标签自定义名字
      type:Array,
      required:true
    }
  }
}
</script>

总结:父组件通过props向下传递数据给子组件。注:组件中的数据共有三种形式:data、props、computed

2.子组件向父组件传值(通过事件形式)
接下来我们通过一个例子,说明子组件如何向父组件传递值:当我们点击“Vue.js Demo”后,子组件向父组件传递值,文字由原来的“传递的是一个值”变成“子向父组件传值”,实现子组件向父组件值的传递。
在这里插入图片描述

// 子组件
<template>
  <header>
    <h1 @click="changeTitle">{{title}}</h1>//绑定一个点击事件
  </header>
</template>
<script>
export default {
  name: 'app-header',
  data() {
    return {
      title:"Vue.js Demo"
    }
  },
  methods:{
    changeTitle() {
      this.$emit("titleChanged","子向父组件传值");//自定义事件  传递值“子向父组件传值”
    }
  }
}
</script>

// 父组件
<template>
  <div id="app">
    <app-header v-on:titleChanged="updateTitle" ></app-header>//与子组件titleChanged自定义事件保持一致
   // updateTitle($event)接受传递过来的文字
    <h2>{{title}}</h2>
  </div>
</template>
<script>
import Header from "./components/Header"
export default {
  name: 'App',
  data(){
    return{
      title:"传递的是一个值"
    }
  },
  methods:{
    updateTitle(e){   //声明这个函数
      this.title = e;
    }
  },
  components:{
   "app-header":Header,
  }
}
</script>

总结:子组件通过events给父组件发送消息,实际上就是子组件把自己的数据发送到父组件。

方法二、$ emit / $ on
这种方法通过一个空的Vue实例作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、跨级。当我们的项目比较大时,可以选择更好的状态管理解决方案vuex。

1.具体实现方式:

  var Event=new Vue();
    Event.$emit(事件名,数据);
    Event.$on(事件名,data => {});

2.举个例子
假设兄弟组件有三个,分别是A、B、C组件,C组件如何获取A或者B组件的数据

<div id="itany">
	<my-a></my-a>
	<my-b></my-b>
	<my-c></my-c>
</div>
<template id="a">
  <div>
    <h3>A组件:{{name}}</h3>
    <button @click="send">将数据发送给C组件</button>
  </div>
</template>
<template id="b">
  <div>
    <h3>B组件:{{age}}</h3>
    <button @click="send">将数组发送给C组件</button>
  </div>
</template>
<template id="c">
  <div>
    <h3>C组件:{{name}}{{age}}</h3>
  </div>
</template>
<script>
var Event = new Vue();//定义一个空的Vue实例
var A = {
	template: '#a',
	data() {
	  return {
	    name: 'tom'
	  }
	},
	methods: {
	  send() {
	    Event.$emit('data-a', this.name);
	  }
	}
}
var B = {
	template: '#b',
	data() {
	  return {
	    age: 20
	  }
	},
	methods: {
	  send() {
	    Event.$emit('data-b', this.age);
	  }
	}
}
var C = {
	template: '#c',
	data() {
	  return {
	    name: '',
	    age: ""
	  }
	},
	mounted() {//在模板编译完成后执行
	 Event.$on('data-a',name => {
	     this.name = name;//箭头函数内部不会产生新的this,这边如果不用=>,this指代Event
	 })
	 Event.$on('data-b',age => {
	     this.age = age;
	 })
	}
}
var vm = new Vue({
	el: '#itany',
	components: {
	  'my-a': A,
	  'my-b': B,
	  'my-c': C
	}
});	
</script>

方法三、vuex
在这里插入图片描述
1.简要介绍Vuex原理:
Vuex实现了一个单向数据流,在全局拥有一个State存放数据,当组件要更改State中的数据时,必须通过Mutation进行,Mutation同时提供了订阅者模式供外部插件调用获取State数据的更新。而当所有异步操作(常见于调用后端接口异步获取更新数据)或批量的同步操作需要走Action,但Action也是无法直接修改State的,还是需要通过Mutation来修改State的数据。最后,根据State的变化,渲染到视图上。

2.简要介绍各模块在流程中的功能:

  • Vue Components:Vue组件。HTML页面上,负责接收用户操作等交互行为,执行dispatch方法触发对应action进行回应。
  • dispatch:操作行为触发方法,是唯一能执行action的方法。
  • actions:操作行为处理模块,由组件中的$store.dispatch(‘action 名称’, data1)来触发。然后由commit()来触发mutation的调用 , 间接更新 state。负责处理Vue Components接收到的所有交互行为。包含同步/异步操作,支持多个同名方法,按照注册的顺序依次触发。向后台API请求的操作就在这个模块中进行,包括触发其他action以及提交mutation的操作。该模块提供了Promise的封装,以支持action的链式触发。
  • commit:状态改变提交操作方法。对mutation进行提交,是唯一能执行mutation的方法。
  • mutations:状态改变操作方法,由actions中的commit(‘mutation 名称’)来触发。是Vuex修改state的唯一推荐方法。该方法只能进行同步操作,且方法名只能全局唯一。操作之中会有一些hook暴露出来,以进行state的监控等。
  • state:页面状态管理容器对象。集中存储Vue components中data对象的零散数据,全局唯一,以进行统一的状态管理。页面显示所需的数据从该对象中进行读取,利用Vue的细粒度数据响应机制来进行高效的状态更新。
  • getters:state对象读取方法。图中没有单独列出该模块,应该被包含在了render中,Vue Components通过该方法读取全局state对象。

3.Vuex与localStorage
vuex 是 vue 的状态管理器,存储的数据是响应式的。但是并不会保存起来,刷新之后就回到了初始状态,具体做法应该在vuex里数据改变的时候把数据拷贝一份保存到localStorage里面,刷新之后,如果localStorage里有保存的数据,取出来再替换store里的state。

let defaultCity = "上海"
try {   // 用户关闭了本地存储功能,此时在外层加个try...catch
  if (!defaultCity){
    defaultCity = JSON.parse(window.localStorage.getItem('defaultCity'))
  }
}catch(e){}
export default new Vuex.Store({
  state: {
    city: defaultCity
  },
  mutations: {
    changeCity(state, city) {
      state.city = city
      try {
      window.localStorage.setItem('defaultCity', JSON.stringify(state.city));
      // 数据改变的时候把数据拷贝一份保存到localStorage里面
      } catch (e) {}
    }
  }
})

作者:浪里行舟
链接:https://juejin.cn/post/6844903845642911752
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

这里需要注意的是:由于vuex里,我们保存的状态,都是数组,而localStorage只支持字符串,所以需要用JSON转换:

JSON.stringify(state.subscribeList);   // array -> string
JSON.parse(window.localStorage.getItem("subscribeList"));    // string -> array 

方法四、$attrs/$listeners

1.简介

多级组件嵌套需要传递数据时,通常使用的方法是通过vuex。但如果仅仅是传递数据,而不做中间处理,使用 vuex 处理,未免有点大材小用。为此Vue2.4 版本提供了另一种方法----$attrs/$listeners

  • $attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件。通常配合 inheritAttrs 选项一起使用。
  • $listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件

接下来我们看个跨级通信的例子:

// index.vue
<template>
  <div>
    <h2>浪里行舟</h2>
    <child-com1
      :foo="foo"
      :boo="boo"
      :coo="coo"
      :doo="doo"
      title="前端工匠"
    ></child-com1>
  </div>
</template>
<script>
const childCom1 = () => import("./childCom1.vue");
export default {
  components: { childCom1 },
  data() {
    return {
      foo: "Javascript",
      boo: "Html",
      coo: "CSS",
      doo: "Vue"
    };
  }
};
</script>
// childCom1.vue
<template class="border">
  <div>
    <p>foo: {{ foo }}</p>
    <p>childCom1的$attrs: {{ $attrs }}</p>
    <child-com2 v-bind="$attrs"></child-com2>
  </div>
</template>
<script>
const childCom2 = () => import("./childCom2.vue");
export default {
  components: {
    childCom2
  },
  inheritAttrs: false, // 可以关闭自动挂载到组件根元素上的没有在props声明的属性
  props: {
    foo: String // foo作为props属性绑定
  },
  created() {
    console.log(this.$attrs); // { "boo": "Html", "coo": "CSS", "doo": "Vue", "title": "前端工匠" }
  }
};
</script>
// childCom2.vue
<template>
  <div class="border">
    <p>boo: {{ boo }}</p>
    <p>childCom2: {{ $attrs }}</p>
    <child-com3 v-bind="$attrs"></child-com3>
  </div>
</template>
<script>
const childCom3 = () => import("./childCom3.vue");
export default {
  components: {
    childCom3
  },
  inheritAttrs: false,
  props: {
    boo: String
  },
  created() {
    console.log(this.$attrs); // { "coo": "CSS", "doo": "Vue", "title": "前端工匠" }
  }
};
</script>
// childCom3.vue
<template>
  <div class="border">
    <p>childCom3: {{ $attrs }}</p>
  </div>
</template>
<script>
export default {
  props: {
    coo: String,
    title: String
  }
};
</script>

在这里插入图片描述
如上图所示$attrs表示没有继承数据的对象,格式为{属性名:属性值}。Vue2.4提供了$attrs , $listeners 来传递数据与事件,跨级组件之间的通讯变得更简单。
简单来说:$attrs$listeners 是两个对象,$attrs 里存放的是父组件中绑定的非 Props 属性,$listeners里存放的是父组件中绑定的非原生事件。

方法五、provide/inject

1.简介

Vue2.2.0新增API,这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。一言而蔽之:祖先组件中通过provider来提供变量,然后在子孙组件中通过inject来注入变量。
provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。

2.举个例子

假设有两个组件: A.vue 和 B.vue,B 是 A 的子组件

// A.vue
export default {
  provide: {
    name: '浪里行舟'
  }
}
// B.vue
export default {
  inject: ['name'],
  mounted () {
    console.log(this.name);  // 浪里行舟
  }
}

可以看到,在 A.vue 里,我们设置了一个 provide: name,值为 浪里行舟,它的作用就是将 name 这个变量提供给它的所有子组件。而在 B.vue 中,通过 inject 注入了从 A 组件中提供的 name 变量,那么在组件 B 中,就可以直接通过 this.name 访问这个变量了,它的值也是 浪里行舟。这就是 provide / inject API 最核心的用法。
需要注意的是:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的----vue官方文档
所以,上面 A.vue 的 name 如果改变了,B.vue 的 this.name 是不会改变的,仍然是 浪里行舟。

3.provide与inject 怎么实现数据响应式

一般来说,有两种办法:

provide祖先组件的实例,然后在子孙组件中注入依赖,这样就可以在子孙组件中直接修改祖先组件的实例的属性,不过这种方法有个缺点就是这个实例上挂载很多没有必要的东西比如props,methods
使用2.6最新API Vue.observable 优化响应式 provide(推荐)

我们来看个例子:孙组件D、E和F获取A组件传递过来的color值,并能实现数据响应式变化,即A组件的color变化后,组件D、E、F会跟着变(核心代码如下:)
在这里插入图片描述

// A 组件 
<div>
      <h1>A 组件</h1>
      <button @click="() => changeColor()">改变color</button>
      <ChildrenB />
      <ChildrenC />
</div>
......
  data() {
    return {
      color: "blue"
    };
  },
  // provide() {
  //   return {
  //     theme: {
  //       color: this.color //这种方式绑定的数据并不是可响应的
  //     } // 即A组件的color变化后,组件D、E、F不会跟着变
  //   };
  // },
  provide() {
    return {
      theme: this//方法一:提供祖先组件的实例
    };
  },
  methods: {
    changeColor(color) {
      if (color) {
        this.color = color;
      } else {
        this.color = this.color === "blue" ? "red" : "blue";
      }
    }
  }
  // 方法二:使用2.6最新API Vue.observable 优化响应式 provide
  // provide() {
  //   this.theme = Vue.observable({
  //     color: "blue"
  //   });
  //   return {
  //     theme: this.theme
  //   };
  // },
  // methods: {
  //   changeColor(color) {
  //     if (color) {
  //       this.theme.color = color;
  //     } else {
  //       this.theme.color = this.theme.color === "blue" ? "red" : "blue";
  //     }
  //   }
  // }

// F 组件 
<template functional>
  <div class="border2">
    <h3 :style="{ color: injections.theme.color }">F 组件</h3>
  </div>
</template>
<script>
export default {
  inject: {
    theme: {
      //函数式组件取值不一样
      default: () => ({})
    }
  }
};
</script>

方法六、$parent / $children与 ref

  • ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
  • $parent / $children:访问父 / 子实例
    需要注意的是:这两种都是直接得到组件实例,使用后可以直接调用组件的方法或访问数据。我们先来看个用 ref来访问组件的例子:
// component-a 子组件
export default {
  data () {
    return {
      title: 'Vue.js'
    }
  },
  methods: {
    sayHello () {
      window.alert('Hello');
    }
  }
}
// 父组件
<template>
  <component-a ref="comA"></component-a>
</template>
<script>
  export default {
    mounted () {
      const comA = this.$refs.comA;
      console.log(comA.title);  // Vue.js
      comA.sayHello();  // 弹窗
    }
  }
</script>

不过,这两种方法的弊端是,无法在跨级或兄弟间通信。

// parent.vue
<component-a></component-a>
<component-b></component-b>
<component-b></component-b>

我们想在 component-a 中,访问到引用它的页面中(这里就是 parent.vue)的两个 component-b 组件,那这种情况下,就得配置额外的插件或工具了,比如 Vuex 和 Bus 的解决方案。

总结
常见使用场景可以分为三类:

  • 父子通信:父向子传递数据是通过 props,子向父是通过 events($emit);通过父链 / 子链也可以通信($parent / $children);ref 也可以访问组件实例;provide / inject API$attrs/$listeners
  • 兄弟通信:Bus;Vuex
  • 跨级通信:Bus;Vuex;provide / inject API、$attrs/$listeners

7. 父子组件生命周期执行顺序

在这里插入图片描述
父组件 先创建,然后再船舰子组件,子组件先挂载,父组件后挂载。

8. $nextTick

作用:是为了可以获取更新后的DOM

由于Vue DOM更新是异步执行的,即修改数据时,视图不会立即更新,而是会监听数据变化,并缓存在同一事件循环中,等同一数据循环中的所有数据变化完成之后,再统一进行视图更新。为了确保得到更新后的DOM,所以设置了 Vue.nextTick(),就是在下次DOM更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的DOM。

原理:

在下次 DOM 更新循环结束之后执行延迟回调。nextTick主要使用了宏任务和微任务。根据执行环境分别尝试采用

  • Promise
  • MutationObserver
  • setImmediate
  • 如果以上都不行则采用setTimeout

定义了一个异步方法,多次调用nextTick会将方法存入队列中,通过这个异步方法清空当前队列。
在这里插入图片描述
$nextTick 这个api会在dom异步渲染完毕之后执行,就可以获取到数据改变之后的数值了。

9. 插槽 slot

作用:1、父组件向子组件传递内容,slot更像一个"出口"
2、 拓展、复用、定制组件
类型:
1、默认插槽(匿名插槽)
2、 具名插槽
3、 作用域插槽
1、 匿名插槽
在子组件创建一个 slot
在这里插入图片描述
在父组件导入子组件,则可将子组件标签中写的html插入到子组件的slot标签位置
在这里插入图片描述
2. 具名插槽
可以在子组件中为插槽命名,未命名的就是默认插槽
在这里插入图片描述
就可以在父组件中根据不同名字的插槽,传递不同的内容
在这里插入图片描述

3、作用域插槽
在子组件的 slot标签中插入的内容是默认内容,如果父组件调用子组件的时候没有插入内容,则默认显示这些内容。若父组件在调用组件时传入了内容,则显示传入的内容。
在这里插入图片描述
在这里插入图片描述

若希望父组件可以显示子组件data里面的数据呢?

  1. 子组件绑定user将user数据传到父组件中的子组件标签上,将数据传递给插槽
    在这里插入图片描述
    子组件 slot 传给默认插槽的数据 ,使用一个变量来接收,就可以访问到子组件的user变量
    在这里插入图片描述
    https://www.jianshu.com/p/0c9516a3be80

10. 动态组件

https://blog.csdn.net/weixin_43974265/article/details/113103437

11. 异步组件

其实test组件一开始是不显示的,若在一开始就将 Test 组件导入,则vue会将代码在一开始就加载进去,导致在首次渲染的时候加载不必要的代码,严重影响性能
采用异步组件,只有在需要的时候才去下载相关代码,可以提升性能
在这里插入图片描述

12. keep-alive

keep-alivekeep-alive可以实现组件缓存,是Vue.js的一个内置组件。

作用:

  1. 它能够把不活动的组件实例保存在内存中,而不是直接将其销毁
  2. 它是一个抽象组件,不会被渲染到真实DOM中,也不会出现在父组件链中

若不使用keep-alive 则组件间切换时,每次都需要重新渲染和销毁
在这里插入图片描述
而使用了keep-alive以后, 使在标签中的组件被缓存起来,而不用频繁销毁渲染
在这里插入图片描述
也可以使用v-show来实现组件之间的切换
但是,v-show的方式是首次渲染的时候,一次性就把3个组件一次性的渲染上去,如果组件的结构简单的话,可以使用v-show ,如果组件代码比较多,结构复杂的话应该用keep-alive,保证同时只会渲染一个组件,提升性能。

13. 使用 mixin 抽离组件公共逻辑

将组件的公共逻辑或者配置提取出来,哪个组件需要用到时,直接将提取的这部分混入到组件内部即可。这样既可以减少代码冗余度,也可以让后期维护起来更加容易。

这里需要注意的是:提取的是逻辑或配置,而不是HTML代码和CSS代码。其实大家也可以换一种想法,mixin就是组件中的组件,Vue组件化让我们的代码复用性更高,那么组件与组件之间还有重复部分,我们使用Mixin在抽离一遍。

使用 mixin 可以让代码维护起来更加方便

优点

  • 提高代码复用性
  • 无需传递状态
  • 维护方便,只需要修改一个地方即可

缺点

  • 命名冲突
  • 滥用的话后期很难维护
  • 不好追溯源,排查问题稍显麻烦
  • 不能轻易的重复代码
  • 在这里插入图片描述
    若组件A 与 组件B 中有公共的数据,则可以使用 mixin 抽离组件公共逻辑

将公共逻辑写道mixin.js 文件中,在需要使用的组件将其按下图二的方式导入即可
在这里插入图片描述
在这里插入图片描述

14. vue2 与 vue3 生命周期的区别

在这里插入图片描述

15. vue2和vue3 的区别

https://juejin.cn/post/7067413380922867725添加链接描述

16. v-for 循环为什么一定要绑定 key ?

页面上的标签都对应具体的虚拟 dom 对象(虚拟 dom 就是 js 对象), 循环中 ,如果没有唯一 key , 页面上删除 一条标签, 由于并不知道删除的是哪一条! 所以要把全部虚拟 dom 重新渲染, 如果知道 key 为 x 标签被删除 掉, 只需要把渲染的 dom 为 x 的标签去掉即可!

为了高效的更新虚拟DOM

需要使用key来给每个节点做一个唯一标识,Diff算法就可以正确的识别此节点。主要是为了高效的更新虚拟DOM。

解析:

vue和react的虚拟DOM的Diff算法大致相同,其核心是基于两个简单的假设
首先讲一下diff算法的处理方法,对操作前后的dom树同一层的节点进行对比,一层一层对比,

img

当某一层有很多相同的节点时,也就是列表节点时,Diff算法的更新过程默认情况下也是遵循以上原则。
比如一下这个情况:

img

我们希望可以在B和C之间加一个F,Diff算法默认执行起来是这样的:

img

即把C更新成F,D更新成C,E更新成D,最后再插入E,是不是很没有效率?

所以我们需要使用key来给每个节点做一个唯一标识,Diff算法就可以正确的识别此节点,找到正确的位置区插入新的节点。

img

16. 组件中的 data 为什么要定义成一个函数而不是一个对象?

每个组件都是 Vue 的实例。组件共享 data 属性,当 data 的值是同一个引用类型的值时,改变其中一 个会影响其他.

17. 谈谈你平时都用了哪些方法进行性能优化?

减少 http 请求次数、打包压缩上线代码、使用懒加载、使用雪碧图、动态渲染组件、CDN 加载包。在频繁切换状态的地方,比如我项目中的音乐播放页,采用v-show,而不是v-if,为点击事件使用节流操作,保证在一段事件内点击事件只触发一次。

18. vue 实例是挂载到哪个标签上的

vue 实例最后会挂载在body 标签里面,所以我们在 vue 中是获取不了 body 标签的,如果要使用 body 标 签的话需要用原生的方式获取

19. webpack/babel

WebPack 是一个模块打包工具,你可以使用 WebPack 管理你的模块依赖,并编绎输出模块们所需的静 态文件。它能够很好地管理、打包 Web 开发中所用到的 HTML、Javascript、CSS 以及各种静态文件(图 片、字体等),让开发过程更加高效。对于不同类型的资源,webpack 有对应的模块加载器。

webpack 模块打包器会分析模块间的依赖关系,最后 生成了优化且合并后的静态资源 babel可以帮助我们转换一些当前浏览器不支持的语法,它会把这些语法转换为低版本的语法以便浏览 器识别。

20. 什么 vuex ,谈谈你对它的理解?

vuex是一个专为 Vue.js 应用程序开发的状态管理模式, 采用集中式存储管理应用的所有组件的状态,解决多组件数据通信。(简单来说就是管理数据的,相当于一个仓库,里面存放着各种需要共享的数据,所有组件都可以拿到里面的数据)

https://juejin.cn/post/7013325675129995272

  1. 首先 vuex 的出现是为了解决 web 组件化开发的过程中,各组件之间传值的复杂和混乱的问题

  2. 将我们在多个组件中需要共享的数据放到 store 中,

  3. 要获取或格式化数据需要使用 getters,

  4. 改变 store 中的数据,使用 mutation,但是只能包含同步的操作,在具体组件里面调用的方式 this.$store.commit(‘xxxx’)

  5. Action 也是改变 store 中的数据,不过是提交的 mutation,并且可以包含异步操作, 在组件中的调 用方式 this.$store.dispatch(‘xxx’) ; 在 actions 里面使用的 commit(‘调用 mutation’)

21. v-model 双向绑定

v-model只不过是一个语法糖而已,真正的实现靠的还是

  1. v-bind:绑定响应式数据
  2. 触发oninput 事件并传递数据
<input v-model="sth" />
<!-- 等同于-->
<input :value="sth" @input="sth = $event.target.value" />
<!--自html5开始,input每次输入都会触发oninput事件,所以输入时input的内容会绑定到sth中,于是sth的值就被改变-->
<!--$event 指代当前触发的事件对象;-->
<!--$event.target 指代当前触发的事件对象的dom;-->
<!--$event.target.value 就是当前dom的value值;-->
<!--在@input方法中,value => sth;-->
<!--在:value中,sth => value;-->

22. vue2 实现双向绑定的原理

vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属

性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

23. Vue3.0 实现数据双向绑定的方法

Vue.js 3.0的一些新特性,其中一个很重要的改变就是Vue3 将使用 ES6的Proxy 作为其观察者机制,取代之前使用的Object.defineProperty。

一、为什么要取代Object.defineProperty
总结来说Object.defineProperty方法存在一定的局限性

1、在Vue中,Object.defineProperty无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实时响应。为了解决这个问题,经过vue内部处理后可以使用以下几种方法来监听数组( Vue为什么不能检测数组变动)

push()
pop()
shift()
unshift()
splice()
sort()
reverse()

2、Object.defineProperty只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。Vue里,是通过递归以及遍历data对象来实现对数据的监控的,如果属性值也是对象那么需要深度遍历,显然如果能劫持一个完整的对象,不管是对操作性还是性能都会有一个很大的提升。

二、什么是Proxy

Proxy 是ES6中新增的一个特性,可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
https://www.jianshu.com/p/68c3e8f2e785

24. 路由守卫

导航守卫主要用来通过跳转或取消的方式守卫导航

简单的说,导航守卫就是路由跳转过程中的一些钩子函数。路由跳转是一个大的过程,这个大的过程分为跳转前中后等等细小的过程,在每一个过程中都有一函数,这个函数能让你操作一些其他的事儿的时机,这就是导航守卫。

路由守卫的具体方法:

  1. 全局前置守卫

    你可以使用 router.beforeEach 注册一个全局前置守卫:

    const router = new VueRouter({ ... })
    router.beforeEach((to, from, next) => {
      // ...
    })
    

    当一个导航开始时,全局前置守卫按照注册顺序调用。守卫是异步链式调用的,导航在最后的一层当中。

    new Promise((resolve, reject) => {
        resolve('第一个全局前置守卫')
    }.then(() => {
        return '第二个全局前置守卫'
    }.then(() => {
        ...
    }.then(() => {
        console.log('导航终于开始了') // 导航在最后一层中
    })
    

每个守卫方法接收三个参数(往后的守卫都大同小异):

  1. to: Route: 即将要进入的目标 路由对象

  2. from: Route: 当前导航正要离开的路由

  3. next: Function: 一定要调用该方法将控制权交给下一个守卫,执行效果依赖 next 方法的参数。

next(): 进入下一个守卫。如果全部守卫执行完了。则导航的状态就是 confirmed (确认的)。

next(false): 中断当前的导航(把小明腿打断了)。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器 后退按钮),那么 URL 地址会重置到 from 路由对应的地址。

next(‘/’) 或者 next({ path: ‘/’ }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航(小明 被打断腿并且送回家了)。你可以向 next 传递任意位置对象,且允许设置诸如 replace: true、name: ‘home’ 之类的选项以及任何用在 router-link 的 to prop 或 router.push 中的选项。

next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给router.

onError() 注册过的回调。

注意:永远不要使用两次next,这会产生一些误会。

  1. 全局解析守卫

    这和 router.beforeEach 类似,但他总是被放在最后一个执行。

  2. 全局后置钩子

    导航已经确认了的,小明已经到了外婆家了,你打断他的腿他也是在外婆家了。

    router.afterEach((to, from) => {
        // 你并不能调用next
      // ...
    })
    
  3. 路由独享的守卫

    在路由内写的守卫

    const router = new VueRouter({
      routes: [
        {
          path: '/foo',
          component: Foo,
          beforeEnter: (to, from, next) => {
            // ...
          }
        }
      ]
    })
    
  4. 组件内的守卫

    5.1 beforeRouteEnter

    5.2 beforeRouteUpdate (2.2 新增)

    5.3 beforeRouteLeave

    const Foo = {
      template: `...`,
      beforeRouteEnter (to, from, next) {
        // 路由被 confirm 前调用
        // 组件还未渲染出来,不能获取组件实例 `this`
      },
      beforeRouteUpdate (to, from, next) {
        // 在当前路由改变,但是该组件被复用时调用
        // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
        // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
        // 可以访问组件实例 `this`,一般用来数据获取。
      },
      beforeRouteLeave (to, from, next) {
        // 导航离开该组件的对应路由时调用
        // 可以访问组件实例 `this`
      }
    }
    

扩展:

导航全过程

  • 导航被触发。
  • 在准备离开的组件里调用 beforeRouteLeave 守卫。
  • 调用全局的 beforeEach 守卫。
  • 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。(如果你的组件是重用的)
  • 在路由配置里调用 beforeEnter。
  • 解析即将抵达的组件。
  • 在即将抵达的组件里调用 beforeRouteEnter。
  • 调用全局的 beforeResolve 守卫 (2.5+)。
  • 导航被确认。
  • 调用全局的 afterEach 钩子。
  • 触发 DOM 更新。
  • 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。

25. vue-router 实现懒加载

什么是路由懒加载?

  • 也叫延迟加载,即在需要的时候进行加载,随用随载。
  • 1:当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。
    2:如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。

为什么需要懒加载?

  • 1:像vue这种单页面应用,如果没有应用懒加载,运用webpack打包后的文件将会异常的大。
    2:造成进入首页时,需要加载的内容过多,时间过长,会出现长时间的白屏,即使做了loading也是不利于用户体验。
    3:而运用懒加载则可以将页面进行划分,需要的时候加载页面,可以有效的分担首页所承担的加载压力,减少首页加载用时

路由懒加载做了什么事情?

  • 1:主要作用是将路由对应的组件打包成一个个的js代码块
    2:只有在这个路由被访问到的时候,才加载对应的组件,否则不加载!

如何实现路由懒加载

  • vue项目实现路由按需加载(路由懒加载)的三种方式:
    1:Vue异步组件
    2:ES6标准语法import()---------推荐使用!!!!!
    3:webpack的require,ensure()

  • Vue异步加载技术

{
  path: '/problem',
  name: 'problem',
  component: resolve => require(['../pages/home/problemList'], resolve)
}
  • ES6推荐方式imprort ()----推荐使用
 	1:直接将组件引入的方式,importES6的一个语法标准,如果需要浏览器兼容,需要转化成es5的语法。
 	2:推荐使用这种方式,但是注意wepack的版本>2.4
 	3:vue官方文档中使用的也是import实现路由懒加载
 	4:上面声明导入,下面直接使用

import Vue from 'vue';
import Router from 'vue-router';
// 官网可知:下面没有指定webpackChunkName,每个组件打包成一个js文件。
const Foo = () => import('../components/Foo')
const Aoo = () => import('../components/Aoo')
// 下面2行代码,指定了相同的webpackChunkName,会合并打包成一个js文件。
// const Foo = () => import(/* webpackChunkName: 'ImportFuncDemo' */ '../components/Foo')
// const Aoo = () => import(/* webpackChunkName: 'ImportFuncDemo' */ '../components/Aoo')
export default new Router({
    routes: [
        {
            path: '/Foo',
            name: 'Foo',
            component: Foo
        },
        {
            path: '/Aoo',
            name: 'Aoo',
            component: Aoo
        }
    ]
})
  • webpack提供的require.ensure()实现懒加载:
 	1:vue-router配置路由,使用webpack的require.ensure技术,也可以实现按需加载。
 	2:这种情况下,多个路由指定相同的chunkName,会合并打包成一个js文件。
 	3:require.ensure可实现按需加载资源,包括js,css等。他会给里面require的文件单独打包,不会和主文件打包在一起。
 	4:第一个参数是数组,表明第二个参数里需要依赖的模块,这些会提前加载。
	5:第二个是回调函数,在这个回调函数里面require的文件会被单独打包成一个chunk,不会和主文件打包在一起,这样就生成了两个chunk,第一次加载时只加载主文件。
	6:第三个参数是错误回调。
	7:第四个参数是单独打包的chunk的文件名
import Vue from 'vue';
import Router from 'vue-router';
const HelloWorld=resolve=>{
		require.ensure(['@/components/HelloWorld'],()=>{
			resolve(require('@/components/HelloWorld'))
		})
	}
Vue.use('Router')
export default new Router({
	routes:[{
	{path:'./',
	name:'HelloWorld',
	component:HelloWorld
	}
	}]
})

import和require的比较(了解)

  • 1:import 是解构过程并且是编译时执行
    2:require 是赋值过程并且是运行时才执行,也就是异步加载
    3:require的性能相对于import稍低,因为require是在运行时才引入模块并且还赋值给某个变量

26. HashRouter 和 HistoryRouter的区别和原理

vue-router是Vue官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。vue-router 默认 hash 模式,还有一种是history模式。
1.hash模式

  • hash模式:www.baidu.com/#hashhash
  • hash指的就是url的#及后面的字符,如上面的“#hashhash”
  • hash模式的特点;
    • hash值的变化不会导致浏览器向服务器发送请求,不会引起页面刷新 hash值变化会触发hashchange事件
    • hash值变化会在浏览器的历史中留下记录,使用的浏览器的后退按钮可以回到上一个hash值
    • hash永远不会提交到服务器,即使刷新页面也不会。

由以上特点可见,hash模式完全满足前端路由的需求,因此在h5的history模式出现之前,基本都是使用hash模式实现前端路由。

优缺点:
优点:

  • 兼容性好,支持低版本和IE浏览器
  • 实现前端路由无需服务端支持

缺点

  • url带有#符号,略丑

2.history模式
在HTML5之前,浏览器就有history对象了,只能用于多页面之间的跳转

history.go(n) // n>0前进n页;n<0后退n页
history.forward() // 前进一页
history.back() // 后退一页

在HTML5规范中,history中增加了新的API:

/*
  参数说明:
    state:合法的JavaScript对象,可以用在popstate对象中
    title:标题,基本忽略,用null
    url: 任意有效的url,将要跳转的新地址
*/
history.pushState(state, title, url) // 保留现有记录的同时,将url加到历史记录中
history.replaceState(state, title, url) // 将历史记录中的当前页面替换成url
history.state // 返回当前状态对象

pushState和replaceState方法可以改变url,但是不会刷新页面,浏览器不会向服务端发送请求,具备了实现前端路由的能力。

如何监听url的变化?

  • 对比hash的hashchange方法,history的变化不会触发任何事件,我们可以通过罗列可能触发history变化的情况,对这些情况进行拦截,以此监听history的变化。
  • 对于单页面的history模式而言,url的改变只能由以下情况引起:
    1.点击浏览器的前进/后退按钮,onpopstate可以监听到
    2.点击a标签
    3.在JS代码中触发history.pushState()或history.replaceState()
    history模式的url发生变化时不会立即向服务器发起请求,刷新会立即请 求。

3.两种模式的区别

  • 外观:hash的url有个#符号,history没有,history外观更好看。
  • 刷新:hash刷新会加载到地址栏对应的页面,history刷新浏览器会重新发起请求,如果服务端没有匹配当前的url,就会出现404页面。
  • 兼容性:hash能兼容到IE8,history只能兼容到IE10。
  • 服务端支持:hash(#及后面的内容)的变化不会导致浏览器向服务器发起请求;history刷新会重新向服务器发起请求,需要在服务端做处理:如果没有匹配到资源,就返回同一个html页面。
  • 原理:hash通过监听浏览器的onhashchange()事件,查找对应的路由规则;history利用H5新增的pushState()和replaceState()方法改变url。
  • 记录:hash模式只有#后面的内容被修改才会添加新的记录栈;history通过pushState()设置的url于当前url一模一样也会被记录到历史记录栈。

4.使用场景(如何选择)
一般情况下,vue-router前端路由使用hash和history模式都可以。

  • 如果追求外观,history更合适。

  • 对比hash,history有以下优势:

    • pushState()设置的url可以是与当前url同源的url;而hash只能改变#后面的内容,只能设置于当前url同文档的url。
    • pushState()设置的url与当前url一模一样也会被添加到历史记录栈;hash必须#后面的内容被更新才会记录。
    • pushState()可以通过stateObject参数添加任意类型的数据到记录中,而hash只能添加短字符串。
      pushState()可以额外设置title属性供后续使用。
  • history模式需要后端配合。

  • hash兼容性更好。

27. hash 和 history 哪个模式不会请求服务器

Vue router 的两种方法,hash模式不会请求服务器

解析:

  1. url的hash,就是通常所说的锚点#,javascript通过hashChange事件来监听url的变化,IE7以下需要轮询。比如这个 URL:http://www.abc.com/#/hello,hash 的值为 #/hello。它的特点在于:hash 虽然出现在 URL 中,但不会被包括在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面
  2. HTML5的History模式,它使url看起来像普通网站那样,以“/”分割,没有#,单页面并没有跳转。不过使用这种模式需要服务端支持,服务端在接收到所有请求后,都指向同一个html文件,不然会出现404。因此单页面应用只有一个html,整个网站的内容都在这一个html里,通过js来处理。

28. vue生命周期中异步加载在mouted还是created里实现

不知道,网上两种说法都有,大佬可以帮我解答一下

说法一:
最常用的是在 created 钩子函数中调用异步请求

解析:

一般来说,可以在,created,mounted中都可以发送数据请求,但是,大部分时候,会在created发送请求。
Created的使用场景:如果页面首次渲染的就来自后端数据。因为,此时data已经挂载到vue实例了。
在 created(如果希望首次选的数据来自于后端,就在此处发请求)(只发了异步请求,渲染是在后端响应之后才进行的)、beforeMount、mounted(在mounted中发请求会进行二次渲染) 这三个钩子函数中进行调用。
因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。但是最常用的是在 created 钩子函数中调用异步请求,因为在 created 钩子函数中调用异步请求有两个优点:
第一点:能更快获取到服务端数据,减少页面 loading 时间;
第二点:放在 created 中有助于一致性,因为ssr(服务端渲染) 不支持 beforeMount 、mounted 钩子函数。

说法二:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W1vk25tg-1665735460770)(img/image-20221013154136818.png)]

29. 为什么选择用vue做页面展示

  • MVVM 框架:

    Vue 正是使用了这种 MVVM 的框架形式,并且通过声明式渲染和响应式数据绑定的方式来帮助我们完全避免了对 DOM 的操作。

  • 单页面应用程序

    Vue 配合生态圈中的 Vue-Router 就可以非常方便的开发复杂的单页应用

  • 轻量化与易学习

    Vue 的生产版本只有 30.90KB 的大小,几乎不会对我们的网页加载速度产生影响。同时因为 Vue 只专注于视图层,单独的 Vue 就像一个库一样,所以使我们的学习成本变得非常低

  • 渐进式与兼容性

    Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。Vue 只做界面,而把其他的一切都交给了它的周边生态(axios(Vue 官方推荐)、Loadsh.js、Velocity.js 等)来做处理,这就要求 Vue 必须要对其他的框架拥有最大程度的兼容性

  • 视图组件化

    Vue 允许通过组件来去拼装一个页面,每个组件都是一个可复用的 Vue 实例,组件里面可以包含自己的数据,视图和代码逻辑。方便复用

  • 虚拟 DOM(Virtual DOM)

    Vue 之所以可以完全避免对 DOM 的操作,就是因为 Vue 采用了虚拟 DOM 的方式,不但避免了我们对 DOM 的复杂操作,并且大大的加快了我们应用的运行速度。

  • 社区支持

    得益于 Vue 的本土化身份(Vue 的作者为国人尤雨溪),再加上 Vue 本身的强大,所以涌现出了特别多的国内社区,这种情况在其他的框架身上是没有出现过的,这使得我们在学习或者使用 Vue 的时候,可以获得更多的帮助

  • 未来的 Vue 走向

    Vue 是由国人尤雨溪在 Google 工作的时候,为了方便自己的工作而开发出来的一个库,而在 Vue 被使用的过程中,突然发现越来越多的人喜欢上了它。所以尤雨溪就进入了一个边工作、边维护的状态,在这种情况下 Vue 依然迅速的发展。

    而现在尤雨溪已经正式辞去了 Google 的工作,开始专职维护 Vue,同时加入进来的还有几十位优秀的开发者,他们致力于把 Vue 打造为最受欢迎的前端框架。事实证明 Vue 确实在往越来越好的方向发展了(从 Angular、React、Vue 的对比图中可以看出 Vue 的势头)。所以我觉得完全不需要担心未来 Vue 的发展,至少在没有新的颠覆性创新出来之前,Vue 都会越做越好。

30. 简单聊聊 new Vue 以后发生的事情

  1. new Vue 会调用 Vue 原型链上的 _init 方法对 Vue 实例进行初始化;
  2. 首先是 initLifecycle 初始化生命周期,对 Vue 实例内部的一些属性(如 children、parent、isMounted)进行初始化;
  3. initEvents,初始化当前实例上的一些自定义事件(Vue.$on);
  4. initRender,解析 slots 绑定在 Vue 实例上,绑定 createElement 方法在实例上;
  5. 完成对生命周期、自定义事件等一系列属性的初始化后,触发生命周期钩子 beforeCreate
  6. initInjections,在初始化 dataprops 之前完成依赖注入(类似于 React.Context);
  7. initState,完成对 dataprops 的初始化,同时对属性完成数据劫持内部,启用监听者对数据进行监听(更改);
  8. initProvide,对依赖注入进行解析;
  9. 完成对数据(state 状态)的初始化后,触发生命周期钩子 created
  10. 进入挂载阶段,将 vue 模板语法通过 vue-loader 解析成虚拟 DOM 树,虚拟 DOM 树与数据完成双向绑定,触发生命周期钩子 beforeMount
  11. 将解析好的虚拟 DOM 树通过 vue 渲染成真实 DOM,触发生命周期钩子 mounted

31. vue首屏白屏如何解决?

  1. 路由懒加载
  2. vue-cli开启打包压缩 和后台配合 gzip访问
  3. 进行cdn加速
  4. 开启vue服务渲染模式
  5. 用webpack的externals属性把不需要打包的库文件分离出去,减少打包后文件的大小
  6. 在生产环境中删除掉不必要的console.log
  plugins: [
    new webpack.optimize.UglifyJsPlugin({ //添加-删除console.log
      compress: {
        warnings: false,
        drop_debugger: true,
        drop_console: true
      },
      sourceMap: true
    }),
  1. 开启nginx的gzip ,在nginx.conf配置文件中配置
http {  //在 http中配置如下代码,
   gzip on;
   gzip_disable "msie6"; 
   gzip_vary on; 
   gzip_proxied any;
   gzip_comp_level 8; #压缩级别
   gzip_buffers 16 8k;
   #gzip_http_version 1.1;
   gzip_min_length 100; #不压缩临界值
   gzip_types text/plain application/javascript application/x-javascript text/css
    application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
 }
  1. 添加loading效果,给用户一种进度感受

32. vue单页面和传统的多页面区别?

单页面应用(SPA)

通俗一点说就是指只有一个主页面的应用,浏览器一开始要加载所有必须的 html, js, css。所有的页面内容都包含在这个所谓的主页面中。但在写的时候,还是会分开写(页面片段),然后在交互的时候由路由程序动态载入,单页面的页面跳转,仅刷新局部资源。多应用于pc端。

多页面(MPA)

指一个应用中有多个页面,页面跳转时是整页刷新

单页面的优点:

用户体验好,快,

内容的改变不需要重新加载整个页面,基于这一点spa对服务器压力较小;

前后端分离;

页面效果会比较炫酷(比如切换页面内容时的专场动画)。

单页面缺点:

不利于seo;

导航不可用,如果一定要导航需要自行实现前进、后退。(由于是单页面不能用浏览器的前进后退功能,所以需要自己建立堆栈管理);

初次加载时耗时多;、

页面复杂度提高很多。

33. 路由跳转和location.href的区别?

使用location.href='/url’来跳转,简单方便,但是刷新了页面;
使用路由方式跳转,无刷新页面,静态跳转;

34. mvc和MVP,mvvm的区别

MVC: MVC是应用最广泛的软件架构之一,一般MVC分为:Model(模型),View(视图),Controller(控制器)。 这主要是基于分层的目的,让彼此的职责分开.View一般用过Controller来和Model进行联系。ControllerModelView的协调者,ViewModel不直接联系。基本都是单向联系。

MVVM: MVVM是把MVC中的Controller改变成了ViewModelView的变化会自动更新到ViewModel,ViewModel的变化也会自动同步到View上显示,通过数据来显示视图层。

MVVM和MVC的区别:

  • MVC中Controller演变成MVVM中的ViewModel
  • MVVM通过数据来显示视图层而不是节点操作
  • MVVM主要解决了MVC中大量的dom操作使页面渲染性能降低,加载速度变慢,影响用户体验的问题

在开发图形界面应用程序的时候,会把管理用户界面的层次称为 View,应用程序的数据为 Model,Model 提供数据操作的接口,执行相应的业务逻辑。

MVC

MVC 除了把应用程序分为 View、Model层,还额外的加了一个 Controller层,它的职责是进行 Model 和 View 之间的协作(路由、输入预处理等)的应用逻辑(application logic);Model 进行处理业务逻辑。

用户对 View 操作以后,View 捕获到这个操作,会把处理的权利交移给Controller(Pass calls);Controller 会对来自 View 数据进行预处理、决定调用哪个 Model 的接口;然后由 Model 执行相关的业务逻辑;当Model 变更了以后,会通过观察者模式(Observer Pattern)通知 View;View 通过观察者模式收到 Model 变更的消息以后,会向 Model 请求最新的数据,然后重新更新界面。

MVP

和 MVC 模式一样,用户对 View 的操作都会从 View 交易给 Presenter。Presenter 会执行相应的应用程序逻辑,并且会对 Model 进行相应的操作;而这时候 Model 执行业务逻辑以后,也是通过观察者模式把自己变更的消息传递出去,但是是传给 Presenter 而不是 View。Presenter 获取到 Model变更的消息以后,通过 View 提供的接口更新界面。

MVVM

MVVM 可以看做是一种特殊的 MVP(Passive View)模式,或者说是对 MVP 模式的一种改良。

MVVM 代表的是 Model-View-ViewModel,可以简单把 ViewModel 理解为页面上所显示内容的数据抽象,和 Domain Model 不一样,ViewModel 更适合用来描述 View。 MVVM 的依赖关系和 MVP 依赖关系一致,只不过是把 P 换成了 VM。

MVVM的调用关系:

MVVM 的调用关系和 MVP 一样。但是,在 ViewModel 当中会有一个叫 Binder,或者是 Data-binding engine 的东西。以前全部由 Presenter 负责的 View 和 Model 之间数据同步操作交由给 Binder 处理。你只需要在View 的模板语法当中,指令式声明 View 上的显示的内容是和 Model 的哪一块数据绑定的。当 ViewModel 对进行 Model 更新的时候,Binder 会自动把数据更新到 View 上,当用户对 View 进行操作(例如表单输入),Binder 也会自动把数据更新到 Model 上。这种方式称为:Two-way data-binding,双向数据绑定。可以简单而不恰当地理解为一个模板引擎,但是会根据数据变更实时渲染。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I9BnfHRy-1665920994022)(D:\找工作\jsr总结\img\image-20220910210853766.png)]
在这里插入图片描述
在这里插入图片描述

35. 单页应用优缺点

优点

  1. 良好的交互体验

    单页应用的内容的改变不需要重新加载整个页面,获取数据也是通过Ajax异步获取,没有页面之间的切换,就不会出现“白屏现象”,也不会出现假死并有“闪烁”现象,页面显示流畅,web应用更具响应性和更令人着迷。

  2. 良好的前后端工作分离模式

    后端不再负责模板渲染、输出页面工作,后端API通用化,即同一套后端程序代码,不用修改就可以用于Web界面、手机、平板等多种客户端。

  3. 减轻服务器压力

单页应用相对服务器压力小,服务器只用出数据就可以,不用管展示逻辑和页面合成,吞吐能力会提高几倍。

缺点

  1. 首屏加载慢

    • 如果不对路由进行处理,在加载首页的时候,就会将所有组件全部加载,并向服务器请求数据,这必将拖慢加载速度;
    • 通过查看Network,发现整个网站加载时间长达10几秒,加载时间最长的就是js、css文件和媒体文件及图片

    解决方案:

    • Vue-router懒加载

      Vue-router懒加载就是按需加载组件,只有当路由被访问时才会加载对应的组件,而不是在加载首页的时候就加载,项目越大,对首屏加载的速度提升得越明显。

    • 使用CDN加速

    在做项目时,我们会用到很多库,采用cdn加载可以加快加载速度。

    • 异步加载组件

    • 服务端渲染

      服务端渲染还能对seo优化起到作用,有利于搜索引擎抓取更多有用的信息(如果页面纯前端渲染,搜索引擎抓取到的就只是空页面)

  2. 不利于SEO

    seo 本质是一个服务器向另一个服务器发起请求,解析请求内容。但一般来说搜索引擎是不会去执行请求到的js的。也就是说,搜索引擎的基础爬虫的原理就是抓取url,然后获取html源代码并解析。 如果一个单页应用,html在服务器端还没有渲染部分数据,在浏览器才渲染出数据,即搜索引擎请求到的html是模型页面而不是最终数据的渲染页面。 这样就很不利于内容被搜索引擎搜索到。

    解决方案:

  • 服务端渲染:服务器合成完整的 html 文件再输出到浏览器
  • 页面预渲染
  • 路由采用h5 history模式
  1. 不适合开发大型项目

大型项目中可能会涉及大量的DOM操作、复杂的动画效果,也就不适合使用Vue、react框架进行开发。

36. 虚拟DOM和Diff算法

虚拟dom是什么?

Virtual dom, 即虚拟DOM节点。它通过JS的Object对象模拟DOM中的节点,然后再通过特定的render方法将其渲染成真实的DOM节点。比操作真实dom减少性能开销

diff算法又是什么?

传统的 Diff 算法也是一直都有的;diff算法,会对比新老虚拟DOM,记录下他们之间的变化,然后将变化的部分更新到视图上。其实之前的diff算法,是通过循环递归每个节点,然后进行对比,复杂程度为O(n^3)n是树中节点的总数,这样性能是非常差的。

dom-diff的原理?

dom-diff算法会比较前后虚拟DOM,从而得到patches(补丁),然后与老Virtual DOM进行对比,将其应用在需要更新的地方,将 O(n^3) 复杂度的问题转换成 O(n^1=n) 复杂度的问题,得到新的Virtual DOM。降低时间复杂度的方法:

  1. 两个不同类型的元素会产生不同的树
  2. 对于同一层级的一组子节点,它们可以通过唯一 key 进行区分

37. 框架带来的好处和弊端

优势:

  1. 组件化:其中以react的组件化最为彻底,甚至可以到函数级别的原子组件,高度的组件化可以使我们的工程易于维护,易于组合扩展;
  2. 天然分层:jQuery时代的代码大部分情况下是面条代码,耦合严重,现代框架不管是MVC、MVP还是MVVM模式都可以帮我们进行分层,代码解耦更易于读写;
  3. 生态:现代主流框架都自带生态,不管是数据流管理架构还是UI库都有成熟的解决方案;
  4. 开发效率:现在前端框架都默认自动更新DOM,而非我们手动操作,解放了开发者的手动DOM成本,提高开发效率,从根本上解决了UI与状体同步问题。

劣势:

  1. 兼容性问题,SEO不友好
  2. 有场景要求,开发自由度降低
  3. 有黑盒开发,框架本身有出错的风险
  4. 有学习成本

38. v-for 和 v-if 为什么不能一起用(优先级比较)

先说结论:

  • 在vue2中,v-for的优先级高于v-if
  • 在vue3中,v-if的优先级高于v-for

因此在vue2中:

1.v-for比v-if优先,如果每一次都需要遍历整个数组,将会影响速度,尤其是当只需要渲染很小一部分的时候。

如果连用的话会把 v-if 给每个元素都添加一下,会造成性能问题。

一般时候把v-if放在外层,如果不符合就不去执行了。

也可以使用计算属性computed来代替v-if

39. Less 怎么定义变量

Less的简单使用(一)——变量的用法
Less是css的扩展,Less不仅与CSS向后兼容,而且它添加的其他功能都使用现有的CSS语法。
变量
一、变量正常用法(variables)
在一个位置控制常用值
.less文件中

/**/ Variables
@link-color:        #428bca; // sea blue
@link-color-hover:  darken(@link-color, 10%);
// Usage
a,
.link {
  color: @link-color;
}
a:hover {
  color: @link-color-hover;
}
.widget {
  color: #fff;
  background: @link-color;
}**

40. v-html 和 v-text

  • v-html 指令的作用:设置元素的innerHTML
  • 内容中有html结构会被解析为标签
  • v-text指令无论内容是什么,只会解析为文本
  • 解析文本使用v-text,需要解析html结构使用v-html

41. 使用Axios来设置请求头的方法

使用Axios来设置请求头(headers)的方法 - 掘金 (juejin.cn)

法一:传递一个对象参数

单个请求

诸如post()get() 等Axios方法使我们能够在请求中附加头信息,方法是将头信息对象作为GET请求的第二个参数和POST请求的第三个参数。

POST和GET请求分别用于创建或检索一个资源。下面是一些一次性或单个请求的例子。

首先,我们声明config 对象,其中包含headers 对象,它将在提出请求时作为一个参数提供。我们还声明了一个api endpoint 和一个data 对象。

const config = {
  headers:{
    header1: value1,
    header2: value2
  }
};
const url = "api endpoint";

const data ={
  name: "Jake Taper",
  email: "taperjake@gmail.com"
}

我们可以使用GET请求从API端点url 检索config 对象。

axios.get(url, config)
  .then(res=> console.log(res))
  .catch(err=> console.log(err))

在这个例子中,我们把API端点url 作为第一个参数传入,把config 对象作为第二个参数。

我们可以使用POST请求将data 对象传递给API端点url

 axios.post(url, data, config)
  .then(res => console.log(res))
  .catch(err => console.log(err))

在这个例子中,我们把API端点url 作为第一个参数,把data 对象作为第二个参数,把config 对象作为第三个参数。

法二:创建一个特定的axios实例

我们还可以通过创建一个特定的Axios实例来为API调用设置请求头。

我们可以使用require 来创建一个新的Axios实例。

const axios = require('axios')

然而,这个选项不允许我们传入配置。为了正确设置每个请求的头信息,我们可以使用axios.create创建一个Axios实例,然后在该实例上设置一个自定义配置。

let reqInstance = axios.create({
  headers: {
    Authorization : `Bearer ${localStorage.getItem("access_token")`
    }
  }
})

我们可以在每次使用这个特定实例进行请求时重复使用这个配置。

当我们使用reqInstance 进行请求时,授权头就会被附上。

reqInstance.get(url);

法三:使用Axios拦截器

我们也可以使用Axios拦截器来为API调用设置请求头。Axios拦截器是由Axios调用的函数。拦截器可以用来在请求传输之前改变它,或者在响应交付之前修改它。拦截器本质上等同于ExpressMongoose的中间件。

我以前做过一个项目,要求在每个请求中附加一个包含用户访问令牌的授权头。这是一个金融应用,系统需要为每个请求验证用户身份。在这个例子中,最好是将授权头自动附加到每个请求中,而不是单独设置它们。

认证是拦截器最常见的应用之一。客户端应用经常通过在授权头中提交一个秘密访问令牌来向服务器验证用户身份。

我们可以使用Axios拦截器为所有的请求自动设置Authorization header。

// Request interceptors for API calls
axios.interceptors.request.use(
  config => {
    config.headers['Authorization'] = `Bearer ${localStorage.getItem('access_token')`};
        return config;
    },
    error => {
        return Promise.reject(error);
    }
);

在这个例子中,我们使用axios.interceptors.request.use 方法来更新每个请求头并在Authorization HTTP头中设置访问令牌。

我们以config.headers 对象中的Authorization 头为目标,并将存储在localStorage 中的Bearer 令牌设为其值。

Axios拦截器对于监控访问令牌是否即将过期也很有用。一个refreshToken() 函数可以用来在令牌过期前更新它。

const refreshToken= ()=>{
  // gets new access token
}

我们也可以调用axios.interceptors.response.use() 方法来获取新的访问令牌,只要响应返回403 错误,这意味着现有令牌已经过期。

// Response interceptor for API calls
axios.interceptors.response.use(
    response => {
        return response;
    },
    error => {
        if(error.response.status == 403){
            refreshToken()
        }
    }
);

在这个例子中,axios.interceptors.response.use 方法拦截所有传入的响应,然后检查response 的状态。如果触发response 的请求没有经过验证,那么令牌就过期了。在这种情况下,我们调用refreshToken() 函数来获得一个新的访问令牌。

42. 在vue中watch和created哪个先执行?为什么?

img

答:官网的生命周期图中,init reactivity是晚于beforeCreate但是早于created的。
1、 watch如果加了immediate: true,应当同init reactivity周期一同执行,会早于created执行。
2、 而正常的watch,则是mounted周期后触发data changes的周期执行,晚于created。

1、immediate
watch 的一个特点是,默认最初绑定的时候是不会执行的,要等到值改变时才执行监听计算。
设置immediate为true后,被监听值初始化的时候就会执行监听函数,也就页面上的数据还未变化的时候。
比如当父组件向子组件动态传值时,子组件props首次获取到父组件传来的默认值时,也需要执行函数,此时就需要将immediate设为true

2、deep
当需要监听对象的改变时,此时就需要设置deep为true,不论其被嵌套多深,改变对象中的属性值能够触发监听,改变整个监听值也会触发。
deep的意思就是深入观察,监听器会一层层的往下遍历,给对象的所有属性都加上这个监听器,但是这样性能开销就会非常大了,任何修改obj里面任何一个属性都会触发这个监听器里的 handler。
优化,我们可以是使用字符串形式监听。

watch: {
  'obj.a': {
    handler(newName, oldName) {
      console.log('obj.a changed');
    },
    immediate: true,
    // deep: true
  }
}

总结:如果需要某个值在初始化时就触发监听,就使用immediate:true,如果需要深度监听就使用deep:true,两个也可以结合使用。

43. vue封装组件注意事项

Vue 组件化注意事项

  • 组件可以理解为特殊的 Vue 实例,管理自己的 template 模板。
  • // 组件的 template 必须有且只有一个根节点(vue2),vue3可以有多个。
    1. 组件的 data 选项必须是一个函数,且函数体返回一个对象。
    1. 子组件不能直接修改父组件传递过来的值。 父组件通过属性的形势可以随意给子组件传递值,但子组件不能修改父组件的数据; 否则会有一个警告,提示不要直接修改父组件传递过来的值。

44. 二次封装axios

axios 的API很友好,你完全可以很轻松地在项目中直接使用。不过随着项目规模增大,如果每发起一次HTTP请求,就要把这些比如设置超时时间、设置请求头、根据项目环境判断使用哪个请求地址、错误处理等等操作,都就地写一遍,得疯!这种重复劳动不仅浪费时间,而且让代码变得冗余不堪,难以维护。

为了提高我们的代码质量,我们应该在项目中二次封装一下 axios 再使用。

封装步骤

封装的本质就是在待封装的内容外面添加各种东西,然后把它们作为一个新的整体呈现给使用者,以达到扩展和易用的目的。

封装axios要做的事情,就是把所有HTTP请求共用的配置,事先都在axios上配置好,预留好必要的参数和接口,然后把它作为新的axios返回。

/ src/utils/http.js

import axios from 'axios';

const getBaseUrl = (env) => {
  let base = {
    production: '/',
    development: 'http://localhost:3000',
    test: 'http://localhost:3001',
  }[env];
  if (!base) {
    base = '/';
  }
  return base;
};

class NewAxios {
  constructor() {
    //给不同环境配置不同请求地址
    //根据 process.env.NODE_ENV 配置不同的 baseURL,使项目只需执行相应打包命令,就可以在不同环境中自     //动切换请求主机地址。
    this.baseURL = getBaseUrl(process.env.NODE_ENV);
      //配置超时时间timeout属性,我一般设置10秒。
    this.timeout = 10000;
      //配置允许携带凭证widthCredentials属性设为true。
    this.withCredentials = true;
  }
// 这里的url可供你针对需要特殊处理的接口路径设置不同拦截器。
  setInterceptors = (instance, url) => {
     //配置请求拦截器
	//在发送请求前对请求参数做的所有修改都在这里统一配置。比如统一添加token凭证、统一设置语言、统一设置内
    //容,类型、指定数据格式等等。做完后记得返回这个配置,否则整个请求不会进行。
	//我这里就配置一个token。
    instance.interceptors.request.use((config) => {
      // 在这里添加loading
      // 配置token
      config.headers.AuthorizationToken = localStorage.getItem('AuthorizationToken') || '';
      return config;
    }, err => Promise.reject(err));
	//配置响应拦截器
	//在请求的then或catch处理前对响应数据进行一轮预先处理。比如过滤响应数据,更多的,是在这里对各种响应
    //错误码进行统一错误处理,还有断网处理等等。
    //我这里就判断一下403和断网。
    instance.interceptors.response.use((response) => {
      // 在这里移除loading
      // todo: 想根据业务需要,对响应结果预先处理的,都放在这里
      return response;
    }, (err) => {
      if (err.response) { // 响应错误码处理
        switch (err.response.status) {
          case '403':
            // todo: handler server forbidden error
            break;
            // todo: handler other status code
          default:
            break;
        }
        return Promise.reject(err.response);
      }
      if (!window.navigator.online) { // 断网处理
        // todo: jump to offline page
        return -1;
      }
      return Promise.reject(err);
    });
  }

  request(options) {
    // 每次请求都会创建新的axios实例。
    const instance = axios.create();
    const config = { // 将用户传过来的参数与公共配置合并。
      ...options,
      baseURL: this.baseURL,
      timeout: this.timeout,
      withCredentials: this.withCredentials,
    };
    // 配置拦截器,支持根据不同url配置不同的拦截器。
    this.setInterceptors(instance, options.url);
    return instance(config); // 返回axios实例的执行结果
  }
}
//默认导出新的实例
export default new NewAxios();

现在 axios 封装算是完成了80%。我们还需要再进一步把axios和接口结合再封装一层,才能达到我在一开始定的封装目标。

2. 使用新的 axios 封装API
  • 在 src 目录下新建 api 文件夹。把所有涉及HTTP请求的接口统一集中到这个目录来管理。
  • 新建 home.js。我们需要把接口根据一定规则分好类,一类接口对应一个js文件。这个分类可以是按页面来划分,或者按模块等等。为了演示更直观,我这里就按页面来划分了。实际根据自己的需求来定。
  • 使用新的 axios 封装API(固定url的值,合并用户传过来的参数),然后命名导出这些函数。
// src/api/home.js 

import axios from '@/utils/http';
export const fetchData = options => axios.request({
  ...options,
  url: '/data',
});
export default {};
  • 在 api 目录下新建 index.js,把其他文件的接口都在这个文件里汇总导出。
// src/api/index.js

export * from './home';

这层封装将我们的新的axios封装到了更简洁更语义化的接口方法中。

现在我们的目录结构长这样:

|--public/
|--mock/
|   |--db.json  # 接口模拟数据
|--src/
|   |--api/     # 所有的接口都集中在这个目录下
|       |--home.js  # Home页面里涉及到的接口封装在这里
|       |--index.js # 项目中所有接口调用的入口
|   |--assets/
|   |--components/
|   |--router/
|   |--store/
|   |--utils/
|       |--http.js  # axios封装在这里
|   |--views/
|       |--Home.Vue
|   |--App.vue
|   |--main.js
|   |--theme.styl
|--package.json
|...
使用封装后的axios

现在我们要发HTTP请求时,只需引入 api 下的 index.js 文件就可以调用任何接口了,并且用的是封装后的 axios

// src/views/Home.vue

<template>
  <div class="home">
    <h1>This is home page</h1>
  </div>
</template>

<script>
// @ is an alias to /src
import { fetchData } from '@/api/index';

export default {
  name: 'home',
  mounted() {
    fetchData()  // axios请求在这里
      .then((data) => {
        console.log(data);
      })
      .catch((err) => {
        console.log(err);
      });
  },
};
</script>

axios请求被封装在fetchData函数里,页面请求压根不需要出现任何axios API,悄无声息地发起请求获取响应,就像在调用一个简单的 Promise 函数一样轻松。并且在页面中只需专注处理业务功能,不用被其他事物干扰。

44. vue 引入组件的三种方式

一、 普通引入方式

import 组件名 from ‘组件路径’
import login from ‘@/components/login’

优点: 易理解,
缺点:webpack在打包的时候会把整个路由打包成一个js文件,如果页面一多,会导致这个文件非常大,加载缓慢

二、 Vue异步组件技术

component: (resolve) => require(['@/views/login/login'], resolve)

在这里插入图片描述

三 、使用动态的import( )语法(推荐使用)

 component: () => import('@/views/login/login')

在这里插入图片描述

路由懒加载

45. Vue动态加载组件的两类方式

Vue加载组件主要有正常加载懒加载,示例代码如下:

//正常加载
import index from '../pages/index.vue'
import view from '../pages/view.vue'
//懒加载
const index = resolve => require(['../pages/index.vue'], resolve)
const view = resolve => require(['../pages/view.vue'], resolve)
//懒加载 - 按组
const index = r => require.ensure([], () => r(require('../pages/index.vue')), 'group-index')
const view = r => require.ensure([], () => r(require('../pages/view.vue')), 'group-view')
// 懒加载 - 按组 import,基于ES6 import的特性
const index = () => import('../pages/index.vue')
const view = () => import('../pages/view.vue')

2.1 使用import导入组件

在这里插入图片描述

如上图所示,红色方框内的,可以写在组件上,当然也可以用一个变量保存,然后直接引用变量。

2.2 使用require导入组件

在这里插入图片描述

或者

在这里插入图片描述

上2个图中,只是不同的写法形式而已。上图中,红色方框内的,可以写在组件上,当然也可以用一个变量保存,然后直接引用变量。

46. vue中require与import的区别

1.require 是赋值过程并且是运行时才执行import 是解构过程并且是编译时执行。require可以理解为一个全局方法,可以在任何位置执行,而import则必须要写死在文件的顶部,不能嵌套在条件语句中,这就是我遇到的问题。
2.require的性能相对于import稍低

47. native 修饰符的作用及使用方法

官网的解释:

            你可能想在某个组件的根元素上监听一个原生事件。可以使用 v-on 的修饰符 .native 。

    通俗点讲:就是在父组件中给子组件绑定一个原生的事件,就将子组件变成了普通的HTML标签,不加'. native'事件是无法触发的。

img

此时点击页面中的按钮无任何反应。

? 添加修饰符:

img

此时点击就会弹窗:

img

可以理解为该修饰符的作用就是把一个vue组件转化为一个普通的HTML标签,并且该修饰符对普通HTML标签是没有任何作用的。

48. vue怎么实现依赖收集和派发更新

  1. 依赖收集

在这里插入图片描述

subs:
Dep类创建的,用于存放所有被收集的依赖即watcher,将依赖存放在subs中,目的是后面数据发生变化的时候能够通知那些subs做出准备。
watcher中定义的:

newDeps:表示新添加的Dep实例数组
deps:表示上一次添加的Dep实例数组
newDepIds:表示在最新的 addDep过程中,收集的 dep id
depIds:表示在 上一次addDep 过程中,收集的 dep id。

为什么vue要创建两个Dep实例数组?
因为Vue是数据驱动的,所以每次数据变化都会重新render,那么 vm._render() 方法又会再次执行,并再次触发数据的 getters,所以 Watcher 在构造函数中会初始化 2 个 Dep 实例数组。目的是在执行 cleanupDeps 函数的时候,会首先遍历 deps,移除对 dep.subs 数组中 Wathcer 的订阅,然后把 newDepIds 和 depIds 交换,newDeps 和 deps 交换,并把 newDepIds 和 newDeps 清空。避免渲染不必要的数据,提升了性能。

  1. 派发更新

![在这里插入图片描述](https://img-blog.csdnimg.cn/f6970cd3738f41ebaa2ffeec9730487c.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAeWV6aV9fNjI2,size_20,color_FFFFFF,t_70,g_se,x_16

queueWatcher:就是将watcher去重,然后添加到队列中。然后执行nextTick(flushSchedulerQueue)异步更新。

flushSchedulerQueue:这个方法简单来说就是对queue队列排序,然后遍历该数组,执行watcher.run()。

watcher.run():这个run()方法其实最终就是调用了watcher的get方法,这个get我们在前面看过了,最主要的就是去调用了data数据的get方法,获取最新数据。因此我们可以理解run()就会进行新一轮的依赖收集,从而获取最新的数据。

最后我们简单总结一下依赖收集和派发更新:
当访问响应式数据时,触发getter,调用dep.depend()进行依赖收集到subs中,这个过程我们叫依赖收集;而当这些响应式数据发生变化,触发它们的 setter 的时候,能知道应该通知哪些订阅者去做相应的逻辑处理,我们把这个过程叫派发更新。
Watcher 和 Dep 就是一个非常经典的观察者设计模式的实现,这里我们暂时先不讲,后面有空的话我会补上哦!

49. Vue弹窗组件的实现

弹窗组件包含内容:

弹窗遮罩层
内容层的实现(涉及slot、props、 o n 、 on、 onemit)
实现步骤:

1、搭建组件UI样式,HTML、css实现遮罩层、内容区
2、编写弹窗内容:通过组件slot插槽接收父组件传递过来的弹窗内容
3、组件开关的实现:通过父组件传递进来的props控制组件的显示与隐藏,子组件关闭时通过事件 $emit 触发父组件改变状态值。

阻止背景滚动:@touchmove.prevent

50. vue双向绑定是怎么实现的?defineProperty()的弊端

采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应监听回调。
当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用Object.defineProperty 将它们转为 getter/setter。用户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。

众所周知,Vue2.0 对于数据响应式的实现上是有一些局限性的,比如:

  • 无法检测数组和对象的新增
  • 无法检测通过索引改变数组的操作

通过索引改变arr[1],我们发现触发了set,也就是Object.defineProperty是可以检测到通过索引改变数组的操作的,那Vue2.0为什么没有实现呢?是尤大能力不行?这肯定毋庸置疑。那他为什么不实现呢?

小结:是出于对性能原因的考虑,没有去实现它。而不是不能实现。

对于对象而言,每一次的数据变更都会对对象的属性进行一次枚举,一般对象本身的属性数量有限,所以对于遍历枚举等方式产生的性能损耗可以忽略不计,但是对于数组而言呢?数组包含的元素量是可能达到成千上万,假设对于每一次数组元素的更新都触发了枚举/遍历,其带来的性能损耗将与获得的用户体验不成正比,故vue无法检测数组的变动。

不过Vue3.0用proxy代替了defineProperty之后就解决了这个问题。

解决方案

1、this.$set(array, index, data)

//这是个深度的修改,某些情况下可能导致你不希望的结果,因此最好还是慎用
this.dataArr = this.originArr
this.$set(this.dataArr, 0, {data: '修改第一个元素'})
console.log(this.dataArr)        
console.log(this.originArr)  //同样的 源数组也会被修改 在某些情况下会导致你不希望的结果 

2、splice

//因为splice会被监听有响应式,而splice又可以做到增删改。

3、利用临时变量进行中转

let tempArr = [...this.targetArr]
tempArr[0] = {data: 'test'}
this.targetArr = tempArr

51. 微前端框架有了解过吗

https://zhuanlan.zhihu.com/p/79388540
微服务,维基上对其定义为:一种软件开发技术- 面向服务的体系结构(SOA)架构样式的一种变体,将应用程序构造为一组松散耦合的服务,并通过轻量级的通信协议组织起来。具体来讲,就是将一个单体应用,按照一定的规则拆分为一组服务。这些服务,各自拥有自己的仓库,可以独立开发、独立部署,有独立的边界,可以由不同的团队来管理,甚至可以使用不同的编程语言来编写。但对前端来说,仍然是一个完整的服务。
微服务,主要是用来解决庞大的一整块后端服务带来的变更和扩展的限制。
同样的,面对越来越重的前端应用,可将微服务的思想照搬到前端,就有了微前端的概念。像微服务一样,一个前端应用,也可以按照一定的规则,拆分为不同的子应用,独立开发,独立部署,然后聚合成一个完整的应用面对客户。
微前端的一般结构如下:
在这里插入图片描述
微前端能带给我们什么

简单、分离、松耦合的代码仓库
对比巨石应用一整块的代码仓库,微前端架构下的代码仓库更加简单、轻量。各个仓库的代码可以基于业务、权限、变更的频率、组织结构、后端微服务等原则拆分,界限明确,降低耦合,便于开发人员在开发过程中快速定位源代码,提高开发效率,降低维护成本。

独立开发、独立部署
代码库拆分以后,我们可以基于各个代码仓库独立开发。由于代码体积的缩小,项目构建时间变短,极大提升开发效率。
另外,各个项目都有自己的交付流水线(从构建、测试到上线),并且能够独立部署,不需要考虑其他项目的情况。

技术栈无关
在实际项目中,各个项目会因为各种各样的原因导致使用的技术栈不一样。比如开发框架有 react、vue、angular 等,构建工具有 webpack、rollup、parcel 等,而且版本还可能不一致。使用微前端架构,可以做到将使用不同技术栈(不同版本)的子应用聚合起来

遗留系统迁移
每个公司中,多多少少会存在一些应用是使用老的技术栈开发的,比如 Backbone、Vue1.0、angular2、jquery 等。这些应用已经在线上稳定运行,而且也没有新的功能。对于这样的应用,我们没有理由浪费时间和精力,可以通过微前端方案直接整合到新的应用中。

使用微前端方案很大一部分原因就是为了解决遗留系统迁移问题。

技术栈升级
除了遗留系统迁移,微前端在技术栈版本升级方面也能提供帮助。
有些项目,在成立之初使用了当前最新的技术如 antd2。随着技术的发展,antd 已经更新到了 4,但项目由于一直在迭代,还是使用 antd2。直接全部重构,肯定是不现实的,费时费力不说,风险也大。
针对这种情况,我们可以重起一个应用,使用 antd4 循序渐进的重构应用,然后使用微前端方案将新旧应用聚合在一起。

团队技术成长
微前端技术栈无关的优点,可以让团队获得更多的机会在项目中尝试新的技术(vue3、webpack5 等),有助于整个团队技术的成长。

作者:0o华仔o0
链接:https://juejin.cn/post/6955341801381167112
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

52. Vue2的数据监听原理 ? 追问 :如何实现新增属性的监听 × 追问 :Vue3监听事件的原理

一、vue2无法监听数组和对象变化的原因

vue2是通过 Object.defineProperty(obj, key, value)这种方式监听数据的

  1. 对于数组
    Object.defineProperty()是可以对数组实现监听操作的,但是vue并没有实现这个功能,因为数组长度不定而且数据可能会很多,如果对每一个数据都实现监听,性能代价太大

但是注意:数组中的元素是引用类型时是会被监听的

  1. 对象
    Object.defineProperty()针对的是对象的某个属性,而且这个操作在vue的初始化阶段就完成了,所以新增的属性无法监听,通过set方法新增对象就相当于初始化阶段的数据响应式处理

vue 3是通过proxy直接代理整个对象来实现的,而不是像Object.defineProperty针对某个属性。所以,只需做一层代理就可以监听同级结构下的所有属性变化,包括新增属性和删除属性

二、如何实现对数组和对象新增属性的监听

一、数组

  1. 不能监听的情况
    (1) 直接通过下标赋值 arr[i] = value

    (2) 直接修改数组长度 arr.length = newLen

  2. 替代做法

(1)修改值

  1. Vue.set(arr, index, newvalue)
  2. vm.$set(arr, index, newvalue)
  3. arr.splice(index, 1, newvalue)

(2) 修改数组长度,arr.splice(newLen)

注意
调用数组的pop、push、shift、unshift、splice、sort、reverse等方法时是可以监听到数组的变化的vue内部相当于重写了数组的原型,劫持了这七个方法

二、对象

  1. 不能监听的情况
    属性的新增和删除

obj.newkey=newvalue

delete obj.key

  1. 替代做法
    // 新增
    1. Vue.set(obj, newkey, newvalue)
    2. vm.$set(obj, newkey, newvalue)
    3. obj = Object.assign({}, obj, {newkey1: newvalue1, newkey2: newvalue2})
      // 删除
    4. Vue.delete(obj, key)
    5. vm.$delete(obj, key)

————————————————
版权声明:本文为CSDN博主「more名奇妙」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/m0_67948827/article/details/127017264

53. 父组件如何调用子组件方法

方案一:通过ref直接调用子组件的方法;

//父组件中

<template>
    <div>
        <Button @click="handleClick">点击调用子组件方法</Button>
        <Child ref="child"/>
    </div>
</template>    

<script>
import Child from './child';

export default {
    methods: {
        handleClick() {
              this.$refs.child.sing();
        },
    },
}
</script>


//子组件中

<template>
  <div>我是子组件</div>
</template>
<script>
export default {
  methods: {
    sing() {
      console.log('我是子组件的方法');
    },
  },
};
</script>

方案二:通过组件的$emit、$on方法;

//父组件中

<template>
    <div>
        <Button @click="handleClick">点击调用子组件方法</Button>
        <Child ref="child"/>
    </div>
</template>    

<script>
import Child from './child';

export default {
    methods: {
        handleClick() {
               this.$refs.child.$emit("childmethod")    //子组件$on中的名字
        },
    },
}
</script>

//子组件中

<template>
    <div>我是子组件</div>
</template>
<script>
export default {
    mounted() {
        this.$nextTick(function() {
            this.$on('childmethods', function() {
                console.log('我是子组件方法');
            });
        });
     },
};
</script>

54. vue-router传递参数的几种方式

https://www.cnblogs.com/lcxcsy/p/13810920.html

55. 对proxy的理解

Proxy是什么?

proxy是ES6中就存在的,用于修改某些操作的默认行为,可以理解成在目标对象前设一个拦截层,因此也叫“代理器”。

proxy英文原意是代理的意思,在ES6中,可以翻译为"代理器"。
它主要用于改变某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。

proxy在目标对象的外层搭建了一层拦截,外界对目标对象的某些操作(后文会说明,有哪些操作可以拦截),必须通过这层拦截。

https://vue3js.cn/interview/es6/proxy.html#%E4%BA%8C%E3%80%81%E7%94%A8%E6%B3%95

let obj = {
        a : 1
    }
    let proxyObj = new Proxy(obj,{
        get : function (target,prop) {
            return prop in target ? target[prop] : 0
        },
        set : function (target,prop,value) {
            target[prop] = 888;
        }
    })
    
    console.log(proxyObj.a);        // 1
    console.log(proxyObj.b);        // 0

    proxyObj.a = 666;
    console.log(proxyObj.a)         // 888

https://www.jianshu.com/p/81eb68ae5eb1

56. vue封装组件

在components 文件夹中创建你需要组件的.vue文件,在该文件中完成 template 以及script style部分代码实现该组件功能,然后在其他文件的js里面引入并注册,就可以直接使用这个组件了

import list from '../components/headComponent.vue'
  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-10-22 21:06:34  更:2022-10-22 21:10:17 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/17 16:44:06-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码