vue2篇

一. vue是什么,vue的核心概念是什么

  1. vue是一套用于构建用户界面的渐进式JS框架
  2. 渐进式:允许用户从一个简单的组件写起,按需加载,直到搭建出一个复杂的前端平台。
  3. vue的核心概念:
    • 组件化
    • MVVM结构,数据双向绑定
    • 响应式,虚拟DOM
    • 生命周期
    • 一切以数据为核心,数据驱动视图。

二. vue的优点

  1. 轻量级:vue作为一款轻量级前端框架,大小只有18-21KB,工程搭建简单。
  2. 高性能:虚拟DOM和响应式避免了不必要的全局渲染,提升了用户体验,使得用户操作更加流畅。
  3. 易上手:vue的组件化开发思想容易理解,分离式语法对初学者较为友好。
  4. 插件化:由于vue框架的流行性,目前有许多基于ue的npm扩展包和开发工具(如vuex)。vue可以在一个文件下统一管理所有外部插件的全局使用。插件化提供了很多的开发便捷性。
  5. 运行速度快:较其他框架就性能而言,vue存在很大的优势。
  6. 便于测试:组件化的思想便于排查问题所在,能快速追踪到。
  7. 视图,数据,结构分离:使数据的更改更为简单,不需要进行逻辑代码的修改,只需要操作数据就能完成相关操作。

三. vue.js的安装

  1. cdn引入 <script src=''></script>
  2. 下载文件,<script src='vue.js'></script>
  3. 通过npm(或者yarn)全局安装:npm install vue@版本 --g/–save
  4. 注意:可以根据实际需求下载对应的开发环境或者生产环境

四. MVVM

  1. M:model 数据模型层,从网络请求中获取到的数据。
  2. V:views 视图层,也就是DOM层,用于向用户展示渲染结果。
  3. VM:view-model 视图模型层,用于连接view和model,实现两者之间的双向绑定,这样改变model,view会跟着改变,同理改变view,model数据层也会自动更新。

五. 生命周期

  1. 概念:一个vue的实例从创建、挂载、渲染、更新到销毁的过程
  2. 创建:
    • 创建前beforeCreate:这个时候vue实例还没有初始化,此时挂载元素$el和数据对象data都为空
    • 创建后created:这个时候vue实例已经初始化了,但是dom未生成,因此$el为空,data存在,可以通过methods操作data了
  3. 挂载:
    • 挂载前beforeMount:这个时候$el和data都已经有了,模板已经在内存中编辑,但是还没有渲染到页面
    • 挂载后mounted:$el已经渲染到页面,用户可以看见
  4. 更新:
    • 更新前beforeUpdate:此时view和model未实现双向绑定
    • 更新后updated:此时view和model已经实现了双向绑定
  5. 销毁:
    • 销毁前beforeDestory: 此时vue的实例依然可以使用
    • 销毁后destoryed:vue实例已经解除绑定,操作data无效,但是dom结构还依然存在

六. 指令

  1. v-once
    • view层展示的数据只会渲染一次,改变data不会跟着发生改变
    • 语法:<h2 v-once>{ {message}}</h2> 这里的message只会渲染一次
  2. v-html
    • 将data里面的数据解析成html语言
    • 语法: <h2 v-html='data'></h2>
  3. v-text
    • 渲染data类似于{ {}} mustache 胡须语法
    • 语法:<h2 v-text='message'></h2> = <h2>{ {message}}</h2>(推荐使用{ {}}语法)
  4. v-pre
    • 将后面的内容不做修改,原封不动的渲染出来
    • 语法:<h2 v-pre>{ {message}}</h2>
  5. v-clock
    • 解决初始化慢导致的页面闪烁问题
    • 语法:<h2 v-clock>{ {message}}</h2>
  6. v-bind
    • 动态绑定,语法糖为":" v-bind:class<=>:class 动态的绑定一个class类
    • 语法:<h2 :class={‘active’:isActive}>{ {message}}</h2>
  7. v-on
    • 点击事件,语法糖为“@” v-on:click=@click 绑定一个点击事件
    • 语法:<h2 @click=‘btn’>{ {message}}</h2>
  8. v-if,v-else,v-else-if,v-show
    • v-if: 条件判断
    • 语法:<h2 v-if='条件'>{ {message}}</h2> 条件为真则渲染,条件为假不渲染
    • v-if和v-show的区别:
      • 当条件为假的时候v-if压根不渲染,v-show会渲染,只是通过display:none的方式将渲染结果隐藏起来
      • v-if的渲染成本高于v-show
      • 当需要频繁的切换显示与隐藏,建议使用v-show
  9. v-for
    • 循环遍历
    • 语法:
      • 遍历数组:<h2 v-for='(book,index) in/of books'>{ {index}}—{ {book.name}}</h2>
      • 遍历对象:<h2 v-for='(value,key,index) in/of books'>{ {index}}—{ {key}}—{ {value}}</h2>
  10. v-model:实现标签数据的双向绑定,本质上是一个语法糖。
