1.关于Vue
Vue是一个构建用户界面的渐进式JS框架
构建用户界面:在合适的时间 展示合适的数据给用户
渐进式:自底向上逐层的应用,可以从简单(小乔核心库)到复杂(各类Vue插件)
特点:
- 组件化编程(复用、方便维护):将HTML+CSS+JS整体地封装成一块
- 声明式编码:无需直接操作DOM
命令式编码:直接操作DOM的JS原生代码
- 虚拟DOM+Diff算法:添加数据不必全部更新,比较后直接插入
Diff算法:一种比较算法,用于更新虚拟DOM
2.配置项
1.data
//普通型
data:{
n:1
}
//函数型
data() {
return {
n:1
}
}
2.methods
方法设置
3.computed
- computed适合处理的场景是,获得一个值或者结果,该结果受其他的依赖的影响。(一个数据受多个数据影响),计算属性是基于响应式的依赖进行缓存,而只有定义在data中的参数才是响应式的,这个得特别注意
- 以下列代码为例–调用get()时机:初次读取isAll时;isAll所依赖的数据发生改变时【其余时候直接读取第一次调用get后存在缓存中的值】
// 只有定义在data中的数据才拥有响应式
// 计算属性只有在data中获取到初始数据才有意义
computed: {
//自定义属性值(全)
isAll: {
set(val) {
// isAll的值被事件触发发生改变时, 传入改变后的值val
},
get() {
//get调用的两个时机:初次读取isAll时、isAll所依赖的数据发生改变时
//其余时get()完会存入缓存,不再重复调用get()
return (计算原始数据,发生改变之后就是isAll的初始值)
},
},
//自定义属性值(简)---仅有get()时
isnAll(){
return xxx
}
},
4.watch
watch适合处理的场景是,侦听一个数的变化,当该数据变化,来处理其他与之相关数据的变化
监听器配置项
// 深度监听和立刻执行(不用写this即可获取到值:data里面的变量)
// 监听器只能起到监听数据变化的效果,不能改写数据
watch: {
//list是要监听的对象
list: {
immediate: true, // 立即执行
deep: true, // 深度监听复杂类型内变化
handler(newVal, oldVal) {
// newVal是改变后的状态
// oldVal是改变后的状态
console.log(newVal);
},
},
//仅handler时可简写
alist(newValue,oldValue){
xxx
},
}
//vm创建完成,后续发现需要监视
vm.$watch("list",{xxx})
3.指令
1.数据绑定
- v-bind=xxx:单向数据绑定 简写为 : xxx
- v-model:value=xxx:双向数据绑定 简写为 v-model=xxx
表单收集数据中,v-model修饰符:
.lazy:失去焦点在收集数据
.number:输入的字符串转化为有效数字
.trim:输入首尾空格过滤
2.事件绑定
- v-on:click=fn:事件绑定 简写为 @click=fn @keyup=fn @keydown=fn
事件修饰符:
.prevent:阻止默认事件 .stop:阻止事件冒泡 .once:事件只执行一次 .capture:事件捕获形式 .self:(虽然vue管理的this指向vm,但按照原生js理解为:)e.target为this时才触发
键盘事件修饰符:
.enter:回车 .delete:删除、退格 .esc:退出 .space:空格 .tab,.ctrl,.alt,.shift,.meta/win:必须配合keydown使用,以tab为例,其本身便有切换焦点的的功能 .up .down .left .right:上下左右
多个时间修饰符:
连写,例如@keydown.ctrl.y:同时按下ctrl和y触发事件
3.条件渲染
- v-show=“true/false”:调整display确定标签是否展示 —操作Style
- v-if=“true/false”😗*确定标签是否存在 ** —操作Node
一般用v-show,不用重复渲染页面
v-if v-else-if v-else-if … v-else 一整套在HTML结构中不允许被打断
4.列表渲染
- v-for:列表渲染
表现形式
1.v-for=“p in persons” :key=“p.id” {{p}}
2.v-for=“(p,index) in persons” :key=“index” --遍历数组,先接收p
3.v-for=“(value,key) in Car” :key=“key” --遍历对象,先接收value
4.v-for=“(char,index) in str” :key=“index” --遍历字符,先接收char
所谓先接收,即如果遍历数组时写成 v-for=“k in persons”,则k为p
5.数据填充
- v-text**:完全替换**标签内文本 —同innerText
- v-html:可解析html标签 —同innerHTML—故也易受XSS攻击(假借用户之手,cookie)
6.无值指令
- v-cloak
- 一个属性,Vue接管容器时删除
- 作用:结合css,解决网速慢时页面展示出未解析模板的问题
- v-once
- 一个属性
- 作用:所在标签仅在初始化时动态渲染一次
- v-model:
- 一个属性
- 作用:Vue将跳过所在标签的编译,效率up↑
4.底层
1.数据代理与数据劫持
1.数据代理【代理模式】模拟
通过一个对象代理另一个对象中属性的操作(读/写)
例如:下例通过obj2代理obj1,对obj2的操作最终都落到obj1上
let obj1={x:100}
let obj2={y:200}
Object.defineProperty(obj2,"x",{
get(){
return obj1.x;
},
set(val){
obj1.x=val;
}
})
2.Vue中的数据代理
在vue中的数据代理,实际上是**通过vm上的属性代理对_data中属性的操作 **
3.Vue中的数据劫持
在vue中的数据劫持,实际上是**劫持到vm上属性的变化,去重新解析模板,更新页面,实现响应式效果 **
4.vue中的数据代理与数据劫持(以vm.name为例)
data中的内容加工(Setter响应式,数据劫持)后,赋值给_data,然后代理(数据代理)给vm上的属性
2.Vue中的函数
- 所有被vue管理的函数,最好写成普通函数(this指向vm或组件实例对象)
- 所有不被vue管理的函数(定时器回调、Ajax回调、Promise回调…),最好写成箭头函数(this指向vm火组件实例对象)
3.Diff算法
在列表渲染时,通过将key与id绑定,在更新节点时使用Diff算法将真实DOM与新虚拟DOM进行比较,相同的直接复用,旧的新生成,以达到结点服用的功能
4.Vue监测数据更新的原理
1.对象中的数据
Setter监视:原理即Vue中的数据劫持
一个重要的API:
Vue.set(target,key,value) / vm.$set(target,key,value)
- 作用:让后续追加在**_data中的数据也能实现响应式**
- 参数:target:追加目标 key:追加名 value:追加值
- 局限:只能给data中的响应式对象追加响应式属性,而不能操作vm与data自身
2.数组中的数据
包装数组身上常用的修改数组(直接修改自身)的方法:底层只做了两件事:1.通过原型链调用“七法” 2.重解析模板
七法:push,pop,shift,unshift,splice,sort,reserve
splice(start,deleteCount,items):
start:要开始改变数组的位置
deleteCount:要删除的元素个数
items:要加入的元素
原理:
若不用“七法”:
- 代码中–无法监视–无法修改
- 控制台中(赋值修改)–可以修改但无法监视–无响应式
5.过滤器
- 语法:
- 注册:
- 局部:filters:{xxx}
- 全局:Vue.filter(name,callback)
- 使用:{{xxx|过A|过B}} / v-bind:value=“xxx|A|B”
- 注册:
- 注意项:
- 默认只接收value,但也可以接受其他
- 多过滤可串联
- 过滤不改变源数据
- 实例:
<body>
<div id="root">
<div>{{name}}</div>
<div>付款:{{money | addIcon }}</div>
</div>
</body>
<script>
Vue.config.productionTip = false;
// 全局注册
Vue.filter("addIcon",(value)=>{
return value + "元"
})
const vm = new Vue({
el:'#root',
data:{
name:"张三",
money:200
},
// 局部注册
// filters:{
// addIcon(value){
// return value + "元"
// }
// }
})
</script>
6.生命周期
7.组件化编程
1.概念
2.概念小结
- 组件:相当于把一整个页面拆分成一个个包含了完整html,js,css的小页面,代码复用性up↑
- 由图可见,组件化编程分为三步:创建组件构造器(Vue.extend)并编写组件->组件注册->使用组件
- 组件编写中的配置项:
- name:在Vue开发者工具中可见的“组件名”,相当于一个注释,在代码中不会用到
- template:代码块,用于放置HTML代码块,且代码块必须放置在一个根目录下
- data():template中的数据
- vc中methods,computed,watch等使用同vm
- 组件注册:
- 全局Vue.component(“xxx”,obj) /局部:components:{xxx:obj}
- 这里的xxx才是html页面中要用到的组件名
- Vue.component:
- 所以,vc是可以访问到vm.__proto__上的属性和方法的
3.单文件组件
//xxx.vue
<template>
<!--HTML代码块 -->
</template>
<script>
export default{
data(){
return{
xxx
}
},
methods:{
xxx
},
...
}
</script>
<style>
/* 组件的样式 */
</style>
4.组件的引入和嵌套
1.组件的引入
ES6模块化的引入,import xxx from “xxx.vue”
2.组件的嵌套
- 非单文件组件·本地注册:**直接在template中使用,但是
- 单文件组件·外部引入:先import,再在此单文件组件中注册该组件,最后使用【引入,注册,使用】
3.父子组件通信
1.父向子传递
- 父组件通过v-bind绑定value,子组件用props接收数据并直接使用【传过来的数据绑定在了子组件vc实例上】
2.子向父传递
- 在子组件内通过 $emit触发父组件上的自定义事件并将参数传给监听器【即父组件】回调
关于vm.$emit( eventName, […args] )
ctrl+f:快速查找方法
- 参数:
- {string} eventName 触发的事件名
- […args] 传递给事件的参数
- 作用: 触发当前实例上的事件。附加参数都会传给监听器回调。
- 内部原理:
$emit也是采用了发布订阅者设计模式。
Vue.prototype.$emit = function (event: string): Component {
const vm: Component = this
let cbs = vm._events[event]
if (cbs) {
cbs = cbs.length > 1 ? toArray(cbs) : cbs
const args = toArray(arguments, 1)
for (let i = 0, l = cbs.length; i < l; i++) {
try {
// apply() 方法调用一个具有给定 this 值的函数,
// 以及以一个数组(或一个类数组对象)的形式提供的参数。
cbs[i].apply(vm, args)
} catch (e) {
handleError(e, vm, `event handler for "${event}"`)
}
}
}
return vm
}
}
根据传入的事件名从当前实例的_events属性(即事件中心)中获取到该事件名所对应的回调函数cbs,然后再获取传入的附加参数args,由于cbs是一个数组,所以遍历该数组,拿到每一个回调函数,执行回调函数并将附加参数args传给该回调。
3.实例
<body>
<div id="root">
<app></app>
</div>
</body>
<script>
Vue.config.productionTip = false;
// 父组件
const app ={
name:"app",
template:`
<div>
<child1 :messageToChild="message"></child1>
<child2 @childFn="parentFn"></child2>
</div>
`,
data() {
return {
message:"Message from app",
}
},
methods: {
parentFn(message){
this.getMessage=message;
}
},
}
// 子组件1--父向子传递信息
const child1 = {
name:"child1",
template:`
<div>
<p>我是子组件1</p>
<p>{{messageToChild}}</p>
</div>
`,
props:['messageToChild']
}
// 子组件2--子向父传递信息
const child2 = {
name:"child2",
template:`
<div>
<p>我是子组件2</p>
<p>{{messageInChild}}</p>
<!-- <button @click="submit"></button> -->
</div>
`,
data() {
return {
messageInChild:"Message from child"
}
},
// methods:{
// submit(){
// this.$emit("childFn",this.messageInChild)
// }
// }
mounted() {
this.$emit("childFn",this.messageInChild)
},
}
// 全局注册
Vue.component("app",app);
Vue.component("child1",child1);
Vue.component("child2",child2);
const vm = new Vue({
el:'#root',
// 组件递归中(即直接在template里面嵌套子组件),不能使用局部注册
// components:{
// app,
// child1,
// child2
// }
})
console.log(vm);
</script>
5.单文件组件的开发
- 组件间的引入和嵌套见4
- 随后所有的组件交由一个大的组件App.vue管理;然后新建一个main.js,用于创建vm实例并使用App大组件;最后在index.html实际执行页面中引入main.js即可
6.实例
1.非单文件组件–本地注册
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="../vue2/vue.js"></script>
<title>Document</title>
</head>
<body>
<div id="root">
<!-- 编写组件标签 -->
<student></student>
<hr>
<school></school>
</div>
<script>
Vue.config.productionTip = false;
// 创建学生组件
const student=Vue.extend({
name:'studentTagInVueDevelopment',
template:`
<div>
<h1>学生姓名:{{name}}</h1>
<h1>学生年龄:{{age}}</h1>
</div>
`,
data() {
return {
name:"张三",
age:18
}
},
})
//创建学校组件,简写方式不用Vue.extend,直接大括号即可
const school = {
name:'schoolInVueDevelopment',
template:`
<div>
<h1>{{name}}</h1>
<h1>{{address}}</h1>
<hr>
<school-door></school-door>
</div>
`,
data() {
return {
name:"atGuigu",
address:"北京昌平"
}
},
}
// 创建学校大门组件,归属于学校组件
const door = {
// name相当于Vue开发者工具中的“注释”
name:'doorInVueDevelopment',
template:`
<div>
<h1>南大门:{{south}}</h1>
<h1>北大门:{{north}}</h1>
</div>
`,
data() {
return {
south:'智行街185号',
north:"天星路173号"
}
},
}
//组件全局注册,这里注册的名字才是要用的名字
Vue.component('student',student);
Vue.component('school',school);
Vue.component('school-door',door);
const vm = new Vue({
el:'#root',
// 组件局部注册
// components:{
// student:student,
// school
// }
})
console.log(student);
</script>
</body>
</html>
2.单文件组件–组件的引入与管理
注意:单文件组件的开发只能在脚手架环境中才能正常执行
<!-- door,school的子组件 -->
<template>
<div>
{{ address }}
</div>
</template>
<script>
export default{
data() {
return {
address:"北大门"
}
},
}
</script>
<!-- school,受App组件管理 -->
<template>
<div id="school">
name:{{ name }}
address: <door></door>
</div>
</template>
<script>
import door from './door.vue';
export default{
data() {
return {
name:"小天星幼儿园"
}
},
components:{
door
}
}
</script>
<!-- stu,受App组件管理 -->
<template>
<div>
{{ options }}
</div>
</template>
<script>
export default{
data() {
return {
options:"stu"
}
},
}
</script>
<!-- App,所有组件的总管,一个大组件 -->
<template>
<div>
<school></school>
<stu></stu>
</div>
</template>
<script>
import school from "./school.vue"
import stu from "./stu.vue"
export default{
components:{
school,
stu
}
}
</script>
<!-- mian.js,用于创建vm实例并使用App大组件 -->
import App from "./App.vue"
new Vue({
el:"#root",
template:`<App></App>`,
components:{App}
})
// index.html,页面实际执行者
<body>
<!-- 准备一个root容器 -->
<div id="root"></div>
<!-- 引入vue,防止main.js无vue可用 -->
<script src="../../vue2/vue.js"></script>
<!-- 引入main.js -->
<script src="./main.js"></script>
</body>
8.Vue简单应用
1.Vue样式绑定
数据单向绑定的应用
1.绑定Class样式
//字符串写法
:class="mood" ==>data内:mood:"xxx"
//数组写法
:class="arr" ==>data内:arr:["a","b","c"]
//对象写法
:class="obj" ==>data内:obj:{a:false,b:false,c:true}
2.绑定style样式
原CSS短横杠形式key要改用驼峰命名法,如font-size==>fontSize
//对象写法
:class="styleObj" ==>data内:obj:{fontSize:'40px',...}
9.Vue-Router
- 用 Vue + Vue Router 创建单页应用非常简单:
- 通过 Vue.js,我们已经用组件组成了我们的应用。
- 当加入 Vue Router 时,我们需要做的就是将我们的组件映射到路由上,让** Vue Router 知道在哪里渲染**它们。
10.Vue-cli
1.脚手架目录结构
//带*号的:需要才用,其余的目录文件是必须的
├── node_modules
├── public
│ ├── favicon.ico: 页签图标
│ └── index.html: 主页面
├── src
│ ├── assets: 存放静态资源
│ │── component: 存放组件
│ │── router:存放路由
│ │── *mixin:存放混入js
│ │── views:存放子页面(组件)
│ │── App.vue: 汇总所有组件
│ │── main.js: 入口文件
├── .gitignore: git版本管制忽略的配置
├── babel.config.js: babel的配置文件//babel:用于es6转es5
├── package.json: 应用包配置文件
├── README.md: 应用描述文件
├── package-lock.json:包版本控制文件
├── vue.congfig.js:可编写的更改的配置文件//文档参考:https://cli.vuejs.org/zh/config/
2.命令,Vue库组成,render函数,ref标识,props配置项
- dev是vue cli2的默认执行命令,serve是vue cli4的默认执行命令
- vue包括两部分:核心库+模板解析器
- render函数:防止引入的是残缺版的vue,作用是生成template模板
- render:h=>h(App),将APP组件放入容器中
- ref属性:vue中对元素的标识,可以标识原生dom元素,也可以标识组件 this.$refs.xxx,前者替代id,后者可以得到组件的实例对象vc
- 标识原生dom元素: ,获取dom元素
- 标识组件:,获取vc实例
- props配置项:优先级高于data,作用就是父向子通信
3.mixin混入
- 作用:多个组件共享一套配置,以分发 Vue 组件中的可复用功能
- 形式:局部注册:mixins:[mixin1,mixin2,…] /全局注册:Vue.mixin(mixin1)
- 局部注册使用方法:
- 将要复用的js部分单独挑出来
- 单独写入一个js文件中并export暴露出来
- 在各个组件内import引入
- 在mixins配置项中注册该混入
- 全局注册使用方法:
- 前二相同
- 不必再在各个组件中引入,直接在main.js中import并全局注册
- 注意:
- 普通配置项/属性 混入与自身冲突时,选择合并,即不同名的合并,同名的则以自身为主
- 钩子函数混入与自身冲突时,来者不拒且混入钩子先于自身钩子
- 全局注册后,所有的vc以及vm都将包含有这个混入对象
- 实例:
//mixin.js
export const showName = {
methods: {
showName(){
alert(this.name)
}
}
}
export const data = {
data() {
return {
name:"name from mixin",
num:3
}
}
}
//局部注册--vc
import { showName } from '@/mixin/mixin';
export default{
name:"School",
mixins:[showName]
}
//全局注册--vm,main.js
import { data } from './mixin/mixin'
Vue.mixin(data)