Vue基础知识
一、vue解决了什么问题
Vue.js(读音 /vjuː/, 类似于 view) 是一套构建用户界面的 渐进式框架。与其他重量级框架不同的是,Vue 采用自底向上增量开发的设计。Vue 的核心库只关注视图层,并且非常容易学习,非常容易与其它库或已有项目整合。另一方面,Vue 完全有能力驱动采用单文件组件和 Vue 生态系统支持的库开发的复杂单页应用。
Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。
轻量级框架:只关注视图层,是一个构建数据的视图集合,大小只有几十kb
简单易学:国人开发,中文文档,不存在语言障碍,易于理解和学习
双向数据绑定:保留了angular的特点,在数据操作方面更为简单
组件化:保留了react的优点;实现了html的封装和重用,在构建单页面应用方面有着独特的优势
视图,数据,结构分离:是数据的更改更为简单,不需要进行逻辑代码的修改,只需要操作数据就能完成相关操作
虚拟DOM:dom操作时非常耗费性能的,不再使用原生的dom操作节点,极大解放dom操作,但具体操作的还是dom不过是换了另一种方式
运行速度更快:像比较与react而言,同样都是操作虚拟dom,就性能而言,vue存在很大的优势
二、MVVM的理解
MVVM定义:MVVM是Model-View-ViewModel的简写。即模型-视图-视图模型。【模型】指的是后端传递的数据。【视图】指的是所看到的页面。【视图模型】mvvm模式的核心,它是连接view和model的桥梁。
它有两个方向:
一是将【模型】转化成【视图】,即将后端传递的数据转化成所看到的页面。实现的方式是:数据绑定。
二是将【视图】转化成【模型】,即将所看到的页面转化成后端的数据。实现的方式是:DOM 事件监听。这两个方向都实现的,我们称之为数据的双向绑定。
总结:在MVVM的框架下视图和模型是不能直接通信的。它们通过ViewModel来通信,ViewModel通常要实现一个observer观察者,当数据发生变化,ViewModel能够监听到数据的这种变化,然后通知到对应的视图做自动更新,而当用户操作视图,ViewModel也能监听到视图的变化,然后通知数据做改动,这实际上就实现了数据的双向绑定。并且MVVM中的View 和 ViewModel可以互相通信
在vue中,Model:指的是js中的数据,如对象,数组等等;View:指的是页面视图;viewModel:指的是vue实例化对象。为什么说VUE是一个渐进式的javascript框架, 渐进式是什么意思?
1.如果你已经有一个现成的服务端应用,你可以将vue 作为该应用的一部分嵌入其中,带来更加丰富的交互体验;
2.如果你希望将更多业务逻辑放到前端来实现,那么VUE的核心库及其生态系统也可以满足你的各式需求(core+vuex+vue-route)。和其它前端框架一样,VUE允许你将一个网页分割成可复用的组件,每个组件都包含属于自己的HTML、CSS、JAVASCRIPT以用来渲染网页中相应的地方。
3.如果我们构建一个大型的应用,在这一点上,我们可能需要将东西分割成为各自的组件和文件,vue有一个命令行工具,使快速初始化一个真实的工程变得非常简单(vue init webpack my-project)。我们可以使用VUE的单文件组件,它包含了各自的HTML、JAVASCRIPT以及带作用域的CSS或SCSS。以上这三个例子,是一步步递进的,也就是说对VUE的使用可大可小,它都会有相应的方式来整合到你的项目中。所以说它是一个渐进式的框架。VUE最独特的特性:响应式系统VUE是响应式的(reactive),也就是说当我们的数据变更时,VUE会帮你更新所有网页中用到它的地方。
三、如何实现一个自定义组件,不同组件之间如何通信的?
Vue.component
来创建组件,这些组件是全局注册的
// 定义一个名为 button-counter 的新组件 Vue.component('button-counter', { data: function () { return { count: 0 } }, template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>' })
组件的使用
<div id="components-demo"> <button-counter></button-counter> </div>
Prop 是你可以在组件上注册的一些自定义特性。当一个值传递给一个 prop 特性的时候,它就变成了那个组件实例的一个属性。为了给博文组件传递一个标题,我们可以用一个 props
选项将其包含在该组件可接受的 prop 列表中
Vue.component('blog-post', { props: ['title'], template: '<h3>{{ title }}</h3>' })
通过下面的代码,即可将数据作为自定义的特性传递进来
<blog-post title="My journey with Vue"></blog-post> <blog-post title="Blogging with Vue"></blog-post> <blog-post title="Why Vue is so fun"></blog-post>
new Vue({ el: '#blog-post-demo', data: { posts: [ { id: 1, title: 'My journey with Vue' }, { id: 2, title: 'Blogging with Vue' }, { id: 3, title: 'Why Vue is so fun' } ] } }) <blog-post v-for="post in posts" v-bind:key="post.id" v-bind:title="post.title" ></blog-post>
如上所示,你会发现我们可以使用 v-bind
来动态传递 prop。
定义组件名的方式有两种:
使用 kebab-case
Vue.component('my-component-name', { /* ... */ })
当使用 kebab-case (短横线分隔命名) 定义一个组件时,你也必须在引用这个自定义元素时使用 kebab-case,例如 <my-component-name>
。
使用 PascalCase
Vue.component('MyComponentName', { /* ... */ })
当使用 PascalCase (首字母大写命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。也就是说 <my-component-name>
和 <MyComponentName>
都是可接受的。注意,尽管如此,直接在 DOM (即非字符串的模板) 中使用时只有 kebab-case 是有效的。
局部注册
new Vue({
el: '#app',
components: {
'component-a': ComponentA,
'component-b': ComponentB
}
})
对于 components
对象中的每个属性来说,其属性名就是自定义元素的名字,其属性值就是这个组件的选项对象。
注意局部注册的组件在其子组件中不可用
如果你使用了诸如 Babel 和 webpack 的模块系统。在这些情况下,我们推荐创建一个 components
目录,并将每个组件放置在其各自的文件中。
然后你需要在局部注册之前导入每个你想使用的组件。例如,在一个假设的 ComponentB.js
或 ComponentB.vue
文件中:
import ComponentA from './ComponentA'
import ComponentC from './ComponentC'
export default {
components: {
ComponentA,
ComponentC
},
// ...
}
现在 ComponentA
和 ComponentC
都可以在 ComponentB
的模板中使用了。
props: {
title: String,
likes: Number,
isPublished: Boolean,
commentIds: Array,
author: Object,
callback: Function,
contactsPromise: Promise // or any other constructor
}
props的类型和传值:https://cn.vuejs.org/v2/guide/components-props.html
组件通信见本页第八个问题
本部分摘自:https://blog.csdn.net/liyunkun888/article/details/83269692
四、nextTick
Vue.nextTick():在下次 DOM
更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM
。
获取更新后的DOM
言外之意就是什么操作需要用到了更新后的DOM
而不能使用之前的DOM
或者使用更新前的DOM
会出问题,所以就衍生出了这个获取更新后的 DOM
的Vue
方法。所以放在Vue.nextTick()
回调函数中的执行的应该是会对DOM
进行操作的js
代码。
- 你在
Vue
生命周期的created()钩子函数进行的DOM
操作一定要放在Vue.nextTick()
的回调函数中。原因是什么呢,原因是在created()
钩子函数执行的时候DOM
其实并未进行任何渲染,而此时进行DOM
操作无异于徒劳,所以此处一定要将DOM
操作的js
代码放进Vue.nextTick()
的回调函数中。与之对应的就是mounted钩子函数,因为该钩子函数执行时所有的DOM
挂载和渲染都已完成,此时在该钩子函数中进行任何DOM
操作都不会有问题 。 - 在数据变化后要执行的某个操作,当你设置
vm.someData = 'new value'
,DOM
并不会马上更新,而是在异步队列被清除,也就是下一个事件循环开始时执行更新时才会进行必要的DOM
更新。如果此时你想要根据更新的DOM
状态去做某些事情,就会出现问题。。为了在数据变化之后等待Vue
完成更新DOM
,可以在数据变化之后立即使用Vue.nextTick(callback)
。这样回调函数在DOM
更新完成后就会调用。 mounted
不会承诺所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以用vm.$nextTick
替换掉mounted。
五、生命周期
创建→初始化数据→编译模板→挂载DOM→更新→渲染→销毁
beforeCreate
:在实例初始化之后,数据观测data observer(props、data、computed
) 和event/watcher
事件配置之前被调用。created
:实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算,watch/event
事件回调。然而,挂载阶段还没开始,$el
属性目前不可见。beforeMount
:在挂载开始之前被调用:相关的render
函数首次被调用。mounted
:el
被新创建的vm.$el
替换,并挂载到实例上去之后调用该钩子。beforeUpdate
:数据更新时调用,发生在虚拟DOM
重新渲染和打补丁之前。 你可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。updated
:无论是组件本身的数据变更,还是从父组件接收到的props
或者从vuex
里面拿到的数据有变更,都会触发虚拟DOM
重新渲染和打补丁,并在之后调用updated
。beforeDestroy
:实例销毁之前调用。在这一步,实例仍然完全可用。destroyed
:Vue
实例销毁后调用。调用后,Vue
实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。 该钩子在服务器端渲染期间不被调用。
注意:created
阶段的ajax
请求与mounted
请求的区别:前者页面视图未出现,如果请求信息过多,页面会长时间处于白屏状态。
单个组件的生命周期
- 初始化组件时,仅执行了
beforeCreate/Created/beforeMount/mounted
四个钩子函数 - 当改变data中定义的变量(响应式变量)时,会执行
beforeUpdate/updated
钩子函数 - 当切换组件(当前组件未缓存)时,会执行
beforeDestory/destroyed
钩子函数 - 初始化和销毁时的生命钩子函数均只会执行一次,
beforeUpdate/updated
可多次执行
本部分内容摘自:https://www.jianshu.com/p/46c9d777cab1
六、虚拟dom的原理
Web界面由DOM树(树的意思是数据结构)来构建,当其中一部分发生变化时,其实就是对应某个DOM节点发生了变化,
虚拟DOM就是为了解决浏览器性能问题而被设计出来的。如前,若一次操作中有10次更新DOM的动作,虚拟DOM不会立即操作DOM,而是将这10次更新的diff内容保存到本地一个JS对象中,最终将这个JS对象一次性attch到DOM树上,再进行后续操作,避免大量无谓的计算量。所以,用JS对象模拟DOM节点的好处是,页面的更新可以先全部反映在JS对象(虚拟DOM)上,操作内存中的JS对象的速度显然要更快,等更新完成后,再将最终的JS对象映射成真实的DOM,交由浏览器去绘制。
用JavaScript的方法来模拟DOM树,用新渲染的对象树去和旧的树进行对比,记录下变化的变化,然后应用到真实的DOM树上,这样我们只需要更改与原来视图不同的地方,而不需要全部重新渲染一次。
diff算法只会对相同颜色方框内的DOM节点进行比较,即同一个父节点下的所有子节点。当发现节点不存在,则该节点和子节点会被完全删除,不会做进一步的比较。
在实际的代码中,会对新旧两棵树进行深度的遍历,给每一个节点进行标记。然后在新旧两棵树的对比中,将不同的地方记录下来。
七、双向绑定的原理?数据劫持?
什么是双向数据绑定?Vue是一个MVVM框架,数据绑定简单来说,就是当数据发生变化时,相应的视图会进行更新,当视图更新时,数据也会跟着变化。
实现数据绑定的方式大致有以下几种:
-
发布者-订阅者模式(backbone.js)
-
脏值检查(angular.js)
-
数据劫持(vue.js)
Vue.js则是通过数据劫持以及结合发布者-订阅者来实现的,数据劫持是利用ES5的Object.defineProperty(obj, key, val)来劫持各个属性的的setter以及getter,在数据变动时发布消息给订阅者,从而触发相应的回调来更新视图。
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
实现数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer
,用来监听所有属性。如果属性发上变化了,就需要告诉订阅者Watcher
看是否需要更新。因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep
来专门收集这些订阅者,然后在监听器Observer
和订阅者Watcher
之间进行统一管理的。
//监听器 function defineReactive(data,key,val) { observe(val); var dep = new Dep(); Object.defineProperty(data, key, { enumerable: true, configurable: true, get: function() { if(Dep.target) { //判断是否需要添加订阅者 dep.addSub(Dep.target); } return val; }, set: function(newVal) { if (val === newVal) { return; } val = newVal; console.log('属性' + key + '已经被监听了,现在值为:“' + newVal.toString() + '”'); dep.notify(); // 如果数据变化,通知所有订阅者 } }); } //订阅者 function Watcher(vm,exp,cb) { this.vm = vm; //指向SelfVue的作用域 this.exp = exp; //绑定属性的key值 this.cb = cb; //闭包 this.value = this.get(); } Watcher.prototype = { update:function() { this.run(); }, run:function() { var value = this.vm.data[this.exp]; var oldVal = this.value; if(value !== oldVal) { this.value = value; this.cb.call(this.vm,value,oldVal); } }, get:function() { Dep.target = this; // 缓存自己 var value = this.vm.data[this.exp]; // 强制执行监听器里的get函数 Dep.target = null; // 释放自己 return value; } }
本部分参考:https://segmentfault.com/a/1190000014616977?utm_source=channel-hottest https://www.cnblogs.com/wangjiachen666/p/9883916.html
八、组件通信
父传子
父传子的实现方式就是通过props属性,子组件通过props属性接收从父组件传过来的值,而父组件传值的时候使用 v-bind 将子组件中预留的变量名绑定为data里面的数据即可。
子 <template> <div id="container"> {{msg}} </div> </template> <script> export default { data() { return {}; }, props:{ msg: String } }; </script> <style scoped> #container{ color: red; margin-top: 50px; } </style> 父 <template> <div id="container"> <input type="text" v-model="text" @change="dataChange"> <Child :msg="text"></Child> </div> </template> <script> import Child from "@/components/Child"; export default { data() { return { text: "父组件的值" }; }, methods: { dataChange(data){ this.msg = data } }, components: { Child } }; </script> <style scoped> </style>
子传父
子传父的实现方式就是用了 this.$emit 来遍历 getData 事件,首先用按钮来触发 setData 事件,在 setData 中 用 this.$emit 来遍历 getData 事件,最后返回 this.msg。
- 子组件中需要以某种方式例如点击事件的方法来触发一个自定义事件
- 将需要传的值作为$emit的第二个参数,该值将作为实参传给响应自定义事件的方法
- 在父组件中注册子组件并在子组件标签上绑定对自定义事件的监听
子 <template> <div id="container"> <input type="text" v-model="msg"> <button @click="setData">传递到父组件</button> </div> </template> <script> export default { data() { return { msg: "传递给父组件的值" }; }, methods: { setData() { this.$emit("getData", this.msg); } } }; </script> <style scoped> #container { color: red; margin-top: 50px; } </style> 父 <template> <div id="container"> <Child @getData="getData"></Child> <p>{{msg}}</p> </div> </template> <script> import Child from "@/components/Child"; export default { data() { return { msg: "父组件默认值" }; }, methods: { getData(data) { this.msg = data; } }, components: { Child } }; </script> <style scoped> </style>
非父子传值
vue 中没有直接子对子传参的方法,建议将需要传递数据的子组件,都合并为一个组件
如果一定需要子对子传参,可以先从传到父组件,再传到子组件(相当于一个公共bus文件)
为了便于开发,vue 推出了一个状态管理工具 vuex,可以很方便实现组件之间的参数传递(详解见本页第十三个问题)
其次还可以通过事件总线eventBus来进行两个组件间的通信,EventBus
又称为事件总线。在Vue中可以使用EventBus
来作为沟通桥梁的概念,就像是所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件,所以组件都可以上下平行地通知其他组件,但也就是太方便所以若使用不慎,就会造成难以维护的灾难,因此才需要更完善的Vuex作为状态管理中心,将通知的概念上升到共享状态层次。
eventBus原理是创建一个空Vue实例, 然后在上面挂载通讯事件, 在挂载事件时, 可以认为这个Vue实例(组件), 是所有组件的父组件, 在触发事件时, 可以认为这个Vue实例, 是所有组件的子组件
//eventBus.js的代码 import Vue from "vue"; let bus = new Vue(); export default bus;
//在需要通信的两个组件中分别import import bus from"../utils/eventBus.js";
//一个组件中发射 bus.$emit('SUBMITSEARCH_PEOPLE',this.searchContent)
//另一个组件中接收 bus.$on('SUBMITSEARCH_PEOPLE',(
searchContent) => { this.bgC =
searchContent; }
)
//存在问题是$on会多次触发,解决方法如下
beforeDestroy () {
bus.$off('SUBMITSEARCH_PEOPLE')
}
九、Proxy 相比于 defineProperty 的优势
常见的基于数据劫持的双向绑定有两种实现,一个是目前Vue在用的Object.defineProperty
,另一个是ES2015中新增的Proxy。
严格来讲Proxy应该被称为『代理』而非『劫持』,不过由于作用有很多相似之处,我们在下文中就不再做区分,统一叫『劫持』。
数据劫持的优势所在。
- 无需显示调用: 例如Vue运用数据劫持+发布订阅,直接可以通知变化并驱动视图,上面的例子也是比较简单的实现
data.name = '渣渣辉'
后直接触发变更,而比如Angular的脏检测则需要显示调用markForCheck
(可以用zone.js避免显示调用,不展开),react需要显示调用setState
。 - 可精确得知变化数据:还是上面的小例子,我们劫持了属性的setter,当属性值改变,我们可以精确获知变化的内容
newVal
,因此在这部分不需要额外的diff操作,否则我们只知道数据发生了变化而不知道具体哪些数据变化了,这个时候需要大量diff来找出变化值,这是额外性能损耗
Object.defineProperty
的全方位加强版。vue3.0的改进Proxy可以直接监听对象而非属性
Proxy可以直接监听数组的变化
Proxy返回的是一个新对象,我们可以只操作新的对象达到目的,而Object.defineProperty
只能遍历对象属性直接修改
//proxy数据劫持 observe(data) { const that = this; let handler = { get(target, property) { return target[property]; }, set(target, key, value) { let res = Reflect.set(target, key, value); that.subscribe[key].map(item => { item.update(); }); return res; } } this.$data = new Proxy(data, handler); }
基本用法:let p = new Proxy(target, handler);
target: 是用Proxy包装的被代理对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。 handler: 是一个对象,其声明了代理target 的一些操作,其属性是当执行一个操作时定义代理的行为的函数。
- get: 读取值,
- set: 获取值,has: 判断对象是否拥有该属性,
- construct: 构造函数
本部分摘自:https://www.jianshu.com/p/2df6dcddb0d7
十、watch computed区别
1. watch属性可以用来监听data属性中数据的变化,同时还可以直接在监听的function中使用参数来获取新值与旧值
data:{ firstname:"", lastname:"" }, methods:{}, watch:{ firstname:function(){ console.log(this.firstname) } } watch:{ firstname:function(newValue,OldValue){ console.log(newValue); console.log(OldValue); } }
同时Watch还可以被用来监听路由router的变化
2. computed属性的作用与watch类似,也可以监听属性的变化,只是他会根据他依赖的属性,生成一个属性,让vm对象可以使用这个属性
computed:{ fullname:function(){ return this.firstname +"-"+this.lastname } }
methods,watch,computed的区别
- computed 属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算。主要当作属性来使用;
- methods 方法表示一个具体的操作,主要书写业务逻辑;
- watch 一个对象,键是需要观察的表达式,值是对应回调函数。主要用来监听某些特定数据的变化,从而进行某些具体的业务逻辑操作;可以看作是 computed 和 methods 的结合体;
十一、vue常用指令——v-bind v-model v-for v-if v-on v-show
1. 数据渲染 v-text、v-html、v-model、{{}}
v-text是用于操作纯文本,它会替代显示对应的数据对象上的值。当绑定的数据对象上的值发生改变,插值处的内容也会随之更新。注意:此处为单向绑定,数据对象上的值改变,插值会发生变化;但是当插值发生变化并不会影响数据对象的值。其中:v-text可以简写为{{}},并且支持逻辑运算。
<div id="app"> {{ message }} </div> var app = new Vue({ el : '#app', data : { message : 'hello world' } })
v-html用于输出html,它与v-text区别在于v-text输出的是纯文本,浏览器不会对其再进行html解析,但v-html会将其当html标签解析后输出。
<div id="app"> <p v-html="html"></p> </div> var app = new Vue({ el: "#app", data: { html: "<b style='color:red'>v-html</b>" } });
v-model通常用于表单组件的绑定,例如input,select等。它与v-text的区别在于它实现的表单组件的双向绑定,如果用于表单控件以外标签是没有用的。
<div id="app"> <input v-model="message " /> </div> var app = new Vue({ el : '#app', data : { message : 'hello world' } })
2. 控制模块的显示/隐藏 v-if、v-show
v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
一般来说, v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。
因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件不太可能改变,则使用 v-if 较好。
<template> <div id="app"> <div v-if="isIf"> if </div> <div v-show="ifShow"> show </div> <button @click="toggleShow">toggle</button> </div> </template> <script> export default { name: 'app', data() { return { isIf : true, ifShow : true, loginType : "username" } }, methods: { toggleShow : function(){ this.ifShow = this.ifShow ? false : true; this.isIf = this.isIf ? false : true; } } } </script>
3. 渲染循环列表 v-for
v-for是一个循环指令,一般跟数组结合起来使用
也可以在模板中使用 v-for
v-for 可以通过一个对象的属性来迭代数据
v-for 通过一个对象的属性来迭代数据,可以提供第二个的参数为键名
v-for 通过一个对象的属性来迭代数据,以第三个参数为索引
v-for 也可以循环整数
<p id="wantuizhijia"> <ol> <li v-for="site in sites"> {{ site.name }} </li> </ol> </p> <script> new Vue({ el: '#wantuizhijia', data: { sites: [ { name: '网推之家' }, { name: '谷歌' }, { name: '淘宝' } ] } }) </script>
4. 事件绑定 v-on
<div id="example-1"> <button v-on:click="counter += 1">Add 1</button> <p>The button above has been clicked {{ counter }} times.</p> </div> var example1 = new Vue({ el: '#example-1', data: { counter: 0 } })
用于监听DOM事件,典型的就是v-on:click,处理的方法放在methods属性里会默认传一个参数。
这里的evt,是标准的鼠标点击事件,类似jquery的click事件的回调函数中的参数。
<button @click="test">点击</button> methods: { test: function (evt) { console.log(evt); } }
如果需要给他一个像上面一样的鼠标点击事件时,则使用$event作为参数(他和不传参数时的默认鼠标事件对象是相同的)
<div id="app"> <a href="http://www.baidu.com" @click="test(items, $event)">点击搜索age</a> </div> <script> var test = {name: "test"}; var vm = new Vue({ el: '#app', data: { items: "test" }, methods: { test: function (msg, evt) { console.log(msg); evt.preventDefault();//阻止默认动作,比如这里是页面跳转 } } }) </script>
Vue.js 为 v-on
提供了事件修饰符。之前提过,修饰符是由点开头的指令后缀来表示的。
.stop
.prevent
.capture
.self
.once
.passive
<!-- 阻止单击事件继续传播 --> <a v-on:click.stop="doThis"></a> <!-- 提交事件不再重载页面 --> <form v-on:submit.prevent="onSubmit"></form> <!-- 修饰符可以串联 --> <a v-on:click.stop.prevent="doThat"></a> <!-- 只有修饰符 --> <form v-on:submit.prevent></form> <!-- 添加事件监听器时使用事件捕获模式 --> <!-- 即元素自身触发的事件先在此处理,然后才交由内部元素进行处理 --> <div v-on:click.capture="doThis">...</div> <!-- 只当在 event.target 是当前元素自身时触发处理函数 --> <!-- 即事件不是从内部元素触发的 --> <div v-on:click.self="doThat">...</div>
本部分摘自:https://www.jianshu.com/p/a0680cb09c83
十二、vue-router(hash, HTML5 新增的 pushState)
1. 单页应用,如何实现其路由功能---路由原理
随着前端应用的业务功能起来越复杂,用户对于使用体验的要求越来越高,单面(SPA
)成为前端应用的主流形式。大型单页应用最显著特点之一就是采用的前端路由系统,通过改变URL
,在不重新请求页面的情况下,更新页面视图。
更新视图但不重新请求页面,是前端路由原理的核心之一,目前在浏览器环境中这一功能的实现主要有2
种方式:
- 利用
URL
中的hash
("#"
); - 利用
History interface
在HTML5
中新增的方法;
vue-router
是Vue.js
框架的路由插件,它是通过mode
这一参数控制路由的实现模式的。
const router=new VueRouter({ mode:'history', routes:[...] })
随着ajax流行,异步数据请求交互运行在不刷新浏览器的情况下。而异步交互体验的更高级版本就是SPA——单页面应用。单页面应用不仅仅是在页是无刷新的,连页面吧跳转也是无刷新的,为了实现单页面应用,所以就有了前端路由。类似于服务端路由,前端路由实现起来其实其实也很简单,就是匹配不同的url路径,进行解析,然后动态的渲染出该区域的html内容,但是这样存在一个问题,就是url每次变化的时候,都会造成页面的刷新,那解决的思路便是在改变url的情况下,保证页面不刷新,早2014年之前,大家是通过has来实现路由,url hash就类似于http://www.xxx.com/#/login,这种#后面hash值的变化并不会导致浏览器向服务器发送请求,也就不会刷新页面,另外每次hash值的变化,还会触发hashchange事件,通过这个事件我们就可以知道hash值发生了改变,然后我们便可以监听hashchange来实现更新页面部分内容的操作。
本部分参考链接:https://segmentfault.com/a/1190000014822765?utm_source=tag-newest
2. vue-router如何做用户登录权限等
首先,在项目的App.vue中获取当前登陆用户的permissionList
并使用vuex状态管理,将用户的权限存入
之后,在nav.vue这个导航文件中用mapGetters获取permissionList,并根据获取到的权限设置当前用户的菜单
store中的定义
// getters const getters = { userInfo: state => state.userInfo, pmList: state => state.pmList }
nav.vue导航文件部分代码
data () { return { // pmList: [], menuList: [] // 当前用户拥有的权限菜单 } }, computed: { ...mapGetters({ user: 'userInfo', pmList: 'pmList', tabList: 'tabList' }) }, // 根据权限设置当前用户的菜单 setUserMenu () { const permissions = this.pmList const routeList = cloneDeep(this.$router.options.routes[0].children) if (permissions && permissions.length) { routeList.forEach(rootItem => { if (rootItem.isNavMenu && permissions.includes(rootItem.name)) { if (rootItem.children.length) { rootItem.children = rootItem.children.filter(subItem => { return subItem.isTabMenu && permissions.includes(subItem.name) }) } this.menuList.push(rootItem) } }) } else { // this.menuList = routeList.filter(item => { // return item.isNavMenu; // }); } },
3. 你在项目中怎么实现路由的嵌套
{ path: '/goods', icon: 'icon-daohangshangpin-px', index: 1, isNavMenu: true, name: 'MALL_PRODUCT', meta: { title: '商品', }, component: { template: `<router-view></router-view>` }, children: goods },
{ path: 'list', index: 0, isTabMenu: true, name: 'MALL_PRODUCT_MANAGER', meta: { title: '商品管理' }, component: () => import('@views/goods/main/list.vue') }, { path: 'listAddGood', isTabMenu: false, meta: { title: '添加商品' }, component: () => import('@views/goods/main/edit.vue') },
十三、vuex的理解
在SPA单页面组件的开发中 Vue的vuex和React的Redux 都统称为同一状态管理,个人的理解是全局状态管理更合适;简单的理解就是你在state中定义了一个数据之后,你可以在所在项目中的任何一个组件里进行获取、进行修改,并且你的修改可以得到全局的响应变更。
先声明一个state变量,并赋值一个空对象给它,里面随便定义两个初始属性值;然后再在实例化的Vuex.Store里面传入一个空对象,并把刚声明的变量state放里面。
vuex官方API提供了一个getters,和vue计算属性computed一样,来实时监听state值的变化(最新状态)
mutattions也是一个对象,这个对象里面可以放改变state的初始值的方法,具体的用法就是给里面的方法传入参数state或额外的参数,然后利用vue的双向数据驱动进行值的改变
actions也是个对象变量,最大的作用就是里面的Action方法 可以包含任意异步操作,这里面的方法是用来异步触发mutations里面的方法,actions里面自定义的函数接收一个context参数和要变化的形参,context与store实例具有相同的方法和属性,所以它可以执行context.commit(' ')
而在外部组件里进行全局执行actions里面方法的时候,你只需要用执行
this.$store.dispatch('hideFooter')
或this.$store.dispatch('showFooter')
以及this.$store.dispatch('getNewNum',6) //6要变化的实参
在组件中获取vuex中的state
computed: { count () { return this.$store.state.count } }
那为什么要使用computed而不是data获取vuex中的state呢?
这是因为data 中的内容只会在 created 钩子触发前初始化一次,具体来说就是data中设置count: this.$store.state.count 则count的值是created钩子执行前this.$store.state.count的值,赋值之后属性的值就是纯粹的字面量,之后this.$store.state.count 如何变化均影响不到count的取值。
vuex模块化
//index.js import Vue from 'vue'; import Vuex from 'vuex'; import footerStatus from './modules/footerStatus' import collection from './modules/collection' Vue.use(Vuex); export default new Vuex.Store({ modules:{ footerStatus, collection } });
//collection.js const state={ collects:[], //初始化一个colects数组 }; const getters={ renderCollects(state){ //承载变化的collects return state.collects; } }; const mutations={ pushCollects(state,items){ //如何变化collects,插入items state.collects.push(items) } }; const actions={ invokePushItems(context,item){ //触发mutations里面的pushCollects ,传入数据形参item 对应到items context.commit('pushCollects',item); } }; export default { namespaced:true,//用于在全局引用此文件里的方法时标识这一个的文件名 state, getters, mutations, actions }
//footerStatus.js const state={ //要设置的全局访问的state对象 showFooter: true, changableNum:0 //要设置的初始属性值 }; const getters = { //实时监听state值的变化(最新状态) isShow(state) { //承载变化的showFooter的值 return state.showFooter }, getChangedNum(){ //承载变化的changebleNum的值 return state.changableNum } }; const mutations = { show(state) { //自定义改变state初始值的方法,这里面的参数除了state之外还可以再传额外的参数(变量或对象); state.showFooter = true; }, hide(state) { //同上 state.showFooter = false; }, newNum(state,sum){ //同上,这里面的参数除了state之外还传了需要增加的值sum state.changableNum+=sum; } }; const actions = { hideFooter(context) { //自定义触发mutations里函数的方法,context与store 实例具有相同方法和属性 context.commit('hide'); }, showFooter(context) { //同上注释 context.commit('show'); }, getNewNum(context,num){ //同上注释,num为要变化的形参 context.commit('newNum',num) } }; export default { namespaced: true, //用于在全局引用此文里的方法时标识这一个的文件名 state, getters, mutations, actions }
本部分参考链接:https://segmentfault.com/a/1190000015782272
十四、Webpack
WebPack
是一个模块打包工具,你可以使用WebPack
管理你的模块依赖,并编绎输出模块们所需的静态文件。它能够很好地管理、打包Web开发中所用到的HTML
、JavaScript
、CSS
以及各种静态文件(图片、字体等),让开发过程更加高效。对于不同类型的资源,webpack
有对应的模块加载器。webpack
模块打包器会分析模块间的依赖关系,最后 生成了优化且合并后的静态资源。- 对
CommonJS
、AMD
、ES6
的语法做了兼容 - 对
js
、css
、图片等资源文件都支持打包 - 串联式模块加载器以及插件机制,让其具有更好的灵活性和扩展性,例如提供
对CoffeeScript
、ES6
的支持 - 有独立的配置文件
webpack.config.js
- 可以将代码切割成不同的
chunk
,实现按需加载,降低了初始化时间 - 支持
SourceUrls
和SourceMaps
,易于调试 - 具有强大的
Plugin
接口,大多是内部插件,使用起来比较灵活webpack
使用异步IO
并具有多级缓存。这使得webpack
很快且在增量编译上更加快
【Loader】:用于对模块源码的转换,loader描述了webpack如何处理非javascript模块,并且在buld中引入这些依赖。loader可以将文件从不同的语言(如TypeScript)转换为JavaScript,或者将内联图像转换为data URL。比如说:CSS-Loader,Style-Loader等。
【Plugin】:目的在于解决loader无法实现的其他事,从打包优化和压缩,到重新定义环境变量,功能强大到可以用来处理各种各样的任务。webpack提供了很多开箱即用的插件:CommonChunkPlugin主要用于提取第三方库和公共模块,避免首屏加载的bundle文件,或者按需加载的bundle文件体积过大,导致加载时间过长,是一把优化的利器。而在多页面应用中,更是能够为每个页面间的应用程序共享代码创建bundle。
一切皆模块: 正如js文件可以是一个“模块(module)”一样,其他的(如css、image或html)文件也可视作模 块。因此,你可以require('myJSfile.js')亦可以require('myCSSfile.css')。这意味着我们可以将事物(业务)分割成更小的易于管理的片段,从而达到重复利用等的目的。
按需加载: 传统的模块打包工具(module bundlers)最终将所有的模块编译生成一个庞大的bundle.js文件。但是在真实的app里边,“bundle.js”文件可能有10M到15M之大可能会导致应用一直处于加载中状态。因此Webpack使用许多特性来分割代码然后生成多个“bundle”文件,而且异步加载部分代码以实现按需加载。
1. 打包原理
webpack只是一个打包模块的机制,只是把依赖的模块转化成可以代表这些包的静态文件。并不是什么commonjs或者amd之类的模块化规范。webpack就是识别你的 入口文件。识别你的模块依赖,来打包你的代码。至于你的代码使用的是commonjs还是amd或者es6的import。webpack都会对其进行分析。来获取代码的依赖。webpack做的就是分析代码。转换代码,编译代码,输出代码。webpack本身是一个node的模块,所以webpack.config.js是以commonjs形式书写的(node中的模块化是commonjs规范的)
-
初始化:启动构建,读取与合并配置参数,加载
Plugin
,实例化Compiler
。 -
编译:从
Entry
发出,针对每个Module
串行调用对应的Loader
去翻译文件内容,再找到该Module
依赖的Module
,递归地进行编译处理。 -
输出:对编译后的
Module
组合成Chunk
,把Chunk
转换成文件,输出到文件系统。
2. webpack热更新原理
热更新是:使得应用在运行状态下,不重载刷新就能更新、增加、移除模块的机制。简单来说,就是为了 提升开发效率。
https://www.jianshu.com/p/652fbae768bf
3. 优化构建速度
- 减少构建的文件,减小文件大小:项目中存在太多的无用的文件和代码,先删除这些无用的东西
- 移除 CommonsChunkPlugin
- Search with Google
//使用 DllPlugin的配置 const manifest = require('./dll/vendor-manifest.json'); // ... 其他完美的配置 plugins: [ new webpack.DllReferencePlugin({ manifest, }), ],
本部分摘自:https://segmentfault.com/a/1190000015088834?utm_source=tag-newest
十五、v-lazy加载图片
引入插件
import VueLazyLoad from 'vue-lazyload'
Vue.use(VueLazyload, {
preLoad: 1.3,
error: require('@/assets/img/dou_dou.jpg'), //请求失败后显示的图片
loading: require('@/assets/img/dou_dou.jpg'), //加载的loading过渡图片
attempt: 1, // 加载图片数量listenEvents: [ 'scroll' ] //监听的事件
})
src属性改成 v-lazy
<img v-lazyload="imageSrc" >
监听的事件列表:['scroll', 'wheel', 'mousewheel', 'resize', 'animationend', 'transitionend']