<input v-model='test'>`等价于<input :value="test" @input="test = $event.target.value">

七. 组件

  1. 组件化思想:将一个页面拆成一个个小的部分,每一个小的部分称为一个组件,完成组件内的独立功能,让后将这些组件合并起来,形成完整功能。
  2. 优点:每个组件之间是相互独立的,便于代码的书写和维护。
  3. 组件的使用(以单一vue文件为例,实际项目中一般不这么写)
// 1. 创建组件
const cpn = Vue.extend({
    
    
	// 组件模板
	template:`<div>我是子组件</div>`
})

// 2. 注册组件
// (1)全局注册
Vue.component('my-cpn',cpn)
// (2)局部注册
let app=new Vue({
    
    
    el:'#app',
    data:{
    
    
        message:'你好'
    },
    // 局部注册
    component:{
    
    
        'my-cpn':cpn
    }
})

// 3. 挂载组件
<div id='app'>
    <my-cpn></my-cpn>
</div>
  1. 组件中的数据:
    • data(){}:必须是一个函数,因为组件涉及到可能在多个页面引用,为了防止数据之间的相互干扰因此data必须是一个函数,且必须有return
  2. 父子组件的通信
    • 父组件向子组件传递数据
      • 在父组件中挂载子组件,并通过v-bind的形式绑定想要传输的数据
      • 在子组件中通过props接受数据
      • 在子组件中根据需求渲染不同的数据
    • 子组件向父组件传递数据
      • 在子组件中通过$emit(event,value) 的形式将数据发射出去
      • 在父组件中通过v-on:event='methods’来接受数据
      • 根据需求在methods中定义该方法
  3. 父子组件之间的访问
    • 父组件访问子组件:this. c h i l d r e n 或者 t h i s . children 或者 this. children或者this.refs
    • 子组件访问父组件:this.$parent
    • this. c h i l d r e n 和 t h i s . children和this. childrenthis.refs的区别
      • this.$children访问到是一个数组类型,需要通过具体的索引值来确定访问到的具体子组件
      • this.$refs可以通过绑定一个id来确定访问的子组件
<!--在子组件中绑定id,在父组件中拿到想要的特定的子组件-->
 <!-- 通过this.$refs.child1可以拿到具体的子组件 -->   
<child_cpn1 ref='child1'></child_cpn1> 
      
 <!-- 通过this.$children[0]索引值可以拿到具体的子组件 -->    
 <child_cpn1></child_cpn1> 
 <child_cpn2></child_cpn1> 
  1. 非父子组件通信
    • 中央事件总线
      • 在mian.js入口文件中注册中央事件总线:Vue.prototype.$bus=new Vue()
      • 在需要通信的位置发射该事件:this.$bus.$emit('事件名',value)
      • 在对应位置接收:this.$bus.$on('发射时候的事件名',callback(value)=>{})
      • vuex vue的集中式状态管理机制
    • 发布订阅模式

八. 插槽

  1. 定义:组件化开发中通过<slot></slot>包裹起来的部分称为插槽。
  2. 作用:在组件化开发中,同一组件可能会被多次复用,而每次仅仅只是内容不同,这时候可以使用插槽占位,然后根据实际需求替换插槽中的内容。
  3. 分类
    (1) 匿名插槽
<!-- 子组件 -->
<div>
  <slot></slot>
</div>

<!-- 父组件 -->
<child>
  <span>我是插槽插入的内容</span>  <!-- 用span标签替换slot插槽 -->
</child>

(2)具名插槽

<!-- 子组件 -->
<div>
  <slot name="wangjiajia"></slot>
</div>

<!-- 父组件 -->
<child>
  <span slot="wangjiajia">我是插槽插入的内容</span>  <!-- 用span标签只替换slot名字为wangjiajia的插槽 -->
</child>

(3)作用域插槽:父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译。不过,我们可以在父组件中使用slot-scope 特性从子组件获取数据。

<!-- 子组件 -->
<div>
  <slot :data="data"></slot>
</div>

<!-- 父组件 -->
<child>
  <span slot-scope="data">我是插槽插入的内容</span>
</child>

九. vue脚手架1. 应用场景:复杂的大型项目

  1. 应用场景:复杂的大型项目
  2. 脚手架2:
    • 全局安装(建议):npm install vue/cli-init@版本号 -g(init起到一个桥接的作用,便于想继续使用cli2)
    • 当前项目下安装:npm install vue/cli-init@版本号 -save
    • 初始化项目:vue init webpack project(自己取一个项目名)
    • 查看当前vue脚手架的版本:vue -V
  3. 脚手架3:
    • 全局安装(建议):npm install vue/cli@版本号 -g
    • 当前项目下安装:npm install vue/cli@版本号 -save
    • 初始化项目:vue create project(自己取一个项目名)
  4. 脚手架2和脚手架3的区别:
    • 基于的版本不同:cli2基于webpack3打造的,cli3基于webpack4
    • 设计原则不同:cli3的设计原则是0配置,移除配置文件根目录下的build和config
    • cli3提供了vue ui命令,提供了可视化配置,更加的方便直观
    • 移除static文件夹,新增了public文件夹,并且index.html移到了public文件夹中

十. vue修饰符

  1. @click.stop 阻止事件继续传播
  2. @click.prevent 阻止标签默认行为
  3. @click.capture 使用事件捕获模式,即元素自身触发的事件先在此处处理,然后才交由内部元素进行处理
  4. @click.self 只当在 event.target 是当前元素自身时触发处理函数
  5. @click.once 事件将只会触发一次
  6. @click.passive 告诉浏览器你不想阻止事件的默认行为

十一. 路由

  1. 原理:根据url和ui的映射关系,通过改变url达到更新ui的效果(无需刷新页面)

  2. 实现:

    • hash实现:通过hashchange事件监听url的变化,利用location.hash=xxx 来读取属性,本质上就是改变window.location的href属性。
    • history实现:
      • history.go(0) 刷新当前页面
      • history.back() === history.go(-1) 返回上一级
      • history.forward === history.go(1) 前进一级
      • history.pushState(stateObject,’ ',‘url’) 改变url的path部分,不引起页面刷新
      • history.repalceState(stateObject,’ ‘,’ ') 用url代替当前的路由,浏览器不能来回切换,不刷新页面。
    • abstract:支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式。
  3. vue-router

    • 目前流行的三大框架都有自己的路由实现
      • angular:ngRouter
      • react:ReactRouter
      • vue:vue-router
    • vue-router的作用:用于访问路径,将路径和组件之间映射起来。
    • vue-router的使用
      • 安装:npm install vue-router@版本号 --save
      • 导入路由对象并安装路由插件(在路由js文件中)
        • import Vue from ‘vue’
        • import VueRouter from “vue-router”
        • Vue.use(VueRouter)
      • 创建路由实例,配置映射关系:const router=new VueRouter({[配置的路由映射,可以提取出来些],mode:'history'})
      • 在main.js中挂载路由实例:import router from ‘./router’
      • 通过<router-link>或者<router-view>的形式来使用路由
        • <router-link> 内置组件会被渲染成一个a标签
        • <router-view> 会根据当前路径动态的渲染不同的组件
  4. 路由参数的传递

    • params类型:只能通过name引入
    • query类型:既能通过name也能通过path引入
  5. 路由跳转
    (1)通过<router-link :to="{}">
    (2) 通过this.$router.push({})

<!-- query传参 -->
<router-link :to="
	{
    	path:'/user/'+userId,
     	query:{name:"beixi",age:18}
 	}">
</router-link>

<!-- params传参 -->
<router-link :to="
	{
    	name:'/user/'+userId,
     	params:{name:"beixi",age:18}
 	}">
</router-link>
// query传参
this.$router.push({
    
    
     path:'/user/'+userId,
     query:{
    
    name:"beixi",age:18}
})

// params传参
this.$router.push({
    
    
     name:'/user/'+userId,
     params:{
    
    name:"beixi",age:18}
})
  1. params传参和query传参的区别

    • params类型:只能通过name引入
    • query类型:既能通过name也能通过path引入
    • query传递显示参数(url那里)params传递不显示参数,参数在请求体内,params相对于query来说较安全一点。
    • query传值页面刷新数据还在,而params传值页面数据消失
    • 路由参数的获取不同,query:this.$route.query.name,params:this.$route.params.name
  2. $router$route的区别

    • $router:路由操作对象,只写对象,一般用于路由跳转。
    • $route:路由信息对象,只读对象,一般用于获取路由参数。

十二. computed和methods的区别

  1. computed:
    • 计算属性,里面的函数会进行缓存,必须要有返回值,只有在依赖项发生变化的时候会执行,否则调用的时候直接拿到return 的值,不会再次执行。
    • 里面的函数是以对象的属性形式存在,在调用的时候直接使用就行,例如:{ {msg}}。
    • 支持get/set操作。一般情况下我们只使用getter属性
computed:{
    
    
	result(){
    
    
	    return this.name
	}
}
// 等价于
computed:{
    
    
	result(){
    
    
		get:function(){
    
    
		    return this.name
		},
		set:function(newValue){
    
    
			this.firstName = newValue
		}
	}
}
  1. methods:
    • 方法,里面的函数不会进行缓存,每次调用都会被执行,不一定要有返回值。
    • 里面的方法必须使用函数调用的形式,例如:{ {msg()}}
  2. 使用场景:
    • 当某些依赖项不经常发生变化,仅仅需要取到一些返回值的时候,建议使用computed。
    • 当涉及到复杂运算的时候,建设使用computed

十三. computed和watch的区别

  1. 相同点:都是监听data里面的变量或者是由父组件传过来的props,然后执行响应的逻辑。
  2. 不同点:
    • computed里面的方法必须要有返回值,watch里面的方法不一定有返回值。
    • computed只有当依赖项发生变化的时候才会执行函数,具有缓存函数以对象的形式存在,使用的时候直接调用watch在每次监听的值发生变化的时候都会执行回调,不具有缓存。
    • computed默认第一次加载的时候就开始监听;watch默认第一次加载不做监听,如果需要第一次加载做监听,添加immediate属性,设置为true(immediate:true)。
    • computed不支持异步,有异步操作时无法监听数据变化;watch支持异步操作。
    • computed里面的属性是一对一或者多对一(一个属性受到一个或者多个属性影响),watch里面的属性是一对多(一个属性影响到多个属性)。
  3. 使用场景
    • 当涉及到复杂的计算场景,一个属性受到多个属性影响,如:购物车商品结算,使用computed。
    • 当一个属性影响到多个属性的时候,如:搜索条件变化,使用watch。

十四. watch

  1. 说明:检测vue实例中数据的变化,执行相应的逻辑,默认第一次加载的时候不监听。
  2. 键:就是需要监测的那个东西。
  3. 值:
    • 可以是一个执行函数,有两个参数,一个是变化前的一个是变化后的,如果只有一个默认是变化后的。
    • 可以是一个函数名,得用单引号包裹。
    • 可以是一个对象,这个对象有三个选项:
      (1)handler :一个回调函数,监听到变化时应该执行的函数。
      (2)deep :boolean值,是否深度监听。(一般监听时是不能监听到对象属性值的变化的,数组的变化可以听到),true表示开启深度监听。
      (3)immediate :boolean值,是否立即执行handler函数,true表示立即监听。
// 1.使用watch监听一个值
watch:{
    
    
	username:function(oldValue,newValue){
    
    
		console.log(newValue)
	}
	// 可以简写为
	username(oldValue,newValue){
    
    
		console.log(newValue)
	}
}

// 2.使用watch监听一个数组
watch:{
    
    
	arr(oldValue,newValue){
    
    
		console.log(newValue)
	}
}

// 3.使用watch监听一个对象

watch:{
    
    
	obj:{
    
    
		handle(oldValue,newValue){
    
    
			console.log(newValue)
		},
		deep: true,   // 开启深度监听
		immediate: true    // 立即监听
	}
}

十五. vue双向数据绑定的原理

  1. vue双向数据绑定是通过数据劫持并结合发布-订阅者模式来实现的。也就是说数据和视图是同步的,数据发生变化,视图跟着变化;视图发生变化,数据也会随之变化。
  2. 数据劫持:vue会遍历data中的所有property(属性),并使用Object.defineProperty(obj,属性值)把这些property全部转化为getter/setter。
  3. 发布-订阅者模式:每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。
const obj={
    
    
  name:'xiaoming'
}
Object.defineProperty(obj,'xiaoming',{
    
    
  get(){
    
    
    return obj.name
  },
  set(vakue){
    
    
    obj.name = value
  }
})

在这里插入图片描述
4. 检测变化时候的注意事项:由于数组和对象是引用类型,vue不能检测数组和对象的变化,但是可以通过一些特殊的方法来解决这个问题。
(1)对于对象:vue无法检测property的添加或移除,由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的。例如:

var vm = new Vue({
    
    
  data:{
    
    
    a:1	// 响应式
  }
})
vm.b = 2	// 非响应式

// 解决办法: 
// 添加属性:通过Vue.set(obj,'属性名','属性值')或者this.$set()的方式
Vue.set(vm.someObject, 'b', 2) 
this.$set(vm.someObject, 'b', 2)

// 删除属性:
Vue.delete(vm.someObject, 'b') 
this.$delete(vm.someObject, 'b')

// 有时你可能需要为已有对象赋值多个新 property,比如使用 Object.assign() 或 _.extend()。但是,这样添加到对象上的新 property 不会触发更新。在这种情况下,你应该用原对象与要混合进去的对象的 property 一起创建一个新的对象。
Object.assign(this.someObject, {
    
     a: 1, b: 2 })  // 不会触发更新
this.someObject = Object.assign({
    
    }, this.someObject, {
    
     a: 1, b: 2 })

(2)对于数组:当利用索引直接设置数组中的某一项,或者直接修改数组的长度是不生效。例如:

var vm = new Vue({
    
    
  data: {
    
    
    items: ['a', 'b', 'c']
  }
})
vm.items[1] = 'x' // 不是响应性的
vm.items.length = 2 // 不是响应性的

// 修改数组中的某一项:
// 解决办法:
// 方法一:this.$set(array,index,newValue)
this.$set(this.items,0,'x')

// 方法二:array.splice(index,1,newValue)
this.items.splice(0,1,'x')

// 修改数组长度:this.items.splice(newLength)
his.items.splice(2)

关于vue的响应式原理,更多内容请参考官网

十六. Object.defineProperty()和Proxy代理的区别

  1. object.defineProperty()和Proxy()分别是vue2和vue3中用于响应式原理中的方法。
  2. 语法不同:
// Object.defineProperty()
Object.defineProperty(obj,属性值,{
    
    
	get(){
    
    
		return obj.xxx
	},
	set(value){
    
    
		obj.xxx = value
	}
})

// Proxy():用于创建一个对象的代理,从而实现基本操作的拦截和自定义。
```javascript
const newProxy = new Proxy(target, handler)
// target:要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
// handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。
  1. Object.defineProperty监听的是对象的每个属性,需要遍历对象的每个属性,而Proxy监听的是对象本身。(如果出现嵌套的对象,Proxy 也是要递归进行代理的,但可以做惰性代理,即用到嵌套对象时再创建对应的 Proxy)
  2. Object.defineProperty无法监听对象的新增属性,无法监听直接通过索引值访问数组,Proxy可以。

