1. Vue2基础知识
1.1 语法规则
- 插值语法:通常用于解析标签体内容,e.g.
<h1>{{变量}}</h1> - 指令语法:通常用于解析标签,e.g.
v-bind:属性名 = "JS表达式/语句" - 常见的几种指令:
· v-once: 变量仅在第一次渲染的时候显示,后不因变量值改变而同步到页面中
· v-html="url" : 将vue实例中的url变量中的html格式字符串作为html语句编译并展示
· v-text="变量" : 以文本的形式展示,不灵活,当标签中有内容时将会覆盖变量值
· v-pre:对{{xxx}}不进行mustache语法解析
· v-bind:属性名="{}/[]" : 对标签某些特殊属性进行动态赋值绑定,例如<a>标签的href,<img>标签的src等,v-bind:简写为:,属于单向绑定,绑定的语法形式有:1)对象语法,2)数组语法
· v-model:主要作用于value上,只能应用在表单类元素上,实现双向绑定 - 自定义指令(v-xxx):
1)定义语法:
局部指令:new Vue({ directives: {指令名: 配置对象}})或new Vue({ directives: {指令名: 回调函数}});
全局指令:Vue.directive(指令名, 配置对象)或Vue.directive(指令名, 回调函数)(当使用该自定义指令时调用函数或对象)
2)配置对象中常用的3个回调(bind、inserted、update)
<div id="root">
<span>Current number is {{n}}</span> <br>
<span v-big="n">Big number: {{n}}</span> <br>
<button @click="n++">n+1</button>
<input type="text" v-fbind:value="n">
</div>
<script>
new Vue({
el: '#root',
data: {
n: 1
},
directives: {
big(element, binding){
element.innerText = binding.value * 10;
},
fbind: {
// 指令与元素成功绑定时调用
bind(element, binding){
element.value = binding.value;
},
// 指令所在元素被插入时调用
inserted(element, binding){
element.focus();
},
// 指令所在模板被重新解析时调用
update(element, binding){
element.value = binding.value;
} }
},
});
</script>
- el的两种写法:
const v = new Vue({
el: '#root', // 写法一
data:{ }
})
// 写法二: 通过挂载函数将内容渲染到浏览器上
v.$mount('#root');
- data的两种写法:
const v = new Vue({
el: '#root',
data:{ } // 写法一: 对象式
})
// 写法二: 函数式
new Vue({
el: '#root',
data: function( ){ // 简写:data(){...}
return{
name: 'Yui'
}
}
});
注意:函数式的data不能使用箭头函数,若写成箭头函数,该函数对象将变成window而不再是vue实例
1.2 MVVM模型
模型(Model):对应data中的数据,负责处理业务逻辑以及和服务器端进行交互
视图(View):模板,负责将数据模型转化为UI展示出来,可以简单的理解为HTML页面
视图模型(ViewModel):Vue实例对象,用来连接Model和View,是Model和View之间的通信桥梁
1.3 事件处理
· 事件修饰符
- prevent:阻止默认事件(常用), e.g.
<a href="url" @click.prevent= "function">text</a> - stop:阻止事件冒泡(常用), e.g.
<a href="url" @click.stop= "function">text</a> - once:事件只触发一次(常用), e.g.
<a href="url" @click.once= "function">text</a> - capture:使用事件的捕获模式,让事件在捕获阶段就触发
- self:只有event.target是当前操作的元素时才触发事件(某种程度上起到了阻止冒泡的作用)
- passive:事件的默认行为立即执行,无需等待事件回调执行完毕,例子:滚动条滚动
· 键盘事件
- vue常用按键别名:回车(enter),删除(delete),退出(esc),空格(space),换行(tab,特殊,配合keydown使用),上(up),下(down),左(left),右(right)
- 上述未出现的按键可通过
.按键名来设定,特别地,当按键名由多个单词组成(e.g. CapsLock)时,要改成小写,且单词之间用”-“来连接,例如:<input type="text" placeholder="enter" @keyup.caps-lock="function"> - 系统修饰按键:ctrl,alt,shift,meta(win)
1)配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发
2)配合keydown使用:正常触发事件
1.4 响应式
· 响应式属性
· 计算属性 computed:要用的属性不存在,需要通过已有的属性计算得来
- 原理:底层借助Object.defineProperty( )方法提供的getter和setter
- 优点:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便
· 监视属性 watch: 当被监视的属性或函数发生变化时,回调函数自动调用,进行相关操作,监视的两种写法为:
- new Vue时传入watch配置,如下:
const vm = new Vue({
el: '#root',
data:{... },
watch:{
属性名:{
immediate: false, // 为true时初始化调用handle函数
// newValue: 变换后的值,oldValue: 变换前的值
handler(newValue, oldValue){...} }
});
- 通过vm.$watch监视,如下:
vm.$watch('属性名', {
immediate: false, // 为true时初始化调用handle函数
// newValue: 变换后的值,oldValue: 变换前的值
handler(newValue, oldValue){...} } });
});
深度监测:Vue中的watch默认不监测对象内部值的改变(一层),配置
deep: true可以检测对象内部值改变(多层)。特别地,Vue自身可以检测对象内部值的改变,但Vue提供的watch默认不可以
· computed VS watch:
- computed能实现的功能,watch都可以实现
- watch能实现的功能,computed不一定能实现,例如:watch可以进行异步操作(定时、延时等)
- 所有被Vue管理的函数,最好写成普通函数,这样确保this的指向是vm或者组件实例对象
- 所有不被Vue管理的函数,最好写成箭头函数(例如:定时器的回调函数、ajax的回调函数、promise的回调函数等),这样确保this的指向是vm或者组件实例对象
· 响应式数据原理
数据劫持: 对Vue的data中的任何属性进行操作时,都会被Vue解析并生成get和set函数用于更好的数据修改和监测
Vue2: 基于Object.defineProperty中的get和set方法进行数据劫持,实现数据的响应式
Vue3: 基于ES6的Proxy实现数据响应式的操作,对于引用数据类型,借助reactive函数中的Proxy
1.5 样式与结构渲染
· 绑定class样式
- 字符串写法,适用于:样式的类名不确定,需要动态指定
- 数组写法,适用于:要绑定的样式个数不确定、名字也不确定。数组写法,个数和样式可增、删、改
- 对象写法,适用于:要绑定的样式个数确定、名字也确定,需要动态指定用不用
<div class="basic" :class="mood" @click="changeMood">
<h1 :class="textObj" @click.stop="changeText">{{name}} feels {{mood}}</h1>
</div>
<script>
const vm = new Vue({
el: '.basic',
data: {
name: 'Yui',
mood: 'normal', // 字符串写法
moodArr: ['happy', 'sad', 'norml'], // 数组写法
textObj: { // 对象写法
text1: true,
text2: false,
text3: false
} },
methods: {
changeMood(){
var index = Math.floor(Math.random()*3);
this.mood = this.moodArr[index];
},
changeText(){
this.textObj.text1 = !this.textObj.text1;
this.textObj.text2 = !this.textObj.text2;
this.textObj.text3 = !this.textObj.text3;
} },
});
</script>
· 动画样式(CSS)
- 过渡:transition,可以配合使用,需要注意的是,标签内只能包裹单个元素,若要包裹多个元素,需要使用,写需要对元素抱歉设定key值,transition标签需要搭配v-show/v-if使用(<transition标签详解>)
- 动画:animation
- 引入animate.css库,只需在指定标签中配置好以下三个属性即可:(animate.css样式网址)
1)class="animateanimated animatebounce"
2)enter-active-class="参照官方文档"
3)leave-active-class="参照官方文档"
· 条件渲染 v-show VS v-if
- 相同点:可以在浏览器页面中隐藏元素,作用于CSS当中的display属性
- 区别:
1)当开启隐藏时,v-show仅隐藏元素的内容,元素结构仍然存在,v-if则会将元素结构干掉;因此,使用v-if时元素可能无法获取到,但是用v-show元素一定能被获取到
2)当v-if搭配v-else-if和v-else作用于不同的元素时,这些元素之间需要连续且不允许中间插入元素打断v-if和v-else-if之间的逻辑 - 何时使用:切换频率较高使用v-show,切换频率低使用v-if
<template>标签: 当需要对符合同一条件进行展示的多个元素作用的时候,可以使用template标签对其进行包裹,此标签不破坏原有的标签结构。该标签只与v-if配合使用,不对v-show生效。
<template v-if="value == 1">
<div>Yui</div>
<div>Tom</div>
<div>Tony</div>
</template>
特别地,当需要对template标签增加插槽时,除了slot="xxx"以外还有另外一种写法为:v-slot:xxx
· 列表渲染 v-for
- 语法结构:
1)v-for="(p, index) in 对象数组名" :key="index/p.id",其中p为对象形参,index为对象所在数组中的索引号
2)v-for="(p, keyName) in 对象名" :key="keyName",其中keyName为对象属性名
3)v-for="(char, index) in 字符串名" :key="index",其中char为单个字符,index为字符对应索引号
key的内部原理/作用:key是虚拟DOM对象的标识,当数据发生变化时,vue会根据新数据生成新的虚拟DOM,随后Vue进行新虚拟DOM与旧虚拟DOM的差异对比。
- 用index作为key可能引发的问题:
1)若对数据进行逆序添加、逆序删除等破坏顺序的操作,会产生不必的真实DOM更新(界面效果没问题,但效率低)
2)如果结构中还包含输入类的DOM,会产生错误的DOM更新,导致界面出错 - 开发中如何选择key:
1)最好使用每条数据的唯一表示,例如id
2)如果不存在对数据的逆序添加、删除等破坏顺序操作,仅用于渲染列表用于展示,可使用index作为key
· 表单数据收集
-
v-model修饰符:
a. v-model.number:将用户输入(e.g. input)的字符数字转换为Number类型
b. v-model.lazy: 原理-失去焦点的瞬间进行数据收集,适用于text文本框类元素
c. v-model.trim: 去除输入框内实际输入字符串前后的空格 -
v-model对不同输入框的数据收集:
1)<input type="text">,收集value,即用户的输入值
2)<input type="radio">,收集value,需要给标签配置value,若没有value,收集的则是checked的状态(boolean)
3)<input type="checkbox">,不配value属性时,收集checked的状态,配置value时,根据v-model的初始值来判断,当为非数组时,收集的仍然是checked的状态,为数组时,收集的是value组成的数组
1.6 生命周期钩子
- beforeCreate()函数:此时,无法通过vm访问到data中的数据、methods中的方法
- created()函数:初始化,数据监测、数据代理,此时,可以通过vm访问到data中的数据、methods中配置的方法
- beforeMount()函数:将内存中的虚拟DOM转为真实DOM,此时页面呈现的是未经Vue编译的DOM结构,所有对DOM的操作,最终都不奏效
- mounted()函数:Vue完成模板的解析并把初始的真实DOM元素放入页面后(挂载完毕)调用mounted
- beforeUpdate()函数:此时数据是新的,但页面是旧的,即:页面尚未和数据保持同步
- updated()函数:数据是新的,页面也是新的,即:页面和数据保持同步
- beforeDestroy()函数:此阶段,vm中的所有data,methods、指令等仍然可用,但关闭定时器、取消订阅消息、解绑自定义事件等,马上要执行销毁过程。
- destroy()函数:完全销毁一个实例,清理其与其他实例的连接,解绑其全部指令及自定义事件监听器,原生事件不变
- nextTick()函数:当需要DOM渲染页面结束后再呈现数据时可调用nextTick函数,使用示例:
this.$nextTick(function(){
this.$refs.inputTitile.focus()
})
1.7 JS高阶函数的使用
- filter()函数:遍历数组并将每个元素传入给filter的回调函数,且该回调函数必须返回一个布尔值,当为true(符合所需条件)时,该value将会被加入到新的数组中
let newArr = oldArr.filter(callbackfn(value){ return boolean;});
const oldArr = [10, 20, 103, 99, 219];
let newArr = oldArr.filter(function(val){
return val < 100;
});
console.log(newArr); // 输出:[10, 20, 99]
- map()函数:遍历数组并将对每个元素的处理结果返回至新数组的对应索引号中
let doubleArr = newArr.map(function(val){
return val * 2;
});
console.log(doubleArr); // 输出:[20, 30, 198]
- reduce()函数:3. 遍历数组,对数组的元素值进行相加并返回
let total = doubleArr.reduce(function(preVal, val){
/* preVal: preVal + val
val: 当前从数组中传入的元素值
initVal: 初始化值,用于第一个preVal
*/
}, initVal);
console.log(total); // 输出:248
2. Vue框架组件
2.1 组件化思想
组件是可复用的实例,组件的本质是一个名为VueComponent的构造函数,由Vue.extend生成;开发人员只需写
<组件标签名></组件标签名>,Vue解析时会创建组件的实例对象,即:Vue执行new VueComponent(options)。
特别地,每次调用Vue.extend,返回的都是一个全新的VueComponent.
· 全局组件和局部组件:在Vue实例外注册的组件为全局组件,在Vue实例中注册的组件为局部组件e.g.
const myCpn = Vue.extend({ // 组件构造器
template: `
<div>
<h1></h1>
<p></p>
</div>
`
})
Vue.component(' cpn', 'myCpn'); // 全局组件
const vm = new Vue({
el: 'root',
data: {...},
components: {
cpn: myCpn // 局部组件
}
});
· 父组件与子组件:子组件在父组件的组件模块中注册,父组件在Vue实例中注册,则父组件构造器的模板中可以调用子组件模块
2.2 组件间通信
- 父子组件的通信
1)父传子:props(只读属性),props属性对应的可以是数组和对象,传递和接收数据的三种方式:
//1. 传递数据
<Demo name="xxx"/>
// 2. 接收数据
props: [ 'name' ], // 方式一
props: {
name: Number, // 方式二
}
props: { // 方式三
name: {
type: String, // 数据类型
required: Boolean, // 必要性
default: 'Yui' // 默认值
}
}
2)子传父:子组件元素的绑定时间,可通过this.$emit('prop', val); 将事件发射给父组件进行响应,例如:
// 父组件-写法一
<Student v-on:atguigu="getStudent"/>
// 父组件-写法二
<Student ref="student"/>
methods: {
getStudent(name){...}
},
// 写法二使用,通过atguigu将getStudent与子组件的emit搭配使用
mounted(){
this.$refs.student.$on('atguigu', this.getStudent);
}
// 子组件
<button @click="getName">click</button>
methods: {
getName(){
this.$emit('atguigu', this.name)
}
}
补充说明:使用ref则不需要在元素上声明自定义事件,而是通过调用
$refs的方式手动声明、绑定事件,但都需要子组件emit触发事件搭配使用。另外,通过this.$refs.xxx可以获取子组件实例对象上的所有数据并使用
Tips: 父组件中接收子组件emit传过来的多个参数的方法,可以写成例如:
if: this.$emit('abc', name, x, y, z)
getName(name,...params) // 此处param包含除name以外的参数
2. 父子组件的访问方式
1)父组件访问子组件:this.$children,通过此方法将返回一个包含所有子组件的数组,缺点:当开发过程中插入新的子组件,索引号对应的组件可能不再是目标组件。此时可以考虑使用this.$refs, 通过refs将返回一个包含子组件的对象,特别地,需要给每一个组件增加一个ref的key值(相当于子组件的id)以作区分。举例:
<h1 v-text="msg" ref="xxx"></h1>
console.log(this.$refs.xxx); // 真实DOM元素
2)子组件访问父组件:this.$parent访问父组件,this.$root访问根组件(Vue实例)
3)自定义事件解绑:可以通过调用this.$off()解绑父子组件的自定义响应事件,共有三种写法:· 解绑单个事件:this.$off('atguigu') · 解绑多个事件:this.$off(['atguigu', 'abc']) · 解绑该父子组件上的所有自定义事件:this.$off()
3. 兄弟组件的事件响应:安装全局事件总线,实现任意组件间通信。
全局总线所需具备的因素:a) 能被所有组件访问,b) 具有
$on、$off、$emit
1)全局事件总线实现方式:
a. 在main.js中vue实例创建之前声明vue实例绑定至全局总线上,这么做的目的是:将Vue原型对象绑定给全局总线bus,以便后续组件事件触发时对应的vc访问到vue中的$on、$off和$emit
beforeCreate(){Vue.prototype.$bus = this;}
b. 在组件挂载时对事件触发进行监听,当事件触发时触发回调函数:
mounted(){ // school:事件名称, name:需要接收的参数
this.$bus.$on('school', (name)=>{
console.log(name);
this.schName = name;
}
}
c. 在事件触发组件中发送事件:
methods:{sendName(){this.$bus.$emit('school', this.name)}}
2)消息订阅与发布:需要借助pubsub-js包,在接收消息方订阅消息,在发送消息方发布消息:
// 消息发送方, this.name:需要发送的参数
methods: { btnclick(){pubsub.publish('school', this.name);}}
// 消息接收方
mounted(){
// school:订阅名称,msg:消息名称,通常不适用,用占位符_占位,name,消息发布的具体参数信息
this.subId=pubsub.subscribe('school', (_, name)=>{
this.schName = name;
});
}
3)以上两种自定义事件,都需要实例销毁前对其进行事件的解绑:
beforeDestroy(){
this.$bus.$off('school');
pubsub.unsubscribe(this.subId);
}
2.3 常用组件介绍
- mixin混入:将组件中都有的属性或方法单独提取一个mixin.js文件并由各个组件引用,以达到相同属性或方法复用的效果。混入分为全局混入和局部混入,写法分别为:
// 全局混入: main.js文件中
import {mixin1, mixin2} from './mixin';
Vue.mixin(mixin1);
Vue.mixin(mixin2);
// 局部混入:由各自组件分别引入,以组件一为例:
<script>
import {mixin} from '../mixin'
export default {
name: 'School',
data(){...},
mixins: [mixin],
}
</script>
// mixin.js文件
export const mixin= {
methods: {
showName(){
alert(this.name);
} }, }
- 插件plugins:包含一个install对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传入的参数(install(Vue, pram1, pram2,...) )。插件用于增强Vue功能,使用方法:
Vue.use(plugins), plugins.js定义如下:
export default {
install(Vue){
// 全局过滤器
Vue.filter('mySlice', function(value){...});
// 定义全局指令
Vue.directive('fbind', {...},
// 指令所在元素被插入页面时
inserted(element, binding){...},
// 指令所在模板被重新解析时
updated(element, binding){...},
});
// 定义混入
Vue.mixin({...});
// 给Vue原型上添加一个方法(vm、vc就都能用了)
Vue.prototype.hello = ()=>{alert("Hello!")};
},
}
- scoped:让样式在局部生效,防止冲突
- 插槽slot:让父组件可以向子组件指定位置插入htmli二狗,也是一种组件间通信的方式。
1)插槽的类型:
· 默认插槽:自定义组件中的所有标签都替换进中。
· 具名插槽:对同一组件,需要根据不同需求更改标签,可对具体的插槽命名,在组件同通过slot="插槽名"进行引用
· 作用域插槽:数据在组件自身,但根据数据生成的结构需要组件的使用者来决定的时候(例如:当子组件的数据仅作用于子组件模块中,父组件无法直通引用时),可以使用作用域插槽进行数据传递,传递的数据类型为对象,且此数据的传递必须通过<template>标签实现,示例:(其中,{games}为解构赋值,ES6语法;slot-scope为新API写法)
// 父组件
<template scope/slot-scope="xxx(接收数据对象名/形参)/{games}">
<ul>
<li v-for="(g, index) in xxx.games/games" :key="index">{{g}}</li>
</ul>
</template>
// 子组件
<slot :games="games"></slot>
data(){
return {
games: ['超级玛丽', '拳皇', '王者荣耀', '绝地求生']
}
}
2.4 模块化与打包(配置)
- 模块化:常用的模块化规范:CommonJS、AMD、CMD、ES6的Modoules,几种常用的 导入导出的方式:
// 方式一:
export {
flag, sum
}
// 方式二:
export var num1 = 1000;
import {}
// 方式三:导出函数/类
export function sum(num1, num2){
return num1 + num2;
}
// 方式四:导入者自己命名, 一个js文件只能有一个default导出
export default function(){...}
import myFun from "./xxx.js" // myFun为自定义函数名
// 方式五:统一全部导入
import * as xxx from "./xxx.js"
-
Webpack: 静态模块化打包工具
· grunt、gulp以及webpack
grunt和gulp的核心是task,适用于模块简单、甚至没有使用到模块化的工程项目,只需grunt/gulp进行简单合并d、压缩等。但若整个项目采用模块化管理,并且模块之间的相互依赖性非常强,就可以考虑使用webpack
· grunt、gulp和webpack的区别:
1)grunt和gulp更加强i奥迪是前端流程的自动化,模块化不是其核心
2)webpack更加强调模块化开发管理,而文件压缩合并、预处理等功能是它的附带功能
· ES6语言打包成ES5 -
Vue的安装方式:
1)npm install vue --save
2)官方下载vue.js文件并由外部引入
3)CDN引入