- 了解什么是VUE
官网:https://cn.vuejs.org/
Vue.js是一套构建用户界面的渐进式框架。
Vue 采用自底向上增量开发的设计。
Vue 的核心库只关注视图层,它不仅易于上手,还便于与第三方库或既有项目整合。
另一方面,当与单文件组件和 Vue 生态系统支持的库结合使用时,Vue 也完全能够为复杂的单页应用程序提供驱动。(SPA单页面应用,所有的显示都在一个页面当中)
渐进式:一步一步,不是说你必须一次把所有的东西都用上
自底向上设计:是一种设计程序的过程和方法,就是先编写出基础程序段,然后再逐步扩大规模、补充和升级某些功能,实际上是一种自底向上构造程序的过程
Vue从设计角度来讲,虽然能够涵盖这张图上所有的东西,但是你并不需要一上手就把所有东西全用上,都是可选的。
声明式渲染和组件系统是Vue的核心库所包含内容,而路由、状态管理、构建工具都有专门解决方案。这些解决方案相互独立,我们可以在核心的基础上任意选用其他的部件,不一定要全部整合在一起。
初始化项目 npm init -y
可以使用npm i -S vue
或 cnpm i -S vue
下载vue的资源包,将dist/vue.js以及 v e.min.js 移动到lib目录下
Vue.js的核心是一个允许采用简洁的模板语法来声明式的将数据渲染进DOM的系统,例如:
{{ message }}
组件系统是Vue的另一个重要概念(后续学习),因为它是一种抽象的允许我们使用小型、独立和通常可复用的“小积木”构建大型应用。几乎任意类型的应用界面都可以抽象为一个组件树。
注意:开发模式≠设计模式
开发模式,就是一个开发项目的方式或者标准。
比较常见的三种开发模式:MVC、MVP、MVVM
MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑(C)、数据(M)、界面显示(V)分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。
用户可以向 View 发送指令(DOM 事件),再由 View 直接要求 Model 改变状态。
用户也可以直接向 Controller 发送指令(改变 URL 触发 hashChange 事件),再由Controller 发送给 View。
Controller 非常薄,只起到路由的作用,而 View 非常厚,业务逻辑都部署在 View。
优点 ----- 高内聚 低耦合
耦合性低,重用性高,部署快,可维护性高,有利于软件工程化管理
缺点
由于模型model和视图view要严格的分离,给前端程序带来了很大的困难,每次操作需要彻底的测试
MVP是Model-View-Presenter简称,MVP是从经典的模式MVC演变而来,它们的基本思想有相通的地方Controller/Presenter负责逻辑的处理,Model提供数据,View负责显示:
各部分之间的通信,都是双向的。
View 与 Model 不发生联系,都通过 Presenter 传递。可以将一个Presenter用于多个视图,而不需要改变Presenter的逻辑。这个特性非常的有用,因为视图的变化总是比模型的变化频繁。
View 非常薄,不部署任何业务逻辑,称为"被动视图"(Passive View),即没有任何主动性,而 Presenter非常厚,所有逻辑都部署在那里。
缺点
由于对视图的渲染放在了Presenter中,所以视图和Presenter的交互会过于频繁。一旦视图需要变更,那么Presenter也需要变了。
MVVM 模式将 Presenter 改名为 ViewModel,基本上与 MVP 模式完全一致。唯一的区别是,它采用双向绑定(data-binding):View的变动,自动反映在 ViewModel,反之亦然。这种模式下,页面输入改变数据,数据改变影响页面数据展示与渲染
vue使用MVVM响应式编程模型,避免直接操作DOM , 降低DOM操作的复杂性。
优点
低耦合。视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的View上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。
可重用性。你可以把一些视图逻辑放在一个ViewModel里面,让很多view重用这段视图逻辑。
可测试。界面素来是比较难于测试的,而现在测试可以针对ViewModel来写。
假设需要输出 “hello bk2007”
传统开发模式的原生js,jQuery代码如下:
<div id="app">div>
<script>
var msg = 'hello every';
var oApp = document.getElementById('app');
oApp.innerHTML = msg;
script>
<script src="../js/jquery.js">script>
<script>
var msg = 'hello every';
$('#app').html(msg);
script>
步骤
定义用于填充数据的标签
引入vue.js库文件( 学习测试使用开发版本,项目上线换成生产版本 )
使用vue语法实现需求
将vue提供的数据填充到“第1步”中的标签里
代码如下
<body>
<div id="app">{{msg}}div>
body>
<script src="../js/vue.js">script>
<script type="text/javascript">
// 3. 使用vue语法实现需求
var vue = new Vue({
// 4. 将vue提供的数据填充到“第1步”中的标签里
el: "#app",
data: {
msg: "hello bk2007"
}
});
script>
Vue实例细节分析:
Vue参数对象属性
el:元素挂载的位置,值可以是CSS选择器或DOM元素
data:模型数据,值是一个对象
插值表达式 {{msg}}
了解:前端渲染方式
前端渲染方式有:
原生JavaScript拼接字符串(维护困难) - 吃了符号的亏,上了大小写的当
使用前端模板引擎(维护较容易,但缺乏事件支持) - ejs / EasyTemplate
使用Vue特有的模板语法
通过chrome中的谷歌插件商店安装Vue Devtools工具,此工具帮助我们进行vue数据调试所用,一定要安装。
Git仓库地址:https://github.com/vuejs/vue-devtools
安装依赖包 cnpm i
构建 cnpm run build
打开Chrome扩展页面
开启开发者模式
加载已解压扩展,选择shells-chrome目录
将产生的 .crx 文件拖入谷歌浏览器 扩展程序 界面
在Windows策略管理器中添加Google策略模板文件,将插件对应的ID添加到 配置扩展程序白名 单
在谷歌浏览器 扩展程序 管理界面中给 Vue.js devtools 插件授权
可以使用 老师给的已经解压好的 chrome 包安装 vue插件
核心:数据劫持 + 发布与订阅
在属性被访问和修改时通知变化。每个组件实例都有相应的 watcher 实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher 重新计算,从而致使它关联的组件得以更新。这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更。这里需要注意的是不同浏览器在控制台打印数据对象时对 getter/setter 的格式化并不同,所以建议安装 vue-devtools 来获取对检查数据更加友好的用户界面。
每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。
数据的改变会引起视图的二次渲染
var obj = { a: 1 }
// obj a. setter getter
console.log(obj.a) // getter
obj.a = 2 // setter
Object.defineProperty(obj, prop, descriptor)
obj
要定义属性的对象。
prop
要定义或修改的属性的名称 。
descriptor
要定义选项。
<body>
<div id="app">
<div id="msg">div>
<input type="text" id="ipt" oninput="changeVal(this)">
div>
body>
<script>
// 参照值 - // 1. 定义对象
var userInfo = { username: 'laura' }
// 需要劫持的对象 - 2. 数据劫持
var obj = {}
// 劫持了 obj对象的 name属性,不管你有没有
Object.defineProperty(obj, 'name', {
get () {
return userInfo.username
},
set (val) {
userInfo.username = val
document.getElementById('msg').innerHTML = obj.name
}
})
// 3. 实时渲染 只作用了第一次,如果不数据劫持,显示的是 undefined
document.getElementById('msg').innerHTML = obj.name
// 4. 发布订阅
function changeVal (element) {
// console.log(element.value)
console.log(document.getElementById('ipt').value)
obj.name = element.value
}
script>
vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的,每个组件实例都有相应的 watcher 实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher 重新计算,从而致使它关联的组件得以更新。
双向数据绑定需要 实现
1.实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。
2.实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。
3.实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。
当把一个普通的JavaScript对象传给Vue实例的data选项,Vue将遍历此对象所有的属性,使用Object.defineProperty把这些属性全部转为getter/setter(数据劫持/数据映射)。
Vue.js 使用了基于 HTML 的模板语法,允许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据。所有 Vue.js 的模板都是合法的 HTML,所以能被遵循规范的浏览器和 HTML 解析器解析。
在底层的实现上,Vue 将模板编译成虚拟 DOM 渲染函数。结合响应系统,Vue 能够智能地计算出最少需要重新渲染多少组件,并把 DOM 操作次数减到最少。
<body>
<div id="app">
1.文本插值--用双大括号显示变量的值<br />
{{message}}<br />
2.显示文字<br />
<div v-text="stu">div>
3.可以解析原始的HTML v-html<br />
<div v-html="myhtml">div>
4.使用v-bind 绑定属性<br />
<div v-bind:id="myid" v-bind:class="myclass">电影列表div>
<button v-bind:disabled="myDis">{{myBtnText}}button>
<img v-bind:src="myImg" />
div>
<script src="../js/vue.js">script>
<script type="text/javascript">
var vm = new Vue({
el: "#app",
data: {
message: "hello",
stu: "熊大",
myhtml: "我来测试一下标签是否被解析
",
myid: 'filmId',
myclass: "red",
myDis: true,
myBtnText: '10',
myImg: "../ziliao/meinv.jpg"
}
})
script>
body>
注意:v-html容易引起xss攻击,尽量少用或不用。
html:''
无可避免时使用,需要进行以下配置,了解,面试或以后项目可能用到!
可以将标签替换成特殊编码:obj[key] = obj[key].replace(/
2.使用DOMPurify使v-html绕过xss攻击.
npm install dompurify
下载完之后在main.js中,将DOMPurify挂在到vue上面,在需要的页面在引入
import VueDOMPurifyHTML from 'vue-dompurify-html'
Vue.use(VueDOMPurifyHTML)
然后在需要使用v-html的页面,将v-html改为 v-dompurify-html即可解决xss攻击问题.
3.最后我在网上找到一个更好的办法通过引入组件xss,在编译前将从vue-loader传入的compilerOptions.directives和baseOptions.directives进行了合并。 这样我们就能覆盖html指令。
npm install xss
同样在main.js页面引入
import xss from 'xss';
Vue.prototype.xss = xss
在vue.config.js中覆写html指令
chainWebpack: config => {
config.module
.rule("vue")
.use("vue-loader")
.loader("vue-loader")
.tap(options => {
options.compilerOptions.directives = {
html(node, directiveMeta) {
(node.props || (node.props = [])).push({
name: "innerHTML",
value: `xss(_s(${directiveMeta.value}))`
});
}
};
return options;
});
}
这样的话,这个项目不论之后如何迭代都可以从根本上面解决xss攻击的问题
指令 (Directives) 是带有 v-
前缀的特殊属性 attribute。指令 attribute 的值预期是单个 JavaScript 表达式。指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。
4.0 v-text v-bind v-html ***
这个指令保持在元素上直到关联实例结束编译。
和 CSS 规则如 [v-cloak] { display: none }
一起用时,这个指令可以隐藏未编译的 Mustache 标签直到实例准备完毕。
如果网络不好的情况下,网页先显示 {{ message }},如果vue加载完毕,显示 hello vue
闪烁
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
<style>
[v-cloak] { /* 如果未加载完vue,{{ message }} 不显示 */
display: none;
}
style>
head>
<body>
<div id="app" v-cloak>
{{ message }}
div>
body>
<script src="../js/vue.js">script>
<script>
const app = new Vue({
el: '#app',
data: {
message: 'hello vue'
}
})
script>
你可以用 v-model
指令在表单 、
及
元素上创建双向数据绑定。
它会根据控件类型自动选取正确的方法来更新元素。
尽管有些神奇,但 v-model
本质上不过是语法糖。
它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理
v-model
会忽略所有表单元素的value
、checked
、selected
attribute 的初始值而总是将 Vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的
data
选项中声明初始值。
v-model
在内部为不同的输入元素使用不同的 property 并抛出不同的事件:
value
property 和 input
事件;checked
property 和 change
事件;value
作为 prop 并将 change
作为事件。<body>
<div id="app">
<div>
用户名:
<input type="text" v-model="username"> {{ username }}
div>
<div>
密码:
<input type="password" v-model="password"> {{ password }}
div>
<div>
性别:
<input type="radio" name="sex" value="1" v-model="sex"> 男
<input type="radio" name="sex" value="0" v-model="sex"> 女 --- {{ sex }}
div>
<div>
爱好:
<input type="checkbox" name="hobby" value="篮球" v-model="hobby"> 篮球
<input type="checkbox" name="hobby" value="排球" v-model="hobby"> 排球
<input type="checkbox" name="hobby" value="网球" v-model="hobby"> 网球 --- {{ hobby }}
div>
<div>
阶段:
<select v-model="lesson">
<option disabled value="">请选择option>
<option value="1">1阶段option>
<option value="2">2阶段option>
<option value="3">3阶段option>
select> - {{ lesson }}
div>
<div>
来自哪个城市:
<select v-model="city">
<option v-for="(item,index) in cityList" :value="item.value">{{item.title}}option>
select>
{{city}}
div>
<input type="checkbox" v-model="flag"> 已阅读****协议 --- {{ flag }}
<div>
<button v-on:click="register">注册button>
div>
div>
<script src="../js/vue.js" type="text/javascript" charset="utf-8">script>
<script type="text/javascript">
var vm = new Vue({
el: "#app",
data: {
username: 'laura',
password: '123456',
sex: "1",
hobby: ["排球"],
lesson: '',
city: '',
cityList: [
{ "title": "广州", "value": "广州" },
{ "title": "深圳", "value": "深圳" },
{ "title": "上海", "value": "上海" }
],
flag: false
},
methods: {
register: function () {
console.log({
username: this.username,
password: this.password,
sex: this.sex === '1' ? '男' : '女',
hobby: this.hobby,
lesson: this.lesson == '3' ? '三阶段' : this.lesson == '2' ? '二阶段' : this.lesson == '1' ? '一阶段' : '未选择阶段',
city: this.city
})
}
}
})
script>
注意:
1.checkbox,如果你的数据类型是数组类型,代表的就是 多选框(爱好,兴趣);如果数据类型是boolean类型,true代表的是选中的状态,false代表的是未选中(注册时已阅读 ****协议,购物车的选中状态)
2.select下拉选择框:如果
v-model
表达式的初始值未能匹配任何选项,元素将被渲染为“未选中”状态。
在 iOS 中,这会使用户无法选择第一个选项。
因为这样的情况下,iOS 不会触发 change 事件。因此,更推荐提供一个值为空的禁用选项。
<div> 阶段: <select v-model="lesson"> <option disabled value="">请选择option> <option value="1">1阶段option> <option value="2">2阶段option> <option value="3">3阶段option> select> - {{ lesson }} div>
只渲染元素和组件一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。
这可以用于优化更新性能。
<body>
<div id="app">
<input type="text" v-model="username"> {{ username }}
<div v-once>
{{ username }}
div>
div>
body>
<script src="../js/vue.js">script>
<script>
const app = new Vue({
el: '#app',
data: {
username: '1111'
}
})
script>
动态地绑定一个或多个 attribute,或一个组件 prop 到表达式。
在绑定 class
或 style
attribute 时,支持其它类型的值,如数组或对象。可以通过下面的教程链接查看详情。
在绑定 prop 时,prop 必须在子组件中声明。可以用修饰符指定不同的绑定类型。
没有参数时,可以绑定到一个包含键值对的对象。注意此时 class
和 style
绑定不支持数组和对象。
vue中当遇到变量,boolean类型或者number类型时,需要使用绑定属性
<body>
<div id="app">
<img v-bind:src="imgsrc" alt="">
<div v-bind:num="10">div>
<div v-bind:flag="true">div>
<div :flag="true">div>
div>
body>
<script src="../js/vue.js">script>
<script>
const app = new Vue({
el: '#app',
data: {
imgsrc: 'https://cn.vuejs.org/images/logo.png'
}
})
script>
**作用:**绑定事件监听器(事件绑定)
<!-- 常规写法 -->
<button v-on:click="num++"></button>
<!-- 缩写 -->
<button @click="num++"></button>
<!-- 事件处理函数调用:直接写函数名 -->
<button @click="say"></button>
<!-- 事件处理函数调用:常规调用,可以传递参数 -->
<button @click="alert('123')"></button>
如果事件处理函数为自定义函数,则需要先进行定义,定义的方式如下:
...
data: {
...
},
methods: {
functionName: function(arg1,arg2,arg3,...) {
// something to do
},
....
}
注意:事件绑定 v-on 属性表达式中切记不能直接写业务逻辑,例如 @click=“alert(‘123’)”
事件处理函数传参
<button @click="say">button>
<button @click="say('hi',$event)">button>
在不传递自定义参数的时候,上述两种用法均可以使用;但是如果需要传递自定义参数的话,则需要使用第2种方式。
事件对象的传递与接收注意点
如果事件直接使用函数名并且不写小括号,那么默认会将事件对象作为唯一参数进行传递
如果使用常规的自定义函数调用(只要写了小括号),那么如果需要使用事件对象则必须作为最后一个参数进行传递,且事件对象的名称必须是“$event”
<body>
<div id="app">
<button v-on:click="num++">+button>{{ num }}
<button @click="num--">-button>
<button @click="alert('123')">vuebutton>
<button onclick="alert('456')">jsbutton>
<button @click="say1">没有参数button>
<button @click="say2('hi', $event)">有参数和事件对象button>
<button @click="say3">查看默认的事件对象button>
div>
body>
<script src="../js/vue.js">script>
<script>
const app = new Vue({
el: '#app',
data: {
num: 100
},
methods: {
say1 () {
console.log('没有参数')
},
say2 (str, event) {
console.log('有参数和事件对象, str-' + str + ',event-' + event)
console.log(event)
},
say3 (event) {
console.log(event)
}
},
})
script>
<style>
#big {width: 300px; height: 300px; background-color: red; }
#mid {width: 200px; height: 200px; background-color: green; }
#sma {width: 100px; height: 100px; background-color: pink; }
style>
<body>
<div id="app">
<div id="big" @click="say('大娃',$event)">
<div id="mid" @click="say('二娃',$event)">
<div id="sma" @click="say('三娃',$event)">div>
div>
div>
div>
body>
<script src="../js/vue.js">script>
<script>
new Vue({
el: '#app',
data: { },
methods:{
say: function(name,event){
console.log('你点了' + name);
}
}
})
script>
含义:用来处理事件的特定行为
使用示例:
<button @click.stop="doThis">button>
<button @click.prevent="doThis">button>
<button @click.stop.prevent="doThis">button>
更多事件修饰符请参考官方文档:https://cn.vuejs.org/v2/guide/events.html#%E4%BA%8B%E4%BB%B6%E4%BF%AE%E9%A5%B0%E7%AC%A6
.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>
<div v-on:click.self="doThat">...div>
DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>事件修饰符title>
<script src="../js/vue.js">script>
<style>
.b0 {
width: 50px;
height: 50px;
background-color: #FFA500;
}
.b1 {
width: 100px;
height: 100px;
background-color: #ADD8E6;
}
.b2 {
width: 200px;
height: 200px;
background-color: pink;
}
style>
head>
<body>
<div id="app">
1.阻止单击事件继续传播 stop 阻止冒泡 ****
<div class="b1" @click="doThis1">
外面的div
<div class="b0" @click.stop="doThis">里面divdiv>
div>
2.prevent 提交事件不再重载页面,拦截默认事件的 ***
<form @submit.prevent="onSubmit" method="get" action="http://www.baidu.com">
<input type="text" vlaue="" name="user" />
<button type="submit">提交button>
form>
3.self 只有修饰符和点击事件绑定的元素与当前被点击的元素一致时才会触发 ****
<div class="b2" @click.self="doThis2">
3
<div class="b1" @click.self="doThis1">
2
<div class="b0" @click.self="doThis">1div>
div>
div>
4.capture 添加事件监听时,使用 捕获模式
<div class="b2" @click.capture="doThis2">
3
<div class="b1" @click.capture="doThis1">
2
<div class="b0" @click.capture="doThis">1div>
div>
div>
5.once 绑定的事件只执行一次
<button @click.once="doThis">绑定一次button>
6.passive 滚动事件的默认行为会立即触发
不会等到 onScroll完成
7.修饰符可以串联 .stop.prevent
<a v-on:click.stop.prevent="doThis">修饰符串联a>
8.只有修饰符 v-on:submit.prevent 表示只是阻止了表单提交却没有绑定任何事件
<form v-on:submit.prevent>form>
div>
<script type="text/javascript">
var vm = new Vue({
el: "#app",
data: {
},
methods: {
doThis: function () {
console.log("b1对应的函数运行了");
},
doThis1: function () {
console.log("b2对应的函数运行了");
},
doThis2: function () {
console.log("b3对应的函数运行了");
},
onSubmit: function () {
console.log("验证,并且提交的函数被触发了");
}
}
})
script>
body>
html>
事件修饰符实例:
DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>title>
<meta name="viewport" content="width=device-width" />
<style>
* {
padding: 0;
margin: 0;
}
ul,
li {
list-style: none;
}
#app {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
background: lightblue;
}
#app ul {
width: 100%;
height: 100%;
overflow: scroll;
}
#app ul li {
width: 100%;
height: 150px;
margin-top: 5px;
background: lightgoldenrodyellow;
}
style>
head>
<body>
<div id="app">
<ul @scroll.passive="scrollFun">
<li v-for="(item,index) in 10" :key="index" @click="itemFun">
<span>商品{{item}}span>
<button @click.stop="addshop(index)">加入购物车button>
li>
ul>
div>
<script src="../js/vue.js" type="text/javascript" charset="utf-8">script>
<script type="text/javascript">
var vm = new Vue({
el: "#app",
data: {
},
methods: {
//添加购物车
addshop: function (id) {
console.log("加入购物车的函数运行了", id);
},
//跳转内容页
itemFun: function () {
console.log("跳转内容页的函数执行了");
},
//执行滚动
scrollFun: function (ev) {
console.log("滚动的函数运行了");
console.log(ev);
//滚动时离上边的距离
console.log(ev.target.scrollTop);
}
}
})
script>
body>
html>
在监听键盘事件时,我们经常需要检查详细的按键。Vue 允许为 v-on 在监听键盘事件时添加按键修饰符。
<input v-on:keyup.enter="submit">
<input v-on:keyup.delete="handle">
更多按键修饰符请参考官方文档:
https://cn.vuejs.org/v2/guide/events.html#%E6%8C%89%E9%94%AE%E4%BF%AE%E9%A5%B0%E7%AC%A6
.enter
.tab
.delete (捕获“删除”和“退格”键)
.esc
.space
.up
.down
.left
.right
<div id="app">
输入用户名:
<input type="text" @keyup.enter="changeValue" value="" />
<input type="text" @keyup.13="changeValue" value="" />
{{username}}
<div v-on:click.ctrl="doSomething">Do somethingdiv>
div>
<script src="../js/vue.js">script>
<script type="text/javascript">
var vm = new Vue({
el: "#app",
data: {
username: "lili"
},
methods: {
changeValue: function (ev) {
console.log(ev);
this.username = ev.target.value;
},
doSomething: function () {
console.log("doSomething运行了!");
}
}
})
script>
需要配合 系统修饰键 完成
https://cn.vuejs.org/v2/guide/events.html#%E7%B3%BB%E7%BB%9F%E4%BF%AE%E9%A5%B0%E9%94%AE
按键修饰符还有一个额外的细节需要注意,Vue内置的按键修饰符是有限的,如果还需要用到其他键盘按键的修饰符,则此时我们可以通过全局对象config.keyCodes自行定义按键修饰符,例如:
Vue.config.keyCodes.KEYNAME = 112
当然,在实际使用的时候也允许我们不去定义修饰符而直接去使用按键对应的数字(按键码),例如:
<input v-on:keyup.13="submit">
但是这种方式直接记忆数字与按键的关系,不是很方便。已经被官方废弃了,并且可能在最新的浏览器中不被支持。参考地址:https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode
<body>
<div id="app">
<input type="text" @keyup.alt.67="test">
div>
body>
<script src="../js/vue.js">script>
<script>
const app = new Vue({
el: '#app',
data: {},
methods: {
test () {
console.log('啦啦啦')
}
}
})
script>
**作用:**根据一组数组或对象的选项列表进行渲染。
**指令:**v-for
<ul>
<li v-for='item in fruits'>{{item}}li>
<li v-for='(item,index) in fruits'>{{item}}{{index}}li>
ul>
......
data: {
fruits: ['apple','pear','banana','orange']
}
......
细节:key的作用,提高性能,不影响显示效果( 如果没有id,可以考虑使用索引替代 )
<ul>
<li :key='item.id' v-for='(item,index) in fruits'>{{item}}li>
ul>
<ul>
<li v-for='(value,key,index) in obj'>
{{value + '-' + key + '-' + index}}
li>
ul>
......
data: {
obj: {
username: 'zhangsan',
age: 28,
gender: 'male'
}
}
......
示例代码:
<body>
<div id="app">
<ul>
<li v-for="item in list">{{item.id}} -- {{item.name}}li>
ul>
<ul>
<li v-for="(item,index) in list">{{index}} -- {{item.name}}li>
ul>
<ul>
<li v-for="(item,index) of list">{{index}} -- {{item.name}}li>
ul>
<ul>
<li v-for="(item,key,index) in myObj">{{index}}-{{key}}-{{item}}li>
ul>
<ul>
<li v-for="(item,index) in list" :key="item.id">{{index}} -- {{item.name}}li>
ul>
div>
<script type="text/javascript" src="../js/vue.js">script>
<script type="text/javascript">
var vm = new Vue({
el: "#app",
data: {
list: [
{ "id": 1, "name": "js" },
{ "id": 2, "name": "css" },
{ "id": 3, "name": "html" },
{ "id": 4, "name": "vue" },
],
myObj: {
"name": "大熊",
"donghua": "叮当猫",
"bieming": "哆啦A梦"
}
}
})
script>
body>
课堂练习,学生信息表,功能:显示学生信息,学生信息表按年龄排序(选做),学生信息表添加(选做)
循环中数组的变更方法和非变更方法:
<div id="app">
<ul>
<li v-for="item in arr">{{item}}li>
ul>
<button @click="addFun">添加button>
<button @click="sFun">截取button>
div>
<script src="../js/vue.js">script>
<script type="text/javascript">
var vm = new Vue({
el: "#app",
data: {
arr: ["html", "css", "js"]
},
methods: {
addFun: function () {
//this.arr[3] = "new";
//我们给arr数组添加了新的元素,但是页面没有同步更新
//Vue 将被侦听的数组的变异方法进行了包裹,所以它们也将会触发视图更新。
//这些被包裹过的方法包括:push pop shift unshift splice sort reverse
this.arr.push("new");//在使用这些方法时,默认会触发页面更新
//以上问题还可以使用 $set来解决,$set 可以强制性去render页面
},
sFun: function () {
//也有非变更 (non-mutating method) 方法,
//例如 filter()、concat() 和 slice(),它们不会改变原始数组,而总是返回一个新数组。
//本身不会触发页面更新
this.arr = this.arr.slice(0, 2);
console.log(this.arr);
}
}
})
script>
数组更新检测:
变更方法:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
非变更方法:例如 filter()、concat() 和 slice()
由于js的一些限制,vue2.0不能检测数组和对象的一些变化,需要特殊处理。
例如:vue不能检测以下情况数组的变动:
1.对数组的某个下标直接赋值 arr[3] = “scss”
2.对数组更改它的长度 arr.length =2
vue不能检测对象中的具体值得变化:
以上情况可以使用 $set来解决,
<div id="app">
<ul>
<li v-for="i in neirong">{{i}}li>
ul>
<button @click="addFun">添加button>
div>
<script src="../js/vue.js" type="text/javascript" charset="utf-8">script>
<script type="text/javascript">
/*
vue不能检测一下情况数组的变动:
1.对数组的某个下标直接赋值 neirong[3] = "scss"
2.对数组更改它的长度 neirong.length =2
*/
var vm = new Vue({
el: "#app",
data: {
neirong: ["js", "css", "html"]
},
methods: {
addFun: function () {
//this.neirong[3] = "scss";//不是响应性的
//使用 $set 去添加或修改数组的值时,会触发 dom重新 render 渲染
this.$set(this.neirong, 3, "scss");
}
}
})
script>
<body>
<div id="app">
<div v-for="(item,key) in stu">{{key}}:{{item}}div>
<button @click="add">添加属性button>
div>
<script src="../js/vue.js">script>
<script type="text/javascript">
var vm = new Vue({
el: "#app",
data: {
stu: {
"id": 1,
"name": "大娃"
}
},
methods: {
add: function () {
//this.stu["age"] = 18;
this.stu["name"] = "二娃";
//页面没发生任何变化,证明Vue不能检测对象属性的添加或删除
//注意:vue对对象原有属性的值进行修改是可以检测到的
//使用$set来完成添加操作
this.$set(this.stu, "age", 18);
console.log(this.stu);
}
}
})
script>
body>
<body>
<div id="app">
<ul>
<li v-for="item in userProfile">{{item}}li>
ul>
<button @click="add">一次添加两个以上属性button>
div>
<script src="../js/vue.js">script>
<script type="text/javascript">
var vm = new Vue({
el: "#app",
data: {
userProfile: {
name: 'Anika'
}
},
methods: {
add: function () {
//需要使用 对象的合并
Object.assign(this.userProfile, {
age: 27,
favoriteColor: 'Vue Green'
})
//以上代码 不ok
// this.userProfile = Object.assign({},this.userProfile,
// {age: 27,favoriteColor: 'Vue Green'});
}
}
})
script>
body>
注:如果你已经做到了上述的事项仍然发现在极少数的情况下需要手动强制更新,那么你可以通过 $forceUpdate
来做这件事!
**作用:**根据表达式的布尔值(true/false)进行判断是否渲染该元素
v-if
v-else
v-else-if
上述三个指令是分支中最常见的。根据需求,v-if可以单独使用,也可以配合v-else一起使用,也可以配合v-else-if和v-else一起使用。
v-show是根据表达式之真假值,切换元素的 display CSS属性。
使用示例:
<div v-if="score >= 90"> 优秀 div>
<div v-else-if="score >= 80 && score < 90"> 良好 div>
<div v-else-if="score >= 70 && score < 80"> 一般 div>
<div v-else> 不及格 div>
<div v-show='flag'>测试v-showdiv>
...... data: {
score: 88,
flag:false
}
......
思考:v-if系列与v-show的区别是什么?
v-if:控制元素是否渲染
v-show:控制元素是否显示(已经渲染,display:none;)
v-if系列指令、v-show指令可以与v-for指令结合起来使用(循环+分支)。例如:
<body>
<div id="app">
<input type="text" v-model="grade">
<div v-if="grade >= 90">优div>
<div v-else-if="grade >= 80">良div>
<div v-else-if="grade >= 70">中div>
<div v-else-if="grade >= 60">差div>
<div v-else>不及格div>
<hr>
<div v-show="grade >= 90">优div>
<div v-show="grade >= 80 && grade < 90">良div>
<div v-show="grade >= 70 && grade < 80">中div>
<div v-show="grade >= 60 && grade < 70">差div>
<div v-show="grade < 60">不及格div>
div>
body>
<script src="../js/vue.js">script>
<script>
const app = new Vue({
el: '#app',
data: {
grade: 98
}
})
script>
通过审查元素观察 v-if 以及 v-show的区别
细节:
展示基本的商品信息
计算每个商品的小计
商品数量的加、减操作
+:增加商品数量,同时更新小计
-:减少商品数量,同时更新小计,如果本身为“1”,-不可以再次点击
如果需要在Vue实例中访问自身data属性中的数据,可以使用以下方式:
this.xxxxx
this.$data.xxxxx
this._data.xxxxx
参考数据源
var cartData = [ {
id: 1,
name: '小米',
price: 100,
num: 1
}, {
id: 2,
name: '华为',
price: 200,
num: 1
},{
id: 3,
name: '联想',
price: 300,
num: 1
} ]
参考核心代码
<body>
<div id="app">
<table border="1">
<tr>
<th>商品idth>
<th>商品名称th>
<th>商品价格th>
<th>操作th>
<th>小计th>
tr>
<tr v-for="(item, index) of cartData" :key="item.id">
<td>{{ item.id }}td>
<td>{{ item.name }}td>
<td>{{ item.price }}td>
<td>
<button :disabled="item.num<=1" @click="item.num-=1">-button>
{{ item.num }}
<button @click="item.num+=1">+button>
<button @click="deleteItem(index)">Xbutton>
td>
<td>
{{ item.num * item.price }}
td>
tr>
table>
div>
body>
<script src="../js/vue.js">script>
<script>
const cartData = [
{id:1,name:'苹果',num:1,price:100},
{id:2,name:'小米',num:1,price:200},
{id:3,name:'华为',num:1,price:300}
]
const app = new Vue({
el: '#app',
data: {
cartData
},
methods: {
deleteItem (index) {
confirm('确认删除吗') && this.cartData.splice(index, 1)
}
}
})
script>
表示tab,一个顶四个
nbsp;
对象语法( 用于控制开关切换 )
<style>
/* CSS片段 */
.active { color: red; }
style>
<div v-bind:class="{active: isActive}">class样式div>
<script type='text/javascript'>
// JavaScript片段
data: {
isActive: true
}
script>
数组写法
<style>
/* CSS片段 */
.active {
color: red;
}
style>
<div v-bind:class="[activeClass]">数组写法div>
<script type='text/javascript'>
// JavaScript片段
data: {
activeClass: 'active'
}
script>
对象写法div>
<script type='text/javascript'>
// JavaScript片段
data: {
redColor: 'red'
}
script>
<div v-bind:style="[color, fontSize]">数组写法div>
<script type='text/javascript'>
// JavaScript片段
data: {
color: {
color: 'red'
},
fontSize: {
'font-size': '20px'
}
}
script>
案例:
<head>
<meta charset="utf-8">
<title>class和styletitle>
<style type="text/css">
.myclass{width:100px; height:100px; background: blue;}
.active{color:green;}
.danger{border:1px solid red;}
.ttt{border:4px dotted orange;}
style>
head>
<body>
<script src="../js/vue.js">script>
<div id="app">
1.class 使用 v-bind绑定时可以使用对象的方式
<div :class="myClass" class="ttt">春花秋月何时了div>****
<div :class="{active:isActive,danger:isDanger}">往事知多少div>
<div :class="[myClass,activeClass]">往事知多少div>
<div :class="isActive ? 'active' :'danger'">****class的三目运算符的使用div>
<div :class="[isActive ? 'active' :'danger', myClass]">class的三目运算符的使用div>
2.style 使用 v-bind绑定时可以使用对象的方式
<div :style="{color:'pink',background:'blue'}">小楼昨夜又东风div>
<div :style="{color:pinkColor,background:blueColor}">小楼昨夜又东风div>
<div :style="myStyle">故国不堪回首月明中div>
<div :style="[color, fontSize]">数组写法div>
div>
<script type="text/javascript">
var vm = new Vue({
el:"#app",
data:{
myClass:"myclass",
isActive:true,
isDanger:false,
pinkColor:'pink',
blueColor:'blue',
activeClass:"active",
myStyle:{color:'pink',background:'blue'},
color: {color: 'red'},
fontSize: {'font-size': '20px'}
}
})
script>
body>
修饰符
.lazy:默认情况下Vue的数据同步采用 input 事件,使用 .lazy 将其修改为失去焦点时触发(change)
.number:自动将用户的输入值转为数值类型(如果能转的话)
.trim:自动过滤用户输入的首尾空白字符
<body>
<div id="app">
<input type="text" v-model.lazy="username"> {{ username }}
<input type="text" v-model.number="num"> {{ num }}
<button @click="getType">获取num的数据类型button>
<input type="text" v-model.trim="note" />
<button @click="getValue">获取note的值button>
[{{note}}]
div>
body>
<script src="../js/vue.js">script>
<script>
new Vue({
el: '#app',
data: {
username: '',
num: '100',
note: ''
},
methods: {
getType() {
console.log(typeof this.num)
},
getValue: function () {
console.log(this.note);
}
}
})
script>
分析给 全选 按钮绑定什么样的事件
click
change
全选 复选框与商品列表前面的复选框不是一回事
两者v-mode的值肯定是不一样的
全选 按钮的v-mode值应该是一个bool值
商品列表前面的复选框v-model值应该是一个数组
参考代码
<body>
<div id="app">
<input type="checkbox" value="a" v-model="arr">a
<input type="checkbox" value="b" v-model="arr">b
<input type="checkbox" value="c" v-model="arr">c
<input type="checkbox" value="d" v-model="arr">d
<div>
{{ arr }}
div>
div>
body>
<script src="../js/vue.js">script>
<script>
new Vue({
el: '#app',
data: {
arr: ['c']
}
})
script>
<body>
<div id="app">
{{ checkarr }}
<table border="1">
<tr>
<th>
<input type="checkbox" v-model="checked" @change="checkAll">
th>
<th>商品idth>
<th>商品名称th>
<th>商品价格th>
<th>操作th>
<th>小计th>
tr>
<tr v-for="(item, index) of cartData" :key="item.id">
<td>
<input type="checkbox" :value="item.id" v-model="checkarr">
td>
<td>{{ item.id }}td>
<td>{{ item.name }}td>
<td>{{ item.price }}td>
<td>
<button :disabled="item.num<=1" @click="item.num-=1">-button>
{{ item.num }}
<button @click="item.num+=1">+button>
<button @click="deleteItem(index)">Xbutton>
td>
<td>
{{ item.num * item.price }}
td>
tr>
table>
div>
body>
<script src="../js/vue.js">script>
<script>
const cartData = [
{id:1,name:'苹果',num:1,price:100},
{id:2,name:'小米',num:1,price:200},
{id:3,name:'华为',num:1,price:300}
]
const app = new Vue({
el: '#app',
data: {
cartData,
checked: false,
checkarr: []
},
methods: {
deleteItem (index) {
confirm('确认删除吗') && this.cartData.splice(index, 1)
},
checkAll () {
if (this.checked) {
this.cartData.forEach((item, index) => {
this.checkarr.push(item.id)
})
} else {
this.checkarr = []
}
}
}
})
script>
html>
除了核心功能默认内置的指令,Vue也允许注册自定义指令。有的情况下,对普通 DOM 元素进行底层操作,这时候就会用到自定义指令绑定到元素上执行相关操作。
自定义指令分为: 全局 指令和 局部 指令,当全局指令和局部指令同名时以局部指令为准。
自定义指令常用钩子函数有:
bind:在指令第一次绑定到元素时调用
inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)
update:数据更新时调用
请注意:不管在定义全局还是局部自定义指令时,所提及的指令名均是不带 v- 前缀的名称
全局自定义指令定义
// 无参(v-once/v-cloak)
Vue.directive('指令名',{
钩子函数名: function(el[,....]){
// 业务逻辑
// el参数是挂载到元素的DOM对象
}
}
// 传参(v-text/v-html/v-model) v-model="username"
Vue.directive('指令名',{
钩子函数名: function(el,binding[,....]){
let param = binding.value // param 其实就时 username 的值
// 业务逻辑
},
....
}
全局自定义指令(后续的知识点也是的)不能写在Vue实例中(或者某个组件中)
全局自定义指令的实例:
<body>
<div id="app">
注意:需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令
指令v-if v-else v-for v-bind v-show v-html v-on v-model v-once
使用自定义指令模拟自动获取文本框焦点的功能
<input type="text" v-basefocus />
div>
<script src="../js/vue.js" type="text/javascript" charset="utf-8">script>
<script type="text/javascript">
/*定义全局的自定义指令*/
Vue.directive('basefocus', {
inserted(el) {
console.log(el);
el.focus();
el.style.border = "1px solid green";
}
})
/********************************************/
var vm = new Vue({
el: "#app",
data: {}
})
script>
body>
局部自定义指令定义
可以在 new Vue 的时候添加 directives 以注册局部自定义指令,局部自定义指令只能在当前组件中使用:
directives: {
指令名: {
// 指令的定义
钩子函数名: function (el,binding) {
// 业务逻辑
}
}
}
实例:
<div id="app">
注意:需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令
指令v-if v-else v-for v-bind v-show v-html v-on v-model v-once
使用自定义指令模拟自动获取文本框焦点的功能
<input type="text" v-basefocus />
div>
<script src="../js/vue.js" type="text/javascript" charset="utf-8">script>
<script type="text/javascript">
var vm = new Vue({
el: "#app",
data: {},
directives: {
//局部的自定义指令
'basefocus': {
inserted(el) {
console.log(el);
el.focus();
el.style.border = "1px solid green";
}
}
}
})
script>
函数简写(重点)
在很多时候,我们可能想在 bind 和 update 时触发相同行为(如果只是其一,则还是单独分开声明),而不关心其它的钩子。那么这样写:
// 全局
Vue.directive('指令名', function (el,binding) {
// 业务逻辑
})
// 局部
directives: {
指令名: function (el,binding) {
// 业务逻辑
}
}
在自定义指令的方法中,不能像以前的 methods 中的方法一样使用关键词 this ,此时 this关键词指向的是 Window 对象。
案例:搜索的内容匹配后标红加粗
需要使用 v-model
v-searchword这个自定义指令需要传递参数
DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>title>
<style type="text/css">
#con{width: 500px; height: 300px; border:2px dotted skyblue; padding: 10px;}
style>
head>
<body>
<div id="app">
<h2>文章内容:h2>
<input type="text" v-model="searchValue"/>
<div id="con" v-searchword="{searchValue:searchValue,message:message}">{{message}}div>
div>
<script src="../js/vue.js" type="text/javascript" charset="utf-8">script>
<script type="text/javascript">
//需求:查找到某个字符或语句,将文章中找到的地方标红
/****************全局自定义指令*************************************/
Vue.directive("searchword",{
inserted(el){
console.log("自定义指令的inserted钩子触发了");
},
//组件更新后自动触发的钩子函数
componentUpdated(el,binding){
console.log(el);
console.log(binding);
var searchValue = binding.value.searchValue;//输入的想要查询的字符串
var dataSource = binding.value.message;//原字符串
if(dataSource.indexOf(searchValue) == -1){
el.innerHTML = dataSource;//没找到该字符
}
//判断是否有输入数据
if(searchValue.length){//有输入搜索数据
//字符串分割
var arr = dataSource.split(searchValue);
console.log(arr);
//将查询的字母变红
var sp = `${searchValue}`;
//将分割的数组再进行连接
el.innerHTML = arr.join(sp);
}else{
el.innerHTML = dataSource;
}
}
})
/******************vue实例************************/
var vm = new Vue({
el:'#app',
data:{
message:`北京市人民政府新闻办公室今日(3月2日)召开第三十八场疫情防控新闻发布会。摄 新京报记者 李凯祥
肖飒表示北京市纪委监委已于2月26日成立调查组,工作重点围绕黄某某如何进京、如何进社区开展调查,核查疫情防控工作全过程是否存在漏洞。
针对黄某某如何进京,肖飒表示,大广高速求贤检查站辅警在黄某某一行人进京时,对其进行测温检查,测温显示正常,工作人员并未按要求对进京人员进行再次身份审核。
针对黄某某如何进入社区,肖飒表示,新怡家园小区地下车库入口为自动抬杆,只要录入系统的车辆,无需人员测温,车辆即可进入。就此,小区地下车库的不规范测温行为,导致黄某某一行人开车进入车库。`,
searchValue:''//将要查询的字符串
}
})
script>
body>
html>
模板中放入太多的逻辑会让模板过重且难以维护,使用计算属性可以让模板变得简洁易于维护。计算属性是基于它们的响应式依赖进行缓存的,计算属性比较适合对多个变量或者对象进行处理后返回一个结果值,也就是数多个变量中的某一个值发生了变化则我们监控的这个值也就会发生变化。
计算属性定义在Vue对象中,通过关键词 computed 属性对象中定义一个个函数,并返回一个值,使用计算属性时和 data 中的数据使用方式一致。
任何复杂的业务逻辑,我们都应当使用计算属性 - 计算属性具有依赖性,只有依赖的值发生改变,才会重新计算
示例
<div id="app">
原字符串:{{message}}<br />
反转字符串:{{message.split("").reverse().join("")}}
模板中放入太多的逻辑会让模板过重且难以维护,使用计算属性可以让模板变得简洁易于维护
{{reverseMessage}}
div>
<script src="../js/vue.js" type="text/javascript" charset="utf-8">script>
<script type="text/javascript">
var vm = new Vue({
el: "#app",
data: {
message: "hello world"
},
computed: {
reverseMessage: function () {
console.log("计算属性调用了");
return this.message.split("").reverse().join("");
}
}
})
script>
计算属性和普通函数的区别:
<div id="app">
<div>{{ cfn }}div>
<div>{{ cfn }}div>
<div>{{ fn() }}div>
<div>{{ fn() }}div>
div>
<script src="../js/vue.js">script>
<script type="text/javascript">
const vm = new Vue({
el: "#app",
data: {
num: 10
},
// 方法
methods: {
fn() {
console.log("methods");
return this.num;
}
},
// 计算属性
computed: {
cfn() {
console.log("computed");
return this.num;
}
}
})
script>
**注意:**只要依赖的数据源不发生改变,计算属性里的对应方法就只被调用1次,其它时候被调用时则使用缓存。
计算属性的getter和setter
<div id="app">
您的姓是:{{firstName}}
您的名是:{{lastName}}
您的全名是:{{fullName}}
div>
<script src="../js/vue.js">script>
<script>
var app = new Vue({
el: "#app",
data: {
firstName: 'yin',
lastName: 'laura'
},
computed: {
fullName: {
get: function () {
return this.lastName + ' ' + this.firstName;
},
set: function (newValue) {
console.log("set触发了!");
console.log(newValue);
var nameArr = newValue.split(" ");
this.lastName = nameArr[0];
this.firstName = nameArr[1];
}
}
}
//计算属性的setter,可以在修改全名的时候,同步更改姓或名
})
script>
使用watch来侦听data中数据的变化,watch中的属性一定是data 中已经存在的数据。
**使用场景:**数据变化时执行异步或开销比较大的操作。
**典型应用:**http://www.pinyinzi.cn/
参考代码:
<body>
<div id="app">
<input type="text" v-model="firstName"> +
<input type="text" v-model="lastName"> =
{{ fullName }}
div>
body>
<script src="../js/vue.js">script>
<script>
new Vue({
el: '#app',
data: {
firstName: '',
lastName: '',
fullName: ''
},
watch: {
firstName (newVal, oldVal) {
this.fullName = newVal + this.lastName
},
lastName (newVal, oldVal) {
this.fullName = this.firstName + newVal
}
}
})
script>
<body>
<div id="app">
<input type="text" v-model="firstName"> +
<input type="text" v-model="lastName"> =
{{ fullName }}
div>
body>
<script src="../js/vue.js">script>
<script>
new Vue({
el: '#app',
data: {
firstName: '',
lastName: ''
},
computed: { // 计算属性优于侦听属性
fullName () {
return this.firstName + this.lastName
}
}
})
script>
注意点:
声明监听器,使用的关键词是 watch
每个监听器的方法,可以接受2个参数,第一个参数是新的值,第二个参数是之前的值
**注意:**当需要监听一个对象的改变时,普通的watch方法无法监听到对象内部属性的改变,此时就需要deep属性对对象进行深度监听。
使用对象的数据形式改写上述案例参考代码:
<body>
<div id="app">
<input type="text" v-model="userInfo.firstName"> +
<input type="text" v-model="userInfo.lastName"> =
{{ userInfo.fullName }}
div>
body>
<script src="../js/vue.js">script>
<script>
new Vue({
el: '#app',
data: {
userInfo: {
firstName: '',
lastName: '',
fullName: ''
}
},
watch: {
// 为什么要 oldVal,路由 /home -> /detail/1,监听路由变化
// 通过对象属性侦听
// 'userInfo.firstName': function (newVal, oldVal) {
// this.userInfo.fullName = newVal + this.userInfo.lastName
// },
// 'userInfo.lastName': function (newVal, oldVal) {
// this.userInfo.fullName = this.userInfo.firstName + newVal
// }
userInfo: {
handler (val) {
this.userInfo.fullName = val.firstName + val.lastName
},
deep: true
}
}
})
script>
进一步需求:
增加自动计算总价功能,只计算被选中的商品【计算属性】
循序渐进
可以先计算全部的,再剔除未选中的商品
增加反选功能
当手动选中全部商品, 全选 复选框自动选中,但凡有一个商品的复选框没有被选中,则 全选复选框不选中【监听】
<body>
<div id="app">
{{ checkarr }}
<table border="1">
<tr>
<th>
<input type="checkbox" v-model="checked" @change="checkAll">全选
th>
<th>商品idth>
<th>商品名称th>
<th>商品价格th>
<th>操作th>
<th>小计th>
tr>
<tr v-for="(item, index) of cartData" :key="item.id">
<td>
<input type="checkbox" :value="item.id" v-model="checkarr">
td>
<td>{{ item.id }}td>
<td>{{ item.name }}td>
<td>{{ item.price }}td>
<td>
<button :disabled="item.num<=1" @click="item.num-=1">-button>
{{ item.num }}
<button @click="item.num+=1">+button>
<button @click="deleteItem(index)">Xbutton>
td>
<td>
{{ item.num * item.price }}
td>
tr>
<tr>
<td>
<button @click="antiselect">反选button>
td>
<td colspan="3">td>
<td >
{{ totalNum }}
td>
<td >
{{ totalPrice }}
td>
tr>
table>
div>
body>
<script src="lib/vue.js">script>
<script>
const cartData = [
{id:1,name:'苹果',num:1,price:100},
{id:2,name:'小米',num:1,price:200},
{id:3,name:'华为',num:1,price:300}
]
const app = new Vue({
el: '#app',
data: {
cartData,
checked: false,
checkarr: []
},
// 4. 计算属性计算总价总数量
computed: {
totalPrice () {
return this.cartData.reduce((sum, item) => {
const index = this.checkarr.indexOf(item.id)
if (index !== -1) {
sum += item.num * item.price
}
return sum
}, 0)
},
totalNum () {
// return this.cartData.reduce((sum, item) => {
// // 检测哪些数据是被选中的
// const index = this.checkarr.indexOf(item.id)
// if (index !== -1) {
// sum += item.num
// }
// return sum
// }, 0)
let sum = 0
this.cartData.forEach((item) => {
const index = this.checkarr.indexOf(item.id)
if (index !== -1) {
sum += item.num
}
})
return sum
}
},
methods: {
// 6.实现反选,找到索引值的 删除,没找到的push
antiselect () {
this.cartData.forEach((item) => {
const index = this.checkarr.indexOf(item.id)
if (index > -1) { // 代表有数据
this.checkarr.splice(index, 1)
} else {
this.checkarr.push(item.id)
}
})
},
deleteItem (index) {
// 7.如果删除完当前的元素,其余的都被选中,全选一定被选中
confirm('确认删除吗') && this.cartData.splice(index, 1)
if (this.checkarr.length === this.cartData.length) {
this.checked = true
} else {
this.checked = false
}
},
checkAll () {
if (this.checked) {
this.checkarr = [] // 2.这一步必不可少,先清空,然后push全部的数据
this.cartData.forEach((item, index) => {
this.checkarr.push(item.id)
})
} else {
this.checkarr = []
}
}
},
// 1.监听 checkarr 的数据变化 -
// 列表全部选中,全选就被选中,只要列表有一个没有被选中,全选就不被选中
watch: {
checkarr (newVal, oldVal) {
console.log(newVal, oldVal)
if (newVal.length === this.cartData.length) {
this.checked = true
} else {
this.checked = false
}
}
}
})
script>
参考代码
<body>
<div id="app">
<div>
<input type="checkbox" v-model="checked" @change="checkAll">全 选
<button @click="antiselect">反选button>
div>
<ul>
<li v-for="(item,index) in cartData" style="list-style: none;">
<input type="checkbox" :value="item.id" v-model="checkArr">
商品ID:{{item.id}}
商品名称:{{item.name}}
商品价格:{{item.price}}
商品数量:<button @click="dec(item,index)">-button> {{item.num}}<button @click="inc(item)">+button>
商品小计:{{item.price * item.num}}
li>
ul>
<div>
总价:{{total}}
div>
div>
body>
<script src="lib/vue.js">script>
<script>
var cartData = [
{id:1,name:'苹果',num:1,price:100},
{id:2,name:'小米',num:1,price:200},
{id:3,name:'华为',num:1,price:300}]
new Vue({
el: '#app',
data: {
cartData,
checked: false, // 默认不全选
checkArr: []
},
methods: {
dec: function (item,index) {
// 判断是否是1件
if(item.num == 1){
confirm('机不可失,确定不要买一件吗?') && this.cartData.splice(index,1)
}else{
item.num--
}
},
inc: function (item) {
item.num++
},
checkAll: function(){
// console.log(this.checked);
// 判断全选按钮是否被选中
if(this.checked){
// 全选
this.cartData.forEach(element => {
// 判断当前列表中没有被选中的数据
let pos = this.checkArr.indexOf(element.id)
if(pos == -1){ // 没有被选中的数据才插入
this.checkArr.push(element.id)
}
})
}else{
// 全不选
this.checkArr = []
}
},
// 反选
antiselect: function(){
this.cartData.forEach(element => {
let pos = this.checkArr.indexOf(element.id)
if(pos > -1){
// 要删除该商品
this.checkArr.splice(pos,1)
}else{
// 添加该商品
this.checkArr.push(element.id)
}
})
}
},
computed: {
// 计算总价
total: function(){
let sum = 0
this.cartData.forEach(element => {
// 确定当前循环到的商品是否被选中
let pos = this.checkArr.indexOf(element.id)
if(pos > -1){
sum += element.num * element.price
}
})
return sum
}
},
// 检查是否需要全选
watch: {
checkArr: function(){
if(this.checkArr.length == this.cartData.length){
// 全选
this.checked = true
}else{
// 不全选
this.checked = false
}
}
}
})
script>
**作用:**格式化数据,比如将字符串格式化为首字母大写、将日期格式化为指定的格式等。
过滤器可以定义成全局过滤器和局部过滤器。
过滤器的本质就是一个方法,使用过滤器实际上就相当于方法调用,仅是书写形式上的差异(使用的时候需要用“|”,其也可以被称之为 管道 或 变量/数据修饰符 )
声明语法:
// 全局过滤器
Vue.filter('过滤器名称',function(value[,arg1,arg2...]){
//过滤器业务逻辑
return ....
})
// 局部过滤器
el: '#app',
data: {},
filters: {
过滤器名称: function(value[,arg1,arg2...]){
return something
},
....
}
使用语法:
<div>{{msg | upper}}div>
<div>{{msg | upper | lower}}div>
<div v-bind:id='id | formatId'>div>
<div>{{msg | mysub(1,2)}}div>
案例:声明转字母为大写的全局过滤器和转字母为小写的局部过滤器
<div id="app">
<div>{{ msg | toUpper }}div>
<div>{{ msg | toLower() }}div>
<div>{{ sex | sexFilter }}div>
可传参过滤器:截取前n个字符
<div>{{ msg | subFilter(5) }}div>
div>
<script src="../js/vue.js">script>
<script>
// 全局大写过滤器
Vue.filter('toUpper', (val) => {
return val.toUpperCase()
})
Vue.filter('sexFilter', (val) => {
return val === 1 ? '男' : '女'
})
new Vue({
el: '#app',
data: {
msg: 'Hello World',
sex: 1
},
// 局部小写过滤器
filters: {
'toLower': (val) => {
return val.toLowerCase()
},
'subFilter': (val, num) => {
return val.substring(0, num);
}
}
})
script>
作业:时间戳转日期时间
时间戳数据:timearr:[‘1579657789851’,‘1563516693000’,‘1578539380000’]
转钱币,可加参数
在vue3.0中删除了,component API 替换了mixin,mixin因为没有任何作用域,导致其属性中的属性名一样时会冲突,最终选择vue对象中的值
component API 最大的特点:可以返回一个函数,所以有局部作用于,不会有冲突问题
混入(mixins)是一种分发Vue组件中可复用功能的非常灵活的方式。混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象(加的水)的选项将被混入该组件本身的选项(锅底)。
混入分为全局混入和局部混入。
示例:
<script src="../js/vue.js">script>
<script type="text/javascript">
// 定义一个混入对象(局部混入)
var myMixin = {
created: function () {
this.hello()
},
methods: {
hello: function () {
console.log("hello from mixin!")
}
},
};
// Vue实例
const vm = new Vue({
mixins: [myMixin]
});
script>
<script src="../js/vue.js">script>
<script type="text/javascript">
// 全局混入
Vue.mixin({
created: function () {
var myOption = this.myOption;
if (myOption) {
console.log(myOption)
}
}
});
new Vue({
data: {
myOption: "hello!"
}
})
script>
<body>
<div id="app">
<button @click="test">测试button>{{ reverseMsg }}
div>
body>
<script src="../js/vue.js">script>
<script>
// 全局混入 --- 强制加载
Vue.mixin({
methods: {
test () {
console.log(this.msg)
}
}
});
var myMixins = {
data: {
msg: '456'
},
computed: {
reverseMsg () {
return this.msg.split('').reverse().join('')
}
},
mounted () {// --- 生命周期的钩子函数
console.log('mixin')
}
}
new Vue({
el: '#app',
data: {
msg: '123'
},
mounted () {
console.log('com')
},
// 局部混入 - 按需加载
mixins: [myMixins]
})
script>
注意事项
当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”,合并策略:
data 数据对象发生冲突时以组件数据优先
同名钩子函数将合并为一个数组,都将被调用,并且混入对象的钩子将在组件自身钩子之前调用
值为对象的选项,例如 methods 、 components 和 directives ,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对
全局注册使用时需要格外小心!一旦使用全局混入,它将影响每一个之后创建的 Vue 实例
每个 Vue 实例在被创建之前都要经过一系列的初始化过程。例如需要设置数据监听、编译模板、挂载实例到 DOM,在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,目的是给予用户在一些特定的场景下添加他们自己代码的机会。
Vue生命周期的主要阶段:4个before, 4个ed,创建,挂载,更新,销毁
挂载(初始化相关属性)
beforeCreate ---- 备孕
注意点:在此时不能获取data中的数据,也就是说 this.msg 得到的是
created ---- 怀上了
beforeMount ---- 怀胎十月
mounted【页面加载完毕的时候就是此时】 ---- 生下来了
注意点:默认情况下,在组件的生命周期中只会触发一次
更新(元素或组件的变更操作)
beforeUpdate
updated
注意点:可以重复触发的
销毁(销毁相关属性)
beforeDestroy — game over前
destroyed — game over
销毁(手动)使用 this.$destroy()
关于8个生命周期涉及到的方法,可以参考Vue官网API:
https://cn.vuejs.org/v2/api/#%E9%80%89%E9%A1%B9-%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E9%92%A9%E5%AD%90
作业:点击按钮,倒计时,按钮从置灰变为可点击
什么是虚拟DOM?
什么是diff(different)算法?
差异比较算法的一种,把树形结构按照层级分解,只比较同级元素。不同层级的节点只有创建和删除操作
虚拟DOM+diff算法 的方式与 传统DOM操作 相比,有什么好处?
传统DOM操作:在一次操作中,往往会伴随多个DOM节点更新,浏览器收到第一个DOM请求后并不知道还有若干次更新操作,因此会马上执行流程,最终执行若干次。在后续找DOM坐标的时候,可能因为前期更新DOM导致了后续需要寻找的DOM坐标发生了变化。而操作DOM频繁还会出现页面卡顿,影响用户体验。
虚拟DOM+diff算法**:若一次操作中有若干次更新DOM的动作,虚拟DOM不会立即操作DOM,而是将这若干次更新的diff内容保存到本地一个JS对象中,最终将这个JS对象**一次性放到DOM树上,再进行后续操作,避免大量无谓的计算量。
浏览器对XMLHttpRequest对象的支持度不足, 创建 XMLHttpRequest 对象时需要对IE浏览器做的兼
容解决。 - ActiveXObject
回顾:XHR
readyState
0-4,0表示未初始化,4表示请求已完成
status(HTTP响应状态码)
200:OK,成功
3XX【重定向系列的状态码】
301:永久重定向
302:临时重定向
307:内部浏览器(缓存)重定向
4XX【错误系列】
400:bad request,错误请求
401:鉴权失败
403:禁止访问
404:找不到对应的资源
405:方法不被允许
5XX【服务器错误,环境问题】
500:服务器内部错误(代码、环境问题)
502:bad Getway,错误网关
使用XHR请求全国高校数据接口
接口地址
https://api.i-lynn.cn/college
只循环展示 list 信息即可
接口可以直接被跨域请求
案例效果
参考代码:
<body>
<div id="app">
<ul>
<li v-for="item of list" :key="item.area">
{{ item.area}}: {{ item.counts }}
li>
ul>
div>
body>
<script src="../js/vue.js">script>
<script>
new Vue({
el:'#app',
data: {
list: []
},
mounted () {
// 1.生成XHR对象
const xhr = new XMLHttpRequest()
const api = 'https://api.i-lynn.cn/college'
// 2.绑定回调函数
xhr.onreadystatechange = () => {
// 3. 判断是否成功
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr.responseText)
const data = JSON.parse(xhr.responseText).list
this.list = data
console.log(this.list)
}
}
// 4. 打开请求
xhr.open('GET',api)
// 5.发送请求
xhr.send()
}
})
script>
jQuery类的引入解决自己写兼容的烦恼,但现在只是使用了jQuery库中的网络请求功能,而jQuery中大量的dom的方法都是无效引入了,有点大材小用的意思。
$.ajax({
url,
type:get/post,
data,
dataType:json/text/xml/html/jsonp
success:function(res){},
error:function(){}
})
$.get(url,data,callback,dataType)
$.post(url,data,callback,dataType)
<body>
<div id="app">
<ul>
<li :key="index" v-for="(el,index) in list">
{{el.area}}:{{el.counts}}所
li>
ul>
div>
body>
<script src="../js/vue.js">script>
<script src="../js/jquery.js">script>
<script type="text/javascript">
const vm = new Vue({
el: "#app",
data: {
list: []
},
async mounted() {
const api = "https://api.i-lynn.cn/college";
let data = await $.get(api,"json");
this.list = data.list;
}
})
script>
async:关键词,用于函数声明关键词 function 之前,标记当前的函数为异步函数
await:关键词,让当前关键词的行代码执行之后等到到结果之后再去执行后续代码
由HTML5提供的内置API
更加简单的数据获取方式,功能更强大、灵活,可以看作是xhr的升级版
基于Promise实现
fetch支持很多请求方式,但默认为 GET 请求,如果需要使用其他方式可以通过第二个自选参数的 method 选项去指定
fetch(url[,some settings]).then(fn2) .then(fn3) ... .catch(fn)
// 通过url表达式来传递数据
fetch("http://xxx/?id=123")
.then(res => res.json())
.then(data => console.log(data));
// post标准提交
fetch("http://xxxx/post", {
method: "post",
body: "uname=lisi&pwd=123",
headers: { "Content-Type": "application/x-www-form-urlencoded"
}
})
.then(res => res.json())
.then(data => console.log(data));
// post提交json数据
fetch("http://localhost:3000/books", {
method: "post",
body: JSON.stringify({ uname: "lisi", pwd: "123", }),
headers: { "Content-Type": "application/json", }
})
.then(res => res.json())
.then(data => console.log(data));
注意:fetch 不会发送 cookies。除非你使用了credentials 的初始化选项 credentials: “include”
在上述代码示例中我们会看到有个 json() 方法,它是fetch的响应结果处理方法,fetch的常用响应
结果处理方法有:
text():将返回体处理成字符串类型
json():返回结果和JSON.parse(responseText)一样
使用fetch方式改写 XHR 部分案例
<body>
<div id="app">
<ul>
<li v-for="item of list" :key="item.area">
{{ item.area}}: {{ item.counts }}
li>
ul>
div>
body>
<script src="../js/vue.js">script>
<script>
new Vue({
el:'#app',
data: {
list: []
},
// mounted () {
// fetch('https://api.i-lynn.cn/college')
// .then(res => res.json()) // 把promise对象转换为 json 对象
// .then(result => { // 输出json数据的结果
// console.log(result)
// this.list = result.list
// })
// }
async mounted () {// async 代表该函数内部含有异步操作
// await 后的代码不执行完毕,后续代码不会继续往下执行
const res = await fetch('https://api.i-lynn.cn/college')
// const data = await res.json()
// this.list = data.list
const { list } = await res.json()
this.list = list
}
})
script>
文档:https://www.kancloud.cn/yunye/axios/234845
axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和node.js中。axios是vue作者推荐使用
的网络请求库,它具有以下特性:
支持浏览器和node.js
支持promise
能够拦截 请求和响应
自动转换json数据
axios的浏览器支持
在使用axios之前需要在对应的模板文件中引入axios的js库文件,随后按照以下用法使用axios:
// GET请求方式
axios.get('/get_data?id=10010')
.then(ret => console.log(ret.data))
axios.get('/get_data',{
params: {
id: 10010,
name: 'zhangsan',
age: 26 } })
.then(ret => console.log(ret.data))
//POST请求方式
axios.post('/set_data', {
firstName: 'zhang', lastName: 'san'
}).then(ret => { })
axios({
method: 'post',
url: 'set_data',
timeout: 1000,
headers: {'X-Custom-Header': 'foobar'},
data: { firstName: 'zhang', lastName: 'san' }
}).then(ret => { })
当然axios除了支持传统的 GET 和 POST 方式以外,常见的请求方式还支持:
put:修改数据
delete:删除数据
以上方的axios请求示例为例,axios响应结果( ret )的主要属性有:
data:实际响应回来的数据(最常用)**
headers:响应头信息
status:响应状态码
statusText:响应状态信息
另外需要注意的是,在使用axios发送请求之前它允许我们通过全局配置做一些设置,这样可以方便后续的请求操作,例如:
axios.defaults.timeout = 3000【设置超时时间】
axios.defaults.baseURL = ‘http://localhost/app’【设置默认地址】
axios.defaults.headers['token’] = ‘123123123’【设置请求头信息,通用头信息】
axios.defaults.headers.get[’_token’] = ‘123123’
axios.defaults.headers.post[’_token’] = ‘123123’
axios.defaults.headers.common[’_token’] = ‘123123’【通用头信息,common可以不写】
注意:
axios发送post请求的时候,默认发送json格式数据
如果需要发送post表单类型请求,则需要指定请求头
axios.post('college',{
username: 'zhangsan', age: 22
},{
headers: { "Content-Type": "application/x-www-form-urlencoded" }
}).then(ret => this.list = ret.data.list)
使用axios方式改写 XHR 部分案例
<body>
<div id="app">
<ul>
<li v-for="item of list" :key="item.area">
{{ item.area}}: {{ item.counts }}
li>
ul>
div>
body>
<script src="../js/vue.js">script>
<script src="../js/axios.js">script>
<script>
new Vue({
el:'#app',
data: {
list: []
},
// mounted() {
// axios.get('https://api.i-lynn.cn/college')
// // .then(res => console.log(res))
// .then(res => {
// this.list = res.data.list
// })
// },
async mounted() {
const res = await axios.get('https://api.i-lynn.cn/college')
this.list = res.data.list
},
})
// axios.get('url?id=1').then(res => console.log(res))
// axios.get('url', { params: { id: 1 } }).then(res => console.log(res))
// axios.post('url', { id: 1}).then(res => console.log(res))
// axios({ url, .....})
script>
**目的:**在请求 发出去之前 / 收到响应之后 做一些操作
请求拦截器
axios.interceptors.request.use(function(config){
// 在请求发出去之前进行一些信息设置
return config;
},function(err){
// 处理响应的错误信息
});
响应拦截器
axios.interceptors.response.use(function(res){
// res为axios对象
return res;
},function(err){
// 处理响应的错误信息
});