十七. vue中父子组件的生命周期

  1. 渲染过程:
    父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子Mounted->父Mounted
  2. 子组件更新:
    父beforeUpdate->子beforeUpdate->子Update->父Update
  3. 父组件更新:
    父beforeUpdate->父Update
  4. 销毁过程:
    父beforeDestory->子beforeDestory->子Destory->父Destory

十八. diff算法

指的是对新旧虚拟节点进行对比,返回一个patch对象,用来存储两个节点不同的地方,最后通过记录的信息,来更新dom节点。

十九.loader和plugin的区别

  1. loader使得webpack具备了解析非js文件
  2. plugin用来给webpack扩展功能的,可以加载许多插件

二十. vue中如何进行依赖收集

  1. vue在初始化实例挂载之后会进行编译,通过renderFunction生成虚拟DOM。
  2. 通过get()和set()函数对数据劫持更新实现响应式原理。
  3. 每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。
  4. 具体的渲染流程就是遍历虚拟DOM节点将前后不同之处记录在patch对象中。最后根据patch对象中记录的消息去更新DOM树。实时更新,每次有变化都会记录更新一遍DOM树,react是先把所有的不同收集起来,在统一更改。)

二十一. 如何理解vue的模板编译原理

  1. 核心:将templete语法转成render函数。
  2. 步骤:
    • templete语法转成ast语法树 -parserHTML
    • 对静态语法做标记(某些节点不改变)
    • 重新生成代码 -codeGen,使用with语法包裹字符串

