Vue

一、vue-router的实现原理

路由的概念来源于服务端,服务端中的路由描述的是URL和处理函数之间的映射关系;web前端单页应用SPA(single page application)中,路由描述的是URL和UI之前的映射关系,这种映射是单向的,即URL的变化引起UI更新;

如何实现前端路由?
解决两个核心问题:

  • 如何监测URL发生变化?
  • 如何改变URL却不引起页面刷新?

使用hash和history两种实现方法可以解决上面两个问题;

  • hash实现
    • hash是URL中hash(#)后面的内容,改变URL的hash部分不引起页面刷新;
    • hash通过hashchange事件监听URL的变化;改变URL的方式有:window.location、a标签、浏览器的前进后退 ;这几种方式都可以被hashchange监听到;
  • history实现
    • history提供了pushState和replaceState两种方法,这两种方法改变URL的pash部分不会引起页面的刷新;
    • history提供类似hashchange事件的popstate事件,用来监听URL的变化,但是只能监听到浏览器前进后退改变的URL,不能监听a标签、以及pushState和replaceState改变的URL;
    • 解决办法:拦截pushState和replaceState的调用以及a标签的点击事件来检测URL的变化;
<a href="https://jingyan.baidu.com/">百度经验</a> 
// 默认在当前页打开链接网页
<a href="https://jingyan.baidu.com/" target="_blank">百度经验</a>
// 加上target,在新的标签页打开链接网页

二、Vue的双向绑定原理和生命周期函数

Vue的双向绑定首先就是MVVM(model-view-viewmodel)模式;当视图发生改变的时候传递给VM,再让数据得到更新,当数据发生改变时传给VM,使得视图发生改变。
MVVM模式通过以下三个核心组件组成,每个都有它独特的角色:

  • Model:包含了业务和验证逻辑的数据模型;
  • view:定义了屏幕中view的结构,布局和外观;
  • viewmodel:扮演model和view之间的使者,帮忙处理view的全部业务逻辑;
    在这里插入图片描述
    vue的数据双向绑定主要通过Object.defineProperty(obj对象,prop属性,descriptor具体的改变方法)方法,进行数据劫持以及发布者——订阅者模式;

任务拆分:

1)组件初始化时,利用Object.defineProperty(在一个对象上定义一个新的属性)方法,给每一个data属性注册getter和setter方法,也就是reaction化(数据劫持);

2)在mount阶段会创建watcher类(每个组件对应一个watcher),然后再new一个watcher对象,watcher对象会立即调用组件的render函数生成虚拟DOM,在调用render函数时会访问data的属性值,此时会触发属性的getter函数,getter函数将当前的watcher注册到属性的dep中;watcher在组件渲染的过程中把“接触”过的data属性记录为依赖;

3)之后当data属性发生变化时,触发setter函数,之后调用dep的notify函数,通知watcher调用updata函数进行更新,从而使它关联的组件重新渲染。

  • 实现一个解析器Compile,可以扫描和解析每个节点的相关指令,把它们初始化成一个订阅者Watcher,并且绑定相应的函数。
  • 实现一个监听器Observer,用来劫持并监听所有属性,如有变动,就通过订阅者管理器(Dep,每个data属性都有一个dep)来通知订阅者Watcher;(每一个data的属性都会有一个dep对象)
  • 实现一个订阅者Watcher,(一个组件对应一个watcher)可以收到属性的变化通知,并执行相应的patch函数,从而更新视图;(watcher在mounted阶段生成)

三、Vue的生命周期

创建阶段:

  • beforeCreate:data和methods中的数据还没有被初始化;
  • created:data和methods已经被初始化好;调用methods中的方法,最早在created中操作;
  • beforeMount:模板在内存中编译完成,但尚未把模板渲染到页面中,此时页面还是旧的;
 beforeMount() {  
    console.log(document.getElementById('h3').innerText)  
    //{{msg}}
}, 
  • mounted:内存中编译好的模板替换到浏览器的页面中;(如果想要通过某些插件操作页面上的DOM节点,最早要在mounted中进行);执行完mounted,就表示整个Vue实例已经初始化完了;此时组件脱离了创建阶段,进入到了运行阶段。
mounted() {    
    console.log(document.getElementById('h3').innerText)  
    //ok
}, 

运行阶段:

  • beforeUpdate:页面中的显示的数据,还是旧的;此时data数据是最新的;页面尚未和最新的数据保持同步。
  • updated:页面和data数据已经保持同步了,都是最新的。

