目录
一.Vue基础
- Vue:构建用户界面的 渐进式 JavaScript框架
- 渐进式:声明式渲染→组件系统→客户端路由→集中式状态管理→项目构建
- Vue代码运行原理分析:Vue 代码 通过 Vue框架 转换为 原生JS
- 官网:https://cn.vuejs.org/v2/guide/
- 参数分析:
- el:元素的挂载位置(值:CSS选择器 / DOM元素)
- data:模型数据(值:对象)
- methods:声明方法
- directives: 自定义指令
- computed:计算属性
- watch:侦听器
- filters:过滤器
components:组件
- 插值表达式:将数据填充到 HTML标签,支持基本计算和字符串拼接
- 前端渲染:将数据填充到 HTML标签中,产生静态HTML内容
- 前端渲染方式:
- 原生js拼接字符串:代码风格差异大,后期维护困难
- 前端模板引擎:按规则写,后期维护方便,但缺少专门的事件机制
- vue特有的模板语法
二.Vue模板语法
- 模板语法概览:插值表达式、指令、事件绑定、属性绑定、样式绑定、分支循环结构
1.指令
- 指令:本质就是自定义属性,格式以 v- 开始
1.1 v-cloak指令
- 使用 v-cloak 可解决:插值表达式存在的“闪动”问题
- 解决原理:先通过样式隐藏内容,然后在内存中进行值的替换,替换好后再显示最终结果
1.2 数据绑定指令
- 数据绑定:将数据填充到标签中
- 数据绑定指令:
- v-text:填充纯文本,比插值表达式简洁,没有闪动问题,不会解析 html标签
- v-html:填充 HTML片段,存在安全问题,本网站内部数据可用,第三方数据不可用,会解析 html标签
- v-pre:填充原始信息,跳过编译过程,可加快静态内容渲染
- v-text 为单向绑定:数据对象上的值改变,插值会发生变化;但插值发生变化,并不影响数据对象的值
1.3 响应式
- HTML5中的响应式:屏幕尺寸变化,导致样式变化
- 数据的响应式:数据变化,导致页面内容变化
- v-once:只编译一次,显示内容后不再有响应式功能
- data里定义了msg,后期我们修改了,仍然显示第一次 data里面存储的数据
- v-once应用场景:如果显示的信息后续不需要再修改,可用v-once,提高性能
1.4 双向数据绑定指令
- 双向数据绑定:
- 当数据发生变化时,视图也发生变化
- 当视图发生变化时,数据也发生变化
- 当输入框中内容改变的时候,页面上的 msg 会自动更新
- v-model:<input type='text' v-model='uname'/>
- v-model 限制在这些地方使用:
<input>、<select>、<textarea>、components
1.5 MVVM设计思想
- MVC:后端分层开发; MVVM:前端分层开发
- MVVM 分三部分:
- M(model):数据层,都放在 data里
- V((view):视图层,即HTML页面
- VM(View-Model):控制器,在数据层和视图层间建立联系,vm就是Vue实例
2.事件绑定
2.1 v-on指令处理事件
- v-on指令用法:<input type=‘button' v-on:click='num++'/>
- v-on简写:<input type=‘button' @click='num++'/>
2.2 事件函数调用
- 直接绑定函数名:<button v-on:click='say'>Hello</button>
- 调用函数:<button v-on:click='say()'>Hello</button>
- methods里的函数,this 指向 vue实例,函数中若想使用 data里的数据,一定要加this
2.3 事件函数传参
- 普通参数和事件对象:<button v-on:click='say("hi",123,$event)'>Say hi</button>
- 事件绑定函数名,默认会传递 事件对象 作为事件函数的 第一个参数
- 事件绑定函数调用,事件对象 必须作为 最后一个参数 传递,并且事件对象的名称必须是 $event
- 输入框中最新的值:event.target.value
2.4 事件修饰符
- .stop 阻止冒泡: <a v-on:click.stop="handle">跳转</a>,event.stopPropagation();
- .prevent 阻止默认行为:<a v-on:click.prevent="handle">跳转</a>,event.preventDefault();
- 修饰符串联:v-on:click.stop.prevent=...
.self event.target 是当前元素自身时触发:<div v-on:click.self="doThat">...</div>
- 使用修饰符时,顺序很重要,代码会顺序产生,因此:
- v-on:click.prevent.self:阻止所有的点击
- v-on:click.self.prevent:阻止对元素自身的点击
2.5 按键修饰符
- .enter 回车键:<input v-on:keyup.enter='submit'>/<input v-on:keyup.13="submit">
- 当点删除键 或 回车键时,调用方法:<input v-on:keyup.delete.enter='handle'>
- 自定义按键修饰符:全局 config.keyCodes 对象 Vue.config.keyCodes.f1 = 113
- 自定义按键修饰符命名自定义,但值必须是按键对应event.keyCode值
3.属性绑定
- v-bind 指令用法:<a v-bind:href='url'>跳转</a>
- 缩写:<a :href='url'>跳转</a>
- v-model 底层实现原理分析:使用输入域中的最新的数据覆盖原来的数据
- <input v-bind:value="msg" v-on:input="msg=$event.target.value">
4.样式绑定
4.1 class样式处理
- 对象语法:<div v-bind:class="{ active: isActive }"></div>
- 数组语法:<div v-bind:class="[activeClass, errorClass]"></div>
- 控制 isActive 在 true/false 间切换:this.isActive = !this.isActive;
- this.activeClass = '';
- 对象绑定和数组绑定可结合使用:
- <div v-bind:class='[activeClass, {test: isTest}]'>测试样式</div>
- 简写数组语法:arrClasses: ['active', 'error'],【data里声明】
4.2 style样式处理
- 对象语法:<div v-bind:style="{ color: activeColor, fontSize: fontSize }"></div>
- 数组语法:<div v-bind:style="[baseStyles, overridingStyles]"></div>
4.3 绑定对象和绑定数组 的区别
- 绑定对象时,对象属性=渲染类名,对象属性值=data中的数据
- 绑定数组时,数组存的=data中的数据
5.分支结构
- ⚫v-if ⚫v-else ⚫v-else-if ⚫v-show
- v-if VS v-show:
- v-if:控制元素是否渲染到页面
- v-show:控制元素是否显示(已经渲染到了页面)
- v-show本质:display设置为none,控制隐藏
- v-show只编译一次,v-if 不停的销毁创建,故 v-show 性能更好
- v-if 动态的向DOM树内添加删除元素
- v-if 切换有 局部编译/卸载 的过程
6.循环结构
- v-for 遍历 数组:
- <li v-for='item in list'> {{item}} </li>
- <li v-for='(item,index) in list'> {{item + '---' + index}} </li>
- <li :key='item.id' v-for='(item,index) in list'> {{item}} + '---' + {{index}} </li>
- :key的作用:区分不同元素,提高性能,高校更新虚拟DOM
- v-for 遍历 对象:
- <div v-for='(value, key, index) in object'></div> // 键值、键名、索引
- v-if 和 v-for 结合使用(不推荐一起用,此时 v-for优先级更高):
- <div v-if='value==12' v-for='(value, key, index) in object'></div> // 寻找键值为12的对象打印
7.Tab选项卡综合
- 模板的结构和最终显示的效果基本一致
- 把 data 中 title 利用 v-for 循环渲染到页面上
- 把 data 中 path 利用 v-for 循环渲染到页面上
<div id="app"> <div class="tab"> <ul> <!-- 动态绑定class:有 active类名高亮 无 active类不高亮--> <li @click='change(index)' :class='currentIndex==index?"active":""' :key='item.id' v-for='(item,index) in list'> {{item.title}} </li> </ul> <!-- 动态绑定class:有 current类名显示 无 current类隐藏--> <div :class='currentIndex==index?"current":""' :key='item.id' v-for='(item, index) in list'> <img :src="item.path"> </div> </div> </div> <script type="text/javascript" src="js/vue.js"></script> <script type="text/javascript"> var vm = new Vue({ el: '#app', data: { currentIndex: 0, // 选项卡当前的索引 list: [{ id: 1, title: 'apple', path: 'img/apple.png' }, { id: 2, title: 'orange', path: 'img/orange.png' }, { id: 3, title: 'lemon', path: 'img/lemon.png' }] }, methods: { change: function(index) { this.currentIndex = index; }}}); </script>
三.Vue常用特性
- 常用特性概览: ⚫ 表单操作 ⚫ 自定义指令 ⚫ 计算属性 ⚫ 侦听器 ⚫ 过滤器 ⚫ 生命周期
1.表单操作
- 基于Vue的表单操作:
- Input 单行文本:v-model='uname',通过v-model 双向绑定 一个值
- textarea 多行文本:v-model='desc',data:{desc:''...
- select 下拉多选:v-model='occupation' multiple,data:{occupation: ['2', '3'],...
- radio 单选框:v-model='gender'相同,data:{gender: 2,.... ,每个单选框必要有value属性 且value 值不一样
- checkbox 多选框:v-model='hobby'相同,data:{hobby: ['2', '3']...
- 表单域修饰符:
- number:转化为数值 <input v-model.number="age" type="number">
- trim:去掉首尾空格
- lazy : 将 input事件 切换为 change事件,在“change”时而非“input”时更新
2.自定义指令
- 局部指令(仅组件内可用):写在 vue实例内部
- 全局指令(全局通用):写在 vue实例外部
- 如果定义全局,Vue.directive 不是复数
- 如果定义局部,directives: 是复数
- 全局自定义指令:Vue.directive('focus' { inserted: function(el) { // 获取元素的焦点 el.focus(); } })
- 全局自定义指令用法:<input type="text" v-focus>
- 带参数的自定义指令:Vue.directive(‘color', { bind: function(el, binding) { el.style.backgroundColor = binding.value.color; } })
- el 为当前自定义指令的 DOM元素
- binding 为自定义的函数形参 通过自定义属性传递过来的值 存在 binding.value 里面
- 带参数的自定义指令用法:<input type="text" v-color='{color:"orange"}'>
- bind / inserted 使用效果几乎一样,带不带参数都可以用
- bind声明周期, 只调用一次,指令第一次绑定到元素时调用。可以进行一次性的初始化设置
- 局部自定义指令(写在vue实例里):directives: { focus: { inserted: function (el) { el.focus() } } }
- 局部指令只能在 当前组件 使用
- 当全局指令和局部指令同名时,以局部指令为准
3.计算属性
- 计算属性使模板内容更简洁
- computed: { reversedMessage: function () { return this.msg.split('').reverse().join('') } } // 写在vue实例内
- <div>{{reverseString}}</div>
- 计算属性与方法的区别:计算属性是基于依赖进行缓存的,而方法不缓存
- 计算属性进行缓存,控制台信息仅输出一次
- 方法不缓存,控制台信息输出两次
4.侦听器
- 侦听器应用场景:数据变化时 执行异步 或 开销较大的操作
- 数据一旦发生变化,就通知 侦听器绑定方法
- watch 中的属性 一定是data中 已经存在的数据
- watch: { firstName: function(val){ // val表示变化之后的值 this.fullName = val + this.lastName; }, lastName: function(val) { this.fullName = this.firstName + val; } }
- 当需要监听一个对象的改变时,普通的watch方法无法监听到对象内部属性的改变,只有data中的数据才能够监听到变化,此时就需要deep属性对对象进行深度监听
5.过滤器
- 过滤器作用:格式化数据,比如将字符串格式化为首字母大写,将日期格式化为指定的格式等
- 全局过滤器:Vue.filter('upper', function(val){ return val.charAt(0).toUpperCase() + val.slice(1); })
- 过滤器使用:
- <div>{{msg | upper}}</div> :插值表达式过滤器
- <div>{{msg | upper | lower}}</div>:级联使用过滤器
- <div v-bind:id=“id | formatId"></div>:属性绑定过滤器
- 局部过滤器:filters:{ 过滤器名称: function(){} }
- 带参数的过滤器:Vue.filter(‘format’, function(value, arg1){ // value就是过滤器传递过来的参数 })
- <div>{{date | format(‘yyyy-MM-dd')}}</div>
6.生命周期
- vue实例 从创建到销毁 的过程中 ,会伴随着一些函数的自调用,这些函数叫 钩子函数
- 挂载(初始化相关属性):
- beforeCreate:在实例初始化之后,数据观测和事件配置之前被调用,此时 data 和 methods 以及页面结构都没有初始化 什么都做不了
- created:在实例创建完成后被立即调用,此时 data 和 methods已经可以使用 但是页面还没有渲染出来
- beforeMount:在挂载开始之前被调用,此时页面上还看不到真实数据 只是一个模板页面
- mounted: el被新创建的vm.$el替换,并挂载到实例上去之后调用该钩子,数据已经真实渲染到页面上 在这个钩子函数里可用一些第三方插件
- mounted:这个方法执行后,证明初始化完成,可以调用后端接口获取数据了
- 更新(元素或组件的变更操作):
- beforeUpdate:数据更新时调用,发生在虚拟DOM打补丁之前,页面上数据还是旧的
- updated:由于数据更改导致的虚拟DOM重新渲染和打补丁,在这之后会调用该钩子,页面上数据已更新
- 销毁(销毁相关属性):
- beforeDestroy:实例销毁之前调用
- destroyed:实例销毁后调用
7.图书管理综合
7.1 数组相关API
- 变异方法(修改原有数据):会影响数组的原始数据的变化
- ⚫push(),后+1 ⚫ unshift(),前+1
- ⚫ pop(),后-1⚫ shift(),前-1
- ⚫ splice(),删除位置,删除个数,删除后替换值
- ⚫ sort(),升序 ⚫ reverse(),倒序
- 替换数组(生成新的数组):不会改变原数组,但返回一个新数组,需要赋值给原数组再显示
- ⚫ filter():返回符合条件的新数组
- ⚫ concat(),链接多个数组,不改变现有的
- ⚫ slice():截取开始位置,截取个数
- 修改响应式数据:
- Vue.set(vm.list, 2, 'lemon');
- vm.$set(vm.list, 1, 'lemon');
- vm.$set(vm.info, 'gender', 'female'); 也可以添加设置新属性
- vm.items:要处理的数组名称
- indexOfItem:要处理的数组的索引
- newValue:处理后数组的值
7.2 常用特性应用场景
- 过滤器(格式化日期)
- 自定义指令(获取表单焦点)
- 计算属性(统计图书数量)
- 侦听器(验证图书存在性)
- 生命周期(图书数据处理)
<body> <div id="app"> <div class="grid"> <div> <h1>图书管理</h1> <div class="book"> <div> <label for="id"> 编号: </label> <!-- 禁用绑定属性::disabled="flag",修改时才会禁用 --> <input type="text" id="id" v-model='id' :disabled="flag" v-focus> <label for="name"> 名称: </label> <input type="text" id="name" v-model='name'> <!-- 禁用绑定属性::disabled="submitFlag",提交名字不存在时才能提交 --> <button @click='handle' :disabled="submitFlag">提交</button> </div> </div> </div> <div class="total"> <span>图书总数:</span> <!-- 计算属性 统计图书总数 --> <span>{{total}}</span> </div> <table> <thead> <tr> <th>编号</th> <th>名称</th> <th>时间</th> <th>操作</th> </tr> </thead> <tbody> <!-- 添加 :key='item.id' 提高vue性能 ,遍历图书列表数组 --> <tr :key='item.id' v-for='item in books'> <td>{{item.id}}</td> <td>{{item.name}}</td> <!-- 通过过滤器,将时间格式化 --> <td>{{item.date | format('yyyy-MM-dd hh:mm:ss')}}</td> <td> <!-- 点击连接后,阻止默认跳转行为,并调用相应方法,根据id进行操作 --> <a href="" @click.prevent='toEdit(item.id)'>修改</a> <span>|</span> <a href="" @click.prevent='deleteBook(item.id)'>删除</a> </td> </tr> </tbody> </table> </div> </div> <script type="text/javascript" src="js/vue.js"></script> <script type="text/javascript"> /* 图书管理-添加图书 */ Vue.directive('focus', { // 自定义全局指令,实现焦点自动锁定 inserted: function(el) { el.focus(); }}); Vue.filter('format', function(value, arg) { // 自定义过滤器,实现时间格式化 function dateFormat(date, format) { if (typeof date === "string") { var mts = date.match(/(\/Date\((\d+)\)\/)/); if (mts && mts.length >= 3) { date = parseInt(mts[2]); } } date = new Date(date); if (!date || date.toUTCString() == "Invalid Date") { return ""; } var map = { "M": date.getMonth() + 1, //月份 "d": date.getDate(), //日 "h": date.getHours(), //小时 "m": date.getMinutes(), //分 "s": date.getSeconds(), //秒 "q": Math.floor((date.getMonth() + 3) / 3), //季度 "S": date.getMilliseconds() //毫秒 }; format = format.replace(/([yMdhmsqS])+/g, function(all, t) { var v = map[t]; if (v !== undefined) { if (all.length > 1) { v = '0' + v; v = v.substr(v.length - 2); } return v; } else if (t === 'y') { return (date.getFullYear() + '').substr(4 - all.length); } return all; }); return format; } return dateFormat(value, arg); }) var vm = new Vue({ el: '#app', data: { flag: false, // 编号的禁用绑定属性 submitFlag: false, // 提交按钮的禁用绑定属性 id: '', // 用户输入的图书编号 name: '', // 用户输入的图书名 books: [] // 书籍列表 }, methods: { handle: function() { // 提交按钮绑定的方法 // 提交按钮点击有两种情况:添加图书 / 修改图书,根据禁用状态确定的 if (this.flag) { // 修改图书状态 flag是true,证明编辑编号区域被禁用 是修改才做 // 就是根据当前的ID去更新数组中对应的数据 this.books.some((item) => { if (item.id == this.id) { // 如果图书列表的id = 点击的id item.name = this.name; // 那么图书列表的书名 改成 现在输入框的书名 // 完成更新操作之后,需要终止循环 return true; } }); this.flag = false; // 修改完成后,解除编号输入框的禁用状态 } else { // 添加图书 flag是false,证明编号禁用状态解除,即可以编辑编号,是添加操作 var book = {}; // 新建一个图书对象 book.id = this.id; book.name = this.name; book.date = new Date(); this.books.push(book); // 将图书对象追加到图书列表后面 } // 执行完添加/修改操作后,清空表单输入框 this.id = ''; this.name = ''; }, toEdit: function(id) { // 接收用户点击的id // 禁止修改ID this.flag = true; // 根据ID查询出要编辑的数据 var book = this.books.filter(function(item) { return item.id == id; }); // 把获取到的信息填充到表单 this.id = book[0].id; this.name = book[0].name; }, deleteBook: function(id) { // 删除图书 // 根据id从数组中查找元素的索引 // var index = this.books.findIndex(function(item){ // return item.id == id; // }); // 根据索引删除数组元素 // this.books.splice(index, 1); // ------------------------- // 方法二:通过filter方法进行删除 this.books = this.books.filter(function(item) { return item.id != id; // 查询所有跟点击id不同的数组元素并展示 }); } }, computed: { // 计算属性 total: function() { return this.books.length; // 计算图书的总数 } }, watch: { // 监听器 name: function(val) { // val是用户输入的值 // 验证图书名称是否已经存在 var flag = this.books.some(function(item) { return item.name == val; }); if (flag) { // 图书名称存在 this.submitFlag = true; // 开启提交按钮禁用状态 } else { // 图书名称不存在 this.submitFlag = false; } } }, mounted: function() { // 该生命周期 钩子函数 被触发时,模板已可以使用 // 此时用于获取后台数据,然后把数据填充到模板 // 这里用写死的模板数据假装后台数据返回值 var data = [{ id: 1, name: '三国演义', date: new Date() // 获取系统时间 }, ...]; this.books = data; }}); </script> </body>