二十二. mixin的使用场景和原理

  1. 作用:抽离公共的业务逻辑,当组件初始化的时候会调用mergeOptions方法进行合并。如果混入数据和组件本身数据有冲突,采用本身数据为准。(vue3)中使用自定义hook。
  2. 缺点:容易造成命名冲突、数据冲突,数据来源不清晰。

二十三. 为什么在组件中data必须是一个函数

  1. 在new Vue() 单例模式下,不存在合并操作,data里面的数据都是独立不存在数据污染情况,因此data用一个对象就行。
  2. 在组件中涉及到组件之间的相互引用,容易造成数据污染的可能,data必须是一个函数,这样在数据合并的时候会产生两个空间,能够避免数据污染。

二十四. 请说明nextTick的原理。

  1. 是一个微任务。
  2. nextTick中的回调是在下次DOM更新循环结束之后执行的延迟回调
  3. 可用于获取更新后的DOM
  4. vue中的数据更新是异步的,使用nextTick可以保证用户定义的逻辑在更新之后执行。

二十五. 说说 Vue 中 CSS scoped 的原理

  1. 作用:使当前CSS样式只作用于当前组件,避免组件之间样式的相互干扰。
  2. 原理:通过PostCSS转译实现,
    • PostCSS给一个组件中的所有dom添加了一个独一无二的动态属性。
    • 给CSS选择器额外添加一个对应的属性选择器来选择该组件中dom。