销毁阶段:

  • beforeDestroy:实例身上所有的data和所有的methods,以及过滤器,指令都处于可用阶段,此时,还没有真正执行销毁的过程。
  • destroyed:组件完全被销毁。

四、页面之间的通信

子组件Scroll.vue

<template>
    <div class="wrapper" ref='wrapper'>
      <div class='content'>
        <slot></slot>
      </div>
    </div>
</template>

<script>
import BScroll from 'better-scroll'
export default {
    name: 'Scroll',
    props: {
      probeType: {
        type: Number,
        default: 0
      },
    },
    data() {
      return {
        scroll: null,
      }
    }
 </script> 

父组件:Detail.vue

<template>
    <div id="detail">
        <scroll class="scroll-content" ref="scroll"
         :probe-type="2" @scroll="contentScroll" >
              <detail-swiper :top-images="topImages"/>
              <goods-list :goods="recommends" ref="recommend"/>
        </scroll>
    </div>
</template>

<script>
import Scroll from '@/components/common/scroll/Scroll'
export default {
  name: "Detail",
  components: {
    Scroll
  },
</script>

五、Vue2

data() {
	return {xx: xx, yy:yy}
},
methods: {
	login() { },
	layout() { }	
},
mounted() {	
	this.login()
},
computed:{	
    lowerCaseUsername () {
      	return this.username.toLowerCase()
    }
} 

六、Vue中的data为什么要使用函数?

七、动态绑定

<div v-bind:class="{ active: isActive }"></div>

八、项目的创建

1、创建项目时可以选择runtime-only和runtime-compiler

  • runtime-only:代码中不可以任何的template
  • runtime-compiler:代码中,可以有template,因为compiler可以来编译template

在这里插入图片描述
vue-template-compiler可以将template解析成ast,再将ast编译为render函数,render函数可以将template翻译成虚拟DOM树,之后将虚拟DOM树渲染成真实DOM树;

使用runtime-compiler:template => ast => render函数 => 虚拟DOM树 => 真实DOM树;
使用runtime-only:render函数 => 虚拟DOM树 => 真实DOM树;

九、vue中的keep-alive:

kee-alive 是 Vue 内置的一个抽象组件,它自身不会渲染一个DOM元素;使用keep-alive包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。可以使被包含的组件保留状态,或避免重新渲染 。也就是所谓的组件缓存;

使用keep-alive组件保留了组件状态且不会重新渲染,这样会导致created(){ } 钩子函数不会再次触发;

keep-alive不仅仅能保存页面 / 组件的状态这么简单,还可以避免组件反复创建和渲染,有效提升系统性能。

include定义缓存白名单,keep-alive会缓存命中的组件;exclude定义缓存黑名单,被命中的组件将不会被缓存;

keep-alive实现原理 原文

export default {
  name: 'keep-alive',
  abstract: true, 
  // 判断当前组件虚拟dom是否渲染成真实dom的关键,true不渲染
  props: {
      include: patternTypes, // 缓存白名单
      exclude: patternTypes, // 缓存黑名单
      max: [String, Number] // 缓存的组件
  },
  created() {
     this.cache = Object.create(null) // 缓存虚拟dom
     this.keys = [] // 缓存的虚拟dom的键集合
  },
  destroyed() {
    for (const key in this.cache) {
       // 删除所有的缓存
       pruneCacheEntry(this.cache, key, this.keys)
    }
  },
 mounted() {
   // 实时监听黑白名单的变动
   this.$watch('include', val => {
       pruneCache(this, name => matched(val, name))
   })
   this.$watch('exclude', val => {
       pruneCache(this, name => !matches(val, name))
   })
 },

 render() {
    // 先省略...
 }
}

keep-alive在它生命周期内定义了三个钩子函数:

  • created:初始化两个对象cache和keys,分别缓存VNode(虚拟DOM)和Vnode对应的键集合
  • destroyed:删除this.cache中缓存的VNode实例;这不是简单地将this.cache置为null,而是遍历调用pruneCacheEntry函数删除。删除缓存的VNode还要对应组件实例的destory钩子函数
  • mounted:在mounted这个钩子中对include和exclude参数进行监听,然后实时地更新this.cahce对象数据;
  • 取出keep-alive包裹着的子组件对象,按照黑白名单进行匹配,决定是否缓存;在缓存对象中查找是否缓存过该组件实例;最后讲该组件实例的keepAlive属性值设为true

vue渲染:render函数将组件对象转化为一个VNode实例,之后调用update函数把Vnode渲染成真实DOM;这个工程调用patch函数完成;

activated // 用于keep-alive激活
 activated() {
  //刚进入该组件时,执行
  // console.log('组件刚进来')
  this.$refs.scroll.scrollTo(0, this.saveY, 0)
  this.$refs.scroll.refresh()  
},
destroyed // 用于keep-alive停用
deactivated() {
  //离开该组件时,执行
  //1.保存Y值
  this.saveY = -this.$refs.scroll.getScrollY
  //2.取消全局事件的监听
  this.$bus.$off('itemImageLoad', this.itemImgListener)
},

新的问题:keep-alive不会生成真正的DOM节点,因为Vue在初始化生命周期的时候,为组件实例建立父子关系会根据abstract属性决定是否忽略某个组件;keep-alive中设置了abstract:true,那么Vue就会跳过该组件实例。

被缓存的组件实例会为其设置keepAlive = true,而在初始化组件钩子函数中,不会再进入$mount过程,那么mounted之前的所有钩子函数(beforeCreate、created、mounted)都不再执行

十、v-if和v-show的区别:

1、相同点:v-if和v-show都可以动态控制DOM元素的显示与隐藏

2、不同点:

  • v-if动态地向DOM树中添加或删除DOM元素节点;

  • v-show通过向DOM元素设置样式display属性值控制显示隐藏。
    当两个demo用一个button按钮执行函数控制v-if和v-show的值,在控制台可以看出,两值都为false时,v-if控制的元素直接从DOM树销毁,而v-show控制的元素还是在DOM树中,只是以display:none的样式隐藏元素内容;
    在这里插入图片描述

  • 编译方面:v-if惰性,若最初指令值为false,不会编译;直至指令值为true时,才会编译存入缓存;
    v-show不管最初指令值为真还是为假,都会马上编译存入缓存;

  • 消耗方面:v-if切换性能消耗较大;v-show最初渲染消耗较大;

  • 适用场景:v-if适用切换条件、项目需求稳定;v-show使用与频繁需要切换;

  • 语法方面:v-if可以v-else、v-else-if配合使用,进行判断执行;但一定需要相邻,不可中断。

拓展

display:none; 不在文档流,无法触发绑定的事件,不会被子元素继承;但是父元素都不在了,子元素肯定也不在了~

visibilty:hidden; 会被子元素继承;在文档流中,会占据位置,但是不能触发绑定的事件;由于父元素隐藏,所以子元素也会隐藏,为了使子元素不被隐藏,可以使用visibility:visible

opacity:0; 在文档流中,会占据位置;绑定的事件也可以触发。

十一、监听input中value属性值发生变化的事件

1、onchange事件:

此事件会在元素内容发生改变,且失去焦点的时候触发。
浏览器支持度较好。

2、onpropertychange事件:

此事件会在元素内容发生改变时立即触发,即便是通过js改变的内容也会触发此事件。

元素的任何属性改变都会触发该事件,不止是value。只有IE11以下浏览器支持此事件。

3、oninput事件:

此事件会在value属性值发生改变时触发,通过js改变value属性值不会触发此事件。只有IE8以上或者谷歌火狐等标准浏览器支持。

十二、keep-alive组件的使用

参考 https://www.cnblogs.com/answershuto/p/7825022.html

是Vue.js一个内置组件;它能够将不活动的组件实例保存在内存中,而不是直接将其销毁,它是一个抽象组件,不会被渲染到真实DOM中也不会出现在父组件链中;它提供了includeexclude两个属性,允许组件有条件地进行缓存;

<keep-alive>
    <component></component>
</keep-alive>

这里面的component组件会被缓存起来。

举例说明:

<keep-alive>
    <coma v-if="test"></coma>
    <comb v-else="test"></comb>
</keep-alive>
<button @click="test=handleClick">请点击</button>

export default {
    data () {
        return {
            test: true
        }
    },
    methods: {
        handleClick () {
            this.test = !this.test;
        }
    }
}

在点击button时候,coma与comb两个组件会发生切换,但是这时候这两个组件的状态会被缓存起来,比如说coma与comb组件中都有一个input标签,那么input标签中的内容不会因为组件的切换而消失。

1、include与exclude属性

keep-alive组件提供了include与exclude两个属性来允许组件有条件地进行缓存,二者都可以用逗号分隔字符串、正则表达式或一个数组来表示。

<keep-alive include="a">
  <component></component>
</keep-alive>
将缓存name为a的组件
<keep-alive exclude="a">
  <component></component>
</keep-alive>
name为a的组件将不会被缓存

2、keep-alive提供的生命钩子

activated与deactivated

keep-alive会将组件保存在内存中,并不会销毁以及重新创建,所以不会重新调用组件的created等方法;需要使用activated与deactivated两个生命钩子来得知当前组件是否处于活动状态;

3、深入keep-alive组件实现

created钩子会创建一个cache对象,用来作为缓存容器,保存vnode节点;

created () {
    /* 缓存对象 */
    this.cache = Object.create(null)
},

destroyed钩子则在组件被销毁的时候清除cache缓存中的所有组件实例。

/* destroyed钩子中销毁所有cache中的组件实例 */
destroyed () {
    for (const key in this.cache) {
        pruneCacheEntry(this.cache[key])
    }
},

4、render函数

首先通过getFirstComponentChild获取第一个子组件,获取该组件的name(存在组件名则直接使用组件名,否则会使用tag)。接下来会将这个name通过include与exclude属性进行匹配,匹配不成功(name不在inlcude中或者在exlude中,说明不需要进行缓存)则不进行任何操作直接返回vnode,vnode是一个VNode类型的对象,不了解VNode的同学可以参考笔者的另一篇文章《VNode节点》。

5、watch

用watch来监听pruneCache与pruneCache这两个属性的改变,在改变的时候修改cache缓存中的缓存数据。

6、总结

Vue.js内部将DOM节点抽象成了一个个的VNode节点,keep-alive组件的缓存也是基于VNode节点的而不是直接存储DOM结构。它将满足条件(pruneCache与pruneCache)的组件在cache对象中缓存起来,在需要重新渲染的时候再将vnode节点从cache对象中取出并渲染。

十三、element.js表单验证

在失去焦点时,进行规则的验证,并且发送ajax请求,在数据库中搜索,用户名有没有被注册;

password: [
 { required: true, message: '密码不能为空', trigger: 'blur' },
 { type: 'string', min: 6, message: '密码不允许小于6位', trigger: 'blur' }
],

十四、async / await

来源:https://www.jianshu.com/p/72e36168944f

async/await 执行原理详解

async函数就是generator函数的语法糖。

async函数,就是将generator函数的 * 换成async,将yield替换成await

1、async函数对generator的改进

  • 内置执行器,不需要使用next()手动执行。

  • await命令后面可以是Promise对象或原始类型的值,yield命令后面只能是Thunk函数或Promise对象。

  • 返回值是Promise;返回非Promise时,async函数会把它包装成Promise返回(Promise.resolve(value))

2、async函数作用

异步编程的终极解决方案

3、通俗理解

async/await,就是异步编程回调函数写法的替代方法。(使代码以同步方式的写法完成异步操作)

4、执行顺序

  • 原理

函数执行时,一旦遇到await就会返回。等到触发的异步操作完成(并且调用栈清空),再接着执行函数体内后面的语句。

await语句后面的代码,相当于回调函数。(即:await的下一行开始,都视作回调函数的内容)

async showSetRightDialog(role) {
  const { data: res } = await this.$http.get('rights/tree')
  if (res.meta.status !== 200) {
    return this.$message.error('获取权限列表失败')
  }
  this.rightsList = res.data
  // console.log(this.rightsList)
  this.getLeafKeys(role, this.defKeys)
  this.dialogVisible = true
  this.roleId = role.id
}

回调函数会被压入microtask队列,当主线程调用栈被清空时,去microtask队列里取出各个回调函数,逐个执行。

await只是让当前async函数内部的代码等待,并不是所有代码都卡在这里。遇到await就先返回,执行async函数之后的代码。

  • await执行细节

(2019.8.20测试发现:浏览器实际执行结果发生变化。await后面的函数,无论返回promise、还是非promise,执行结果都与曾经返回非promise相同)

主线程执行过程中,遇到await后面的函数调用,会直接进入函数,并执行。

(1)当这个函数返回非promise:

await后面的代码被压入microtask队列。当主线程执行完毕,取出这个回调,执行。

(2)当这个函数返回promise:—— 这种情况,看浏览器实际表现,已经不是这样处理了。

await后面的代码被压入microtask队列。当主线程执行完毕,取出这个回调,发现await语句等待的函数返回了promise,把后续代码赋给这个promise对象的then,并把这个promise的回调再压入microtask队列,重新排队。当它前面的回调函数都被取出执行后,再取出它,执行。

- 举例说明

【1】await返回非promise

async function func1(){
  console.log('func1');
  var a = await func2(); //当await返回非promise
  console.log('func1 return');
}

function func2(){
  console.log('func2');
}
 
func1();

new Promise(function(resolve){
    console.log('promise1')
    resolve('resolved');
}).then(function(data){
    console.log(data);
})

// 结果:
// func1
// func2
// promise1
// func1 return
// resolved

【No2】await返回promise(来自头条笔试题)

async function async1() {     
  console.log("async1 start");      
  await  async2();     
  console.log("async1 end");   
}  
async function async2() {    
  console.log( 'async2');  
} 

console.log("script start");  

setTimeout(function () {      
  console.log("settimeout");  
},0);

async1();  

new Promise(function (resolve) {      
  console.log("promise1");      
  resolve();  
}).then(function () {      
  console.log("promise2"); 
}); 
console.log('script end');  

//结果:
// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// settimeout

【注】
async1 end、promise2的console顺序,原来是反过来的。

十五、Vuex

来源
这里有一个非常重要的问题: 为什么要用Action管理异步操作?

action和mutation

乍一想,使用mutation也可以实现异步操作,为什么要多此一举,使用actions呢?

mutation必须同步执行这个限制,action不受约束;
可以在actions内部执行异步操作;

如果遇到异步操作涉及互相依赖的情况的时候,我们希望被依赖的操作执行完成之后,再执行依赖项,这样能保证程序执行得到正确的结果。但是异步操作,比如接口访问,往往不能确定执行完成的时间;

比如:在接口A中得到一个值,a;接口B需要使用这个值,结合B接口返回的值进行一些判断操作,if(a&&b){ … }; 这个时候如果B接口执行完毕了,A接口的值还没过来的话,就可能得到错误的结果。 a => undefined

addCountAction (context) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      context.commit('add') // 先提交
      resolve()
    }, 1000)
  })
}