二十六. 刷新浏览器后,Vuex的数据是否存在?如何解决?

  1. 不存在了,store里面的数据是保存在运行内存中的,当页面刷新的时候,页面会重新加载vue实例,store里面的数据就会被重新赋值
  2. 解决办法:
  • vuex-along 一个插件本质上也是将数据存放到localstorage或者sessionStorage中
  • 在数据存储在localStorage或者sessionStorage中。

二十七. hash路由和history路由

  1. hash路由
  • 通过hashchange事件监听url的变化,利用location.hash=xxx 来读取属性,本质上就是改变window.location的href属性。
  • hash路由在地址栏URL上有#显得不美观,而history路由没有会好看一点。
  • hash路由的兼容性好一点。
  • 在浏览器进行回车操作,hash路由会加载到地址栏对应的页面,不会进行http请求,改变hash不会重新加载页面,所以这也是单页面应用的必备。
//监听hash路由
window.addEventListener('hashchange', function(){
    
     
    // 监听hash变化,点击浏览器的前进后退会触发
 })
  1. history路由
    • history路由是通过调用history.pushState()或者history.replaceState()方法来进行路由跳转。
    • history路由在地址栏URL上没有#,显得整洁美观
    • history路由的兼容性较hash路由差一点
    • 在浏览器进行回车操作,history路由会进行http请求,因此需要后端数据,否则会报错。
    • history路由运用了浏览器的历史记录栈,它们提供了对历史记录进行修改的功能,不过在进行修改时,虽然改变了当前的URL,但是浏览器不会马上向后端发送请求。

二十八. vue的页面渲染流程,挂载的过程中发生了什么

  1. 在实例化的时候调用init()初始化
  2. 调用$mount()挂载,实际上就是调用mountComponent,除了调用一些生命周期钩子函数,最主要是updateComponent。
  3. updateComponent会被传入渲染watcher,每当数据变化的时候触发watcher更新就会执行该函数。
  4. vm._render 创建并返回VNode,vm._update 接受VNode将其转化为真实节点。

二十九. 怎么自定义指令

  1. 全局注册,第一个参数:指令名称;第二个参数:指令函数
Vue.directive('focus',{
    
    
  inserted:function(el){
    
    
    el.focus()
  }
})
  1. 局部注册:在当前组件中注册
directives:{
    
    
  focus:{
    
    
    inserted:function(el){
    
    
      el.focus()
    }
  }
}
  1. 使用
<div v-focus />

三十. vuex的辅助函数

state辅助函数为mapState(),actions辅助函数为mapActions(),mutations辅助函数为mapMutations(),getters辅助函数为mapGetters()。

三十一.谈谈你对你vue中keep-alive的理解

  1. 被keep-alive包裹的组件具有缓存效果
  2. 是vue的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染DOM。
  3. 两个参数:
  • include=‘组件名’:指定只有这些组件会被缓存
  • exclude=‘组件名’:指定这些组件不会被缓存,剩余组件都会被缓存

三十二. 虚拟DOM一定更快吗

  1. 什么叫DOM:Document Object Model,文档对象模型,是一套用于管理html文档的规则,一个DOM树就是一个html完整文档。
  2. 作用:将web网页和编程语言连接起来,通过操作脚本以改变文档的结构、样式和内容。
  3. 什么叫虚拟DOM:js对象模拟出来的DOM节点,是一种编程概念,一种模式,他会和真实DOM同步
  4. 虚拟DOM可以提升性能但不一定更快。(点击按钮,加一。直接操作真实DOM更快)
  5. 真实DOM在直接操作的时候会引起页面的回流或重绘,会造成性能下降,虚拟DOM通过diff算法更新不同虚拟DOM节点,进而达到重新渲染的目的
  6. 优点:
    • 减少dom操作,提高了性能,更新快
    • 无需手动操作DOM只需要关心view-modal里面的逻辑
    • 更方便的跨平台操作,例如服务器渲染
  7. 缺点:
    • 无法进行极致优化,对于性能要求极高的应用中,虚拟DOM无法针对性的极致优化
    • 首次渲染大量dom时,由于多了一层虚拟dom的计算,速度比innerHtml插入慢

三十三. 谈谈对key的理解

  1. 概念:给每一个节点一个唯一的标识,可以根据key值找到对应的节点
  2. 作用
    • 尽可能的复用DOM更高效的更新虚拟DOM
    • 如果不使用key:vue会尽可能地修改/复用相同类型元素的算法
    • 如果使用key:会基于key的变化重新排列元素顺序,并且会移除key不存在的元素
  3. 注意:如果在dom非常简单的前提下,使用key也不一定就能提高diff算法
  4. 为什么一般不使用index作为key:如果做了一些删除和添加操作,会使得部分key对应不上,造成一些没有必要的dom更新,渲染效率低下。

三十四. 首屏加载慢的原因

  1. 什么是首屏加载慢:从用户在浏览器输入网址,到首屏内容渲染完成所用的时间过长。
  2. 加载慢的原因:
    • 网络延迟
    • 资源文件体积过大
    • 资源是否重复发送请求
    • 加载脚本的时候,渲染内容堵塞
  3. 解决方案
    • 减小入口文件体积
    • 静态资源本地缓存
    • 资源按需加载、异步加载
    • 图片资源的压缩懒加载
    • CSS代码放在头部,JS代码放在尾部
    • 开启GZip压缩

三十五. 为什么vue中的v-if和v-for不建议一起用?

  1. 在vue2中v-for的优先级高于v-if。一起使用的时候每次判断之前都要遍历一边,浪费性能。
  2. 在vue3中v-for的优先级低于v-if。一起使用的时候会直接报错。
  3. 解决办法:分开写,将v-if写到外层,可以用一个templete标签包裹起来。

三十六. v-show 和 v-if 的区别

  1. 相同点:都是控制元素在页面是否显示
  2. 不同点:
    • v-show本质上是通过控制css的display:none属性。即使隐藏了,dom结构依然还在,是一种视觉上的隐藏v-if是真正意义上的删除和添加。
    • v-show不会触发生命周期钩子,v-if会触发。
    • v-show对性能消耗较少,v-if对性能消耗较大,因此当需要频繁的切换的时候建议使用v-show。

三十七. 观察者模

  1. 在vue初始化的时候通过observe劫持监听属性。
  2. 定义更新函数和watcher。
  3. 一般会有多个watcher所以会用一个管家dep来管理多个watcher。
  4. 当data里面的数据发生变化的时候会找到对应的管家dep,来通知所有的watcher调用更新函数,更新dom。

三十八. 发布订阅者模式

  1. 触发事件的对象称为发布者
  2. 接收通知的对象称为订阅者
  3. 举个例子:js中的事件绑定就是发布订阅模式

三十九. 父组件怎么监听子组件的生命周期

  1. $emit在子组件的生命周期钩子里面用$emit触发事件,父组件里用v-on接收
// 子组件
mounted(){
    
      // 监听子组件的挂载完成
	this.$emit('mounted')
}
// 父组件监听后执行一些相应的操作
<Child @mounted='doSomething' />
methods:{
    
    
	doSomething(){
    
    }
}
  1. @hook
// 子组件
mounted(){
    
    
	console.log('111')
}
// 父组件监听后执行一些相应的操作
<Child @hook:mounted='doSomething'>
methods:{
    
    
	doSomething(){
    
    }
}

四十. vue组件通信

  1. 父子组件通信父组件通过v-bind绑定传递的值,子组件中通过props接收。子组件中通过$emit('函数名',传递的值),父组件中通过v-on接收函数并定义。
// 父组件,以下都是部分代码
<div class="box" @click="clickAction">
   我是父组件:{
    
    {
    
    msg}}
</div> 
<Child :count="count" @message="message"/>
data(){
    
    
	return {
    
    
      msg:'你好,我的世界',
      count: 0
	}
},
methods:{
    
    
	// 向子组件传值
 	clickAction(){
    
    
       ++this.count
    },
    // 接收子组件传过来的值
	message(data){
    
    
	   this.msg = data
	}
}

// 子组件
<button @click="clickChild">我是子组件:{
    
    {
    
    count}}</button>
// 通过props接收父组件传过来的值
props: {
    
    
    count: {
    
    
        type:Number,
        default:0
    }
},
data(){
    
    
   return {
    
    
      msg: ''
    }
},
methods:{
    
    
	// 通过$emit向父组件传值
    clickChild(){
    
    
      this.$emit('message','这是我在测试父子组件通信')
    }
}