actions: {
  async actionA ({ commit }) {
    commit('gotData', await getData())
  },
  async actionB ({ dispatch, commit }) {
    await dispatch('actionA') // 等待 actionA 完成
    commit('gotOtherData', await getOtherData())
  }
}

在这里插入图片描述
具体实例:

  1. 点击“加入购物车”,将商品添加到购物车
    在mutations中写入方法:this.$store.commit("addCart", product)
    首先在状态的carList列表中使用find函数去查找之前数组中是否有该商品,有的话,购物车数量 + 1;
    没有的话,将该数据添加到carList中,将其count属性设为1;
    在这里插入图片描述
  2. 购物车页面需要显示加入商品的数量,所以需要拿到state,可以使用getters;
    在这里插入图片描述
  3. actions返回一个promise
addCart(context, payload) {
	return new Promise((resolve, reject) => {
	})
}

1、state状态的更改

当在其他地方更改了state状态时,其他组件应用到这个状态的时候,也会得到更新;

2、mutations:

  • 更改Vuex的store中的状态的唯一方法是提交mutation;
  • 通过this.$store.state来直接修改store中的状态也是有效的;但是这种方式的修改无法被Vue的调试工具记录(所以不推荐)
  • 通常情况下,Vuex要求mutation中的方法必须是同步,这是由于mutation对异步方法的提交无法被Vue调试工具记录。

3、store的状态是响应式的

Vuex中的store的状态是响应式的,mutations作为更改store中状态的唯一方法,需要遵循Vue的响应规则;

提前在store中初始化好所需的属性;
当给state中的对象添加新属性时,使用以下方法;
方式一:使用Vue.set()方法

addProp(state, payload) {
    // 该方法新增的属性不会添加至响应系统
    // state.obj.id = payload.id
    // Vue.set()添加的属性可以加入响应式系统
    // 三个参数分别是改变的对象,新增的属性名,新增的属性值
    Vue.set(state.obj, 'id', payload.id) 
    // 删除对象使用Vue.delete()方法
}

方式二:用新对象给旧对象重新赋值(新对象替换旧对象)

addPorp(state, payload) {
    // (...) 扩展运算符(对象展开符)
    state.obj = {...state.obj, 'id': payload.id}
}

4、为什么要加action功能?

在 mutation 中混合异步调用会导致你的程序很难调试。例如,当你调用了两个包含异步回调的 mutation 来改变状态,无法知道什么时候回调和哪个先回调

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值