在这里插入图片描述在这里插入图片描述
2. 祖孙组件之间的通信:在传递层使用 v-bind="$attrs" v-on="$listeners"配合 inheritAttrs: false一起使用。

  • v-bind="$attrs:包含了父组件中不被props所识别的特性绑定,如果当前组件没有声明props则会继承父组件的所有绑定的值。
  • v-on=“$listeners”:包含了父作用域中的(不含.native修饰器的)v-on事件监听器
  • inheritAttrs: false 可以关闭自动挂载到组件根元素上的没有在 props 声明的属性参见
// 子组件
<GrandChildA v-bind="$attrs" v-on="$listeners"/>

// 孙子组件
<div @click="clickGrandSonA">这是孙子组件A{
    
    {
    
     $attrs }}</div>
methods:{
    
    
 clickGrandSonA(){
    
    
     this.$emit("message",'这是测试祖孙组件之间的通信');
  }
}

在这里插入图片描述
3. 利用provide和inject实现祖孙组件通信
provide:Object | () => Object,是一个对象或者是一个返回对象的函数。
inject:Array | { [key: string]: string | Symbol | Object },是一个字符串数组,或者是一个对象,对象的key是本地的绑定名。
具体用法:

// 父组件:
<div class="box" @click="clickAction">
   我是父组件:{
    
    {
    
    msg}} -- {
    
    {
    
     coo }} -- {
    
    {
    
     obj.name }}
</div>
data(){
    
    
   msg:'你好,我的世界',
   count: 0,
   coo: "CSS",
   obj:{
    
    
     name:'wangjiajia'
   }
},
provide(){
    
    
  return {
    
    
    msg:this.msg,   // 方式一:直接传值
    obj:this.obj,   // 方式二:传入一个可以监听的对象
    computedCount:()=>this.count,   // 方式三:通过computed来计算注入的值
    app:this    // 方式四:提供祖先组件的实例,缺点是造成传递过去很多没用的东西,冗余。
  }
},
methods:{
    
    
  clickAction(){
    
    
    ++this.count,
    this.msg = '世界真美好',
    this.obj.name = 'wangtongtong',
    this.coo = '通过app引用'
  },
}

// 子组件
<GrandChildB />   // 只是一个过渡的作用

// 孙子组件
<div @click="clickGrandSonB">
        我是孙子组件B{
    
    {
    
     msg }} -- {
    
    {
    
     count }} -- {
    
    {
    
     this.app.coo }} --{
    
    {
    
       obj.name }}
</div>
computed:{
    
    
 count(){
    
    
   return this.computedCount()
 }
},
inject:['msg','computedCount','app','obj'],
methods:{
    
    
  clickGrandSonB(){
    
    
    this.msg = '测试祖孙组件传值'
    this.obj.name = 'caocongyouren'
    this.app.coo = '你礼貌吗!'
  }
}

在这里插入图片描述
在这里插入图片描述在这里插入图片描述
说明:这种方法下要注意,直接传值是不可以实现响应式的,实现数据响应式具体方法有:
* 传入监听对象
* 利用computed计算属性注入
* 传入整个组件实例(缺点是可能传入一些没用的量,造成冗余)

  1. 利用中央事件总线实现任意组件之间的通信
    • 全局注册:Vue.prototype.$bus = new Bus() / new Vue()
    • 发射事件:this. b u s . bus. bus.emit(‘foo’)
    • 接收事件:this. b u s . bus. bus.on(‘foo’)
// 父组件
mounted(){
    
    
  this.$bus.$on('mooValue',this.receive)
},

methods:{
    
    
  clickAction(){
    
    
    this.$bus.$emit('dooValue',"Vue")
  }
  receive(data){
    
    
  	this.moo = data
  }
}

// 孙子组件
mounted(){
    
    
  this.$bus.$on('dooValue',this.receive)
},
methods:{
    
    
  clickGrandSonB(){
    
    
	this.$bus.$emit('mooValue','React')
  },
  receive(data){
    
    
  	this.doo = data
  }
}

在这里插入图片描述

四十一. vuex

  1. vuex是什么,为什么要用vuex
    • vuex是vue官方提供的一种集中式管理vue多组件共享状态数据的插件。
    • vuex可以帮助我们管理共享状态,实现跨组件通信。
    • 适用于较大类型的vue项目
  2. 安装方式
    • 在脚手架创建项目的时候可以选择安装
    • 使用npm或者yarn安装
      • npm install vuex@next --save
      • yarn add vuex@next --save
  3. 配置
    • 创建一个store文件夹,在文件夹下面创建一个index.js文件
    • 在main.js文件中引入并挂载到vue实例上。
import Vue from 'vue
import App from './App.vue'
import store from './store'
new Vue({
    
    
	store,
	render:h=>h(App)
}).$mount('#app')
  1. 核心概念

    • State
    • Getters
    • Mutations
    • Actions
    • Modules
  2. 图解关系
    在这里插入图片描述

  3. State:提供唯一的公共数据源,所有共享的数据统一放到store的state进行储存,相似与data。

    • 方法一:在标签中直接使用:<p>{ {$store.state.name}}<p>
    • 方法二:通过this调用:this.$store.state.name
    • 方法三:通过辅助函数:mapState(['name','age','count'])写在当前组件的computed中
  4. Mutations:改变数据的唯一方法,接受state作为第一个参数,第二个参数可选

    • 方法一:通过commit触发:this.$store.commit('mutations里面定义的函数名字','可选参数')
    • 方法二:通过辅助函数:mapMutations(['函数名']) => this.函数名('可选参数') => 写在methods中。
  5. Actions:异步操作存放处,本质上还是会触发mutations来改变数据。

    • 方法一:使用dispatch触发:this.$store.dispatch("mutations里面定义的函数名字",'可选参数')
    • 方法二:通过辅助函数:mapActions['函数名'] => this.函数名('可选参数') => 写在methods中。
  6. Getters:对数据进行加工处理形成新的数据,具有缓存

    • 方法一: 在当前组件的computed中定义一个函数,在这个函数中直接使用this.$store.getters.'getters中定义的函数名'
    • 方法二:使用辅助函数:mapGetters(['函数名']),写在当前组件的computed中。
      注意:辅助函数都可以配置别名
  7. Modules:当遇到大型项目,数据量比较复杂。将每一个状态写在一个文件中,该文件具有自己的state,mutations等,然后在modules中导入,

  8. 看一个具体的例子熟悉vuex的使用

store.js文件

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
    
    
  // state: 共享数据,相当于data
  state: {
    
    
    name: 'wangjiajia',
    age: 25,
    count: 0,
    todos: [
      {
    
     id: 1, done: true },
      {
    
     id: 2, done: false },
      {
    
     id: 3, done: false },
    ]
  },
	// getters:对数据进行加工处理形成新的数据,具有缓存
	getters: {
    
    
		// 过滤出done为true的项
	    doneTodos: state => {
    
    
	      return state.todos.filter(item => item.done)
	    },
	    // 传入一个id值,从当前数组中找到第一个id等于传入值的项
	    getTodoById: (state) => (id) => {
    
    
	      return state.todos.find(item => item.id === id)
	    }
 	},
 	// mutations:改变state中数据的唯一办法
 	mutations: {
    
    
 	  // 传入一个值,使得count每次加上这个值
      addCount(state,num){
    
    
        state.count += num
      },
      // 使得count每次自减1
      reduceCount(state){
    
    
        --state.count
      },
      ['ADDCOUNT'](state){
    
      // 可以配置方法别名
      ++state.count
    }
    },
    // actions: 异步操作,本质上也是通过触发mutations从而改变state里面的数据
    actions: {
    
    
	    asyncAdd(context,data){
    
    
	      setTimeout(()=>{
    
    
	        // 可以调用mutation里面的方法
	        context.commit('addCount',data)
	
	        // 可以调用另一个异步
	        // context.dispatch('asyncReduce')
	      },1000)
	    },
	    asyncReduce(context){
    
    
	      setTimeout(()=>{
    
    
	        // 可以调用mutation里面的方法
	        context.commit('reduceCount')
	      },1000)
   	 },
   	 // modules:复杂业务时候,将每一个状态单独写在一个文件中,最后在这里导入
   	 modules:{
    
    
		app,
		cell
    }
  },
})

vue文件:

<button @click="clickAdd">点我增加</button>
    <div class="direct">直接引用:{
    
    {
    
     $store.state.name }} -- {
    
    {
    
     $store.state.age }} -- {
    
    {
    
     $store.state.count }} -- {
    
    {
    
     doneTodos }} -- {
    
    {
    
     getTodoById(2) }}
 </div>
<button @click="clickReduce">点我减少</button>
<div class="direct">通过辅助函数:{
    
    {
    
     name }} -- {
    
    {
    
     age }} -- {
    
    {
    
     count }} -- {
    
    {
    
     doneTodosCount }} -- {
    
    {
    
     doneTodosId }}</div>
<button @click="clickAsync">点我触发一个异步任务</button>

import {
    
     mapActions, mapMutations, mapState,mapGetters } from 'vuex';
export default ({
    
    
	name:'ChildStore',
	computed:{
    
    
    ...mapState(['name','age','count']),  // mapState({name:state=>state.name,age:'age'})
    // 使用辅助函数
    ...mapGetters(['doneTodos','getTodoById']),    // 可以配置别名:mapGetters({doneCount:'doneTodos'})
    doneTodosCount () {
    
    
      return this.$store.getters.doneTodos
    },
    doneTodosId () {
    
    
      return this.$store.getters.getTodoById(2)
    }
  },
  methods:{
    
    
    // 辅助函数
    ...mapMutations(['addCount','reduceCount','ADDCOUNT']),  // 可以配置别名:mapMutations({increment:'addCount'})
    ...mapActions(['asyncAdd','asyncReduce']),    // 可以配置别名:mapActions({increment:'asyncAdd'})
    clickAdd(){
    
    
      this.$store.commit('addCount',5)  // 每次增加5,通过commit
      // this.addCount(5)   // 通过辅助函数
    },
    clickReduce(){
    
    
      // this.$store.commit('reduceCount')  // 每次减少1,通过commit
      this.reduceCount()   // 通过辅助函数
      // this.$store.commit('ADDCOUNT')
      this.ADDCOUNT()  // 方法别名
    },

    //异步操作
    clickAsync(){
    
    
      // this.$store.dispatch('asyncAdd',5)   // 通过dispatch触发一个增加的异步操作
      // this.$store.dispatch('asyncReduce')   // 通过dispatch触发一个减少的异步操作
      this.asyncAdd(5)   // 通过辅助函数
      // this.asyncReduce()   // 通过辅助函数
    }
  }
})

在这里插入图片描述
更过关于vuex的相关内容请大家参考官网

猜你喜欢

转载自blog.csdn.net/du_aitiantian/article/details/128400634