1. vue基础知识和原理
1.1 初识Vue
- 想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象
- demo容器里的代码依然符合html规范,只不过混入了一些特殊的Vue语法
- demo容器里的代码被称为【Vue模板】
- Vue实例和容器是一一对应的
- 真实开发中只有一个Vue实例,并且会配合着组件一起使用
- {{xxx}}是Vue的语法:插值表达式,{{xxx}}可以读取到data中的所有属性
- 一旦data中的数据发生改变,那么页面中用到该数据的地方也会自动更新(Vue实现的响应式)
初始示例代码
<!-- 准备好一个容器 -->
<div id="demo">
<h1>Hello,{{name.toUpperCase()}},{{address}}</h1>
</div>
<script type="text/javascript" >
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
//创建Vue实例
new Vue({
el:'#demo', //el用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串。
data:{ //data中用于存储数据,数据供el所指定的容器去使用,值我们暂时先写成一个对象。
name:'hello,world',
address:'北京'
}
});
</script>
1.2 模板语法
Vue模板语法有2大类:
-
插值语法:
功能:用于解析标签体内容
写法:{{xxx}},xxx是js表达式,且可以直接读取到data中的所有属性
-
指令语法:
功能:用于解析标签(包括:标签属性、标签体内容、绑定事件…)
举例:v-bind:href=“xxx” 或 简写为 :href=“xxx”,xxx同样要写js表达式,且可以直接读取到data中的所有属性
代码
<div id="root">
<h1>插值语法</h1>
<h3>你好,{{name}}</h3>
<hr/>
<h1>指令语法</h1>
<!-- 这里是展示被Vue指令绑定的属性,引号内写的是js表达式 -->
<a :href="school.url.toUpperCase()" x="hello">点我去{{school.name}}学习1</a>
<a :href="school.url" x="hello">点我去{{school.name}}学习2</a>
</div>
<script>
new Vue({
el:'#root',
data:{
name:'jack',
school:{
name:'百度',
url:'http://www.baidu.com',
}
}
})
</script>
1.3 数据绑定
Vue中有2种数据绑定的方式:
-
单向绑定(v-bind):数据只能从data流向页面
-
双向绑定(v-model):数据不仅能从data流向页面,还可以从页面流向data
tips:
1.双向绑定一般都应用在表单类元素上(如:input、select等)
2.v-bind可以简写为:
2.v-model:value 可以简写为 v-model,因为v-model默认收集的就是value值
代码
<div id="root">
<!-- 普通写法 单向数据绑定 -->
单向数据绑定:<input type="text" v-bind:value="name"><br/>
双向数据绑定:<input type="text" v-model:value="name"><br/>
<!-- 简写 v-model:value 可以简写为 v-model,因为v-model默认收集的就是value值-->
单向数据绑定:<input type="text" :value="name"><br/>
双向数据绑定:<input type="text" v-model="name"><br/>
</div>
<script>
new Vue({
el:'#root',
data:{
name:'jack',
}
})
</script>
1.4 el与data的两种写法
el有2种写法
-
new Vue时候配置el属性
-
先创建Vue实例,随后再通过vm.$mount(‘#root’)指定el的值
代码
<script>
// 第一种
const vm = new Vue({
el:'#root',
data:{
name:'jack',
}
})
// 第二种
vm.$mount('#root')
</script>
data有2种写法
-
对象式
-
函数式
在组件中,data必须使用函数式,否则会报错
一个重要的原则:由Vue管理的函数,一定不要写箭头函数,一旦写了箭头函数,this就不再是Vue实例了。
代码
<script>
new Vue({
el:'#root',
// 第一种
data:{
name:'jack',
}
// 第二种
data() {
return {
name: 'jack'
}
}
})
</script>
1.5 Vue中的MVVM
- M:模型(Model) :data中的数据
- V:视图(View) :模板代码
- VM:视图模型(ViewModel):Vue实例
1.data中所有的属性,最后都出现在了vm身上。
2.vm身上所有的属性及Vue原型上所有属性,在Vue模板中都可以直接使用。
const vm = new Vue({
el: '#root',
data: {
name: 'yaya',
address: '北京'
}
})
1.6 数据代理
了解数据代理需要js的一些知识:Object.defineProperty(),属性标志,属性描述符,getter,setter。。。
建议学习文章地址:
https://zh.javascript.info/property-descriptors
https://zh.javascript.info/property-accessors
这里简单介绍一下:
属性标志:
对象属性(properties),除 value
外,还有三个特殊的特性(attributes),也就是所谓的“标志”
writable
— 如果为true
,则值可以被修改,否则它是只可读的enumerable
— 如果为true
,则表示是可以遍历的,可以在for… .in Object.keys()中遍历出来configurable
— 如果为true
,则此属性可以被删除,这些特性也可以被修改,否则不可以
eg:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>回顾Object.defineproperty方法</title>
</head>
<body>
<script>
let number = 18;
let person = {
name: '张三',
sex: '男',
// age: 18,
}
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;
}
})
console.log(person);
</script>
</body>
</html>
Object.getOwnPropertyDescriptor(obj, propertyName)
这个方法是查询有关属性的完整信息 obj是对象, propertyName是属性名
let user = {
name: "John"
};
let descriptor = Object.getOwnPropertyDescriptor(user, 'name');
console.log(descriptor)
/* 属性描述符:
{
"value": "John",
"writable": true,
"enumerable": true,
"configurable": true
}
*/
打印结果
Object.defineProperty(obj, prop, descriptor)
obj:要定义属性的对象。
prop:要定义或修改的属性的名称
descriptor:要定义或修改的属性描述符
let user = {
name: "John"
};
Object.defineProperty(user, "name", {
writable: false
});
user.name = "Pete";
// 打印后还是显示 'John',无法修改name值
其他的属性标志就不演示了,接下来看重点:访问器属性。
访问器属性:
本质上是用于获取和设置值的函数,但从外部代码来看就像常规属性。
访问器属性由 “getter” 和 “setter” 方法表示。在对象字面量中,它们用 get
和 set
表示:
let obj = {
get name() {
// 当读取 obj.propName 时,getter 起作用
},
set name() {
// 当执行 obj.name = value 操作时,setter 起作用
}
}
更复杂一点的使用
let user = {
surname: 'gao',
name: 'han'
get fullName() {
return this.name + this.surname;
}
}
console.log(user.fullName)
从外表看,访问器属性看起来就像一个普通属性。这就是访问器属性的设计思想。我们不以函数的方式 调用 user.fullName
,我们正常 读取 它:getter 在幕后运行。
vue的计算属性的底层构造感觉用到了这种思想,我目前还没看过源码,是这样猜想的。
截至目前,fullName
只有一个 getter。如果我们尝试赋值操作 user.fullName=
,将会出现错误:
user.fullName = "Test"; // Error(属性只有一个 getter)
为 user.fullName
添加一个 setter 来修复它:
let user = {
surname: 'gao',
name: 'han'
get fullName() {
return this.name + ' ' + this.surname;
}
set fullName(value) {
// 这个用到了新语法 结构赋值
[this.surname, this.name] = value.split(' ');
}
}
user.fullName = 'Li Hua'
console.log(user.name);
console.log(user.surname);
终于可以介绍数据代理了:
数据代理:通过一个对象代理对另一个对象中属性的操作(读/写)
先来看个案例:
let obj = {
x: 100
}
let obj2 = {
y: 200
}
这时候提一个需求:我们想要访问 obj 中的 x 的值,但我们最好不要直接去访问 obj ,而是想要通过 obj2 这个代理对象去访问。
这时候就可以用上 Object.defineProperty(),给 obj2 添加上访问器属性(也就是getter和setter)
代码
let obj = {
x: 100
}
let obj2 = {
y: 200
}
Object.defineProperty(obj2, 'x', {
get() {
return obj.x;
},
set(value) {
obj.x = value;
}
})
接下来介绍Vue中的数据代理
- Vue中的数据代理:通过vm对象来代理data对象中属性的操作(读/写)
- Vue中数据代理的好处:更加方便的操作data中的数据
- 基本原理:
- 通过Object.defineProperty()把data对象中所有属性添加到vm上。
- 为每一个添加到vm上的属性,都指定一个getter/setter。
- 在getter/setter内部去操作(读/写)data中对应的属性。
我来用一个案例来详细解释这一个过程。
<!-- 准备好一个容器-->
<div id="root">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm = new Vue({
el: '#root',
data: {
name: 'yaya',
address: '北京'
}
})
console.log(vm);
</script>
我们在控制台打印 new 出来的 vm
可以看到,写在配置项中的 data 数据被 绑定到了 vm 对象上,我先来讲结果,是 Vue 将 _data 中的 name,address 数据 代理到 vm 本身上。
一脸懵逼?
先来解释下_data 是啥, _data 就是 vm 身上的 _data 属性,就是下图那个
这个 _data 是从哪来的?
<script>
const vm = new Vue({
el: '#root',
// 我们在Vue 初始化的配置项中写了 data 属性。
data: {
name: 'yaya',
address: '北京'
}
})
</script>
new Vue 时, Vue 通过一系列处理, 将匹配项上的 data 数据绑定到了 _data 这个属性上,并对这个属性进行了处理(数据劫持),但这个属性就是来源于配置项中的 data,我们可以来验证一下。
<script>
let data = {
name: 'yaya',
address: '北京'
}
const vm = new Vue({
el: '#root',
// 我们在Vue 初始化的配置项中写了 data 属性。
data
})
</script>
打印结果为true,说明两者就是同一个
好了,再回到数据代理上来,将 vm._data 中的值,再代理到 vm 本身上来,用vm.name 代替 vm._data.name。这就是 Vue 的数据代理
这一切都是通过 Object.defineProperty() 来完成的,我来模拟一下这个过程
Object.defineProperty(vm, 'name', {
get() {
return vm._data.name;
},
set(value) {
vm._data.name = value
}
})
这样有啥意义?明明通过 vm._data.name 也可以访问 name 的值,为啥费力去这样操作?
在插值语法中,{{ name }} 取到的值就相当于 {{ vm.name }},不用数据代理的话,在插值语法就要这样去写了。
{{ _data. name }} 这不符合直觉,怪怪的。vue 这样设计更利于开发者开发,我们在研究原理会觉得有些复杂
来个尚硅谷张天禹老师做的图(非常推荐去看他的课,讲的非常好)
1.7 事件处理
事件的基本使用:
- 使用v-on:xxx 或 @xxx 绑定事件,其中xxx是事件名
- 事件的回调需要配置在methods对象中,最终会在vm上
- methods中配置的函数,都是被Vue所管理的函数,this的指向是vm 或 组件实例对象
- methods种配置的函数,都是被Vue所管理的函数,this的指向是vm或组件实例对象
- @click="demo"和@click="demo($event)"效果一致,但后者可以传参
<!-- 准备好一个容器-->
<div id="root">
<h2>欢迎来到{{name}}学习</h2>
<!-- <button v-on:click="showInfo">点我提示信息</button> -->
<button @click="showInfo1">点我提示信息1(不传参)</button>
<!-- 主动传事件本身 -->
<button @click="showInfo2($event,66)">点我提示信息2(传参)</button>
</div>
<script>
const vm = new Vue({
el:'#root',
data:{
name:'vue',
},
methods:{
// 如果vue模板没有写event,会自动传 event 给函数
showInfo1(event){
// console.log(event.target.innerText)
// console.log(this) //此处的this是vm
alert('同学你好!')
},
showInfo2(event,number){
console.log(event,number)
// console.log(event.target.innerText)
// console.log(this) //此处的this是vm
alert('同学你好!!')
}
}
});
</script>
Vue中的事件修饰符
- prevent:阻止默认事件(常用)
- stop:阻止事件冒泡(常用)
- once:事件只触发一次(常用)
- capture: 使用事件的捕获模式
- self: 只有event.target是当前操作的元素时才触发事件
- 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>
</div>
<!-- 事件只触发一次 -->
<button @click.once="showInfo">点我提示信息</button>
<!-- 使用事件的捕获模式 -->
<div class="box1" @click.capture="showMsg(1)">
div1
<div class="box2" @click="showMsg(2)">
div2
</div>
</div>
<!-- 只有event.target是当前操作的元素时才触发事件 -->
<div class="demo1" @click.self="showInfo">
<button @click="showInfo">点我提示信息</button>
</div>
<!-- 事件的默认行为立即执行,无需等待事件回调执行完毕 -->
<ul @wheel.passive="demo" class="list">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
</div>
<script type="text/javascript">
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
new Vue({
el: "#root",
data: {
name: "yaya",
},
methods: {
showInfo(e) {
// if (e.keyCode !== 13) return;
console.log(e.target.value);
},
},
});
</script>
1.8 键盘事件
键盘事件语法糖:@keydown,@keyup
- Vue中常用的按键别名:
- 回车 => enter
- 删除 => delete
- 退出 => esc
- 空格 => space
- 换行 => tab (特殊,必须配合keydown去使用)
- 上 => up
- 下 => down
- 左 => left
- 右 => right
- Vue未提供别名的按键,可以使用按键原始的key值去绑定,但注意要转为kebab-case(短横线命名)
- 系统修饰键(用法特殊) : ctrl、alt、shift、 meta
- 配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。
- 配合keydown使用:正常触发事件。
- 也可以使用keyCode去指定具体的按健(不推荐)
- Vue.config.keyCodes.自定义键名 = 键码,可以去定制按键别名
<!-- 准备好一个容器-->
<div id="root">
<h2>欢迎来到{{name}}学习</h2>
<input type="text" placeholder="按下回车提示输入" @keydown.enter="showInfo">
</div>
<script>
new Vue({
el:'#root',
data:{
name:'浙江理工大学'
},
methods: {
showInfo(e){
// console.log(e.key,e.keyCode)
console.log(e.target.value)
}
},
})
</script>
1.9 计算属性
- 定义:要用的属性不存在,要通过已有属性计算得来
- 原理:底层借助了Objcet.defineProperty方法提供的getter和setter
- get函数什么时候执行?
- (1).初次读取时会执行一次
- (2).当依赖的数据发生改变时会被再次调用
- 优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便
- 备注:
- 计算属性最终会出现在vm上,直接读取使用即可
- 如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变
计算属性完整版写法
<!-- 准备好一个容器-->
<div id="root">
姓:<input type="text" v-model="firstName">
名:<input type="text" v-model="lastName">
全名:<span>{{fullName}}</span>
</div>
<script>
const vm = new Vue({
el:'#root',
data:{
firstName:'张',
lastName:'三',
}
computed:{
fullName:{
//get有什么作用?当有人读取fullName时,get就会被调用,且返回值就作为fullName的值
//get什么时候调用?1.初次读取fullName时。2.所依赖的数据发生变化时。
get(){
console.log('get被调用了')
return this.firstName + '-' + this.lastName
},
//set什么时候调用? 当fullName被修改时。
// 可以主动在控制台修改fullName来查看情况
set(value){
console.log('set',value)
const arr = value.split('-')
this.firstName = arr[0]
this.lastName = arr[1]
}
}
}
})
</script>
计算属性简写
<!-- 准备好一个容器-->
<div id="root">
姓:<input type="text" v-model="firstName">
名:<input type="text" v-model="lastName">
全名:<span>{{fullName}}</span>
</div>
<script>
const vm = new Vue({
el:'#root',
data:{
firstName:'张',
lastName:'三',
}
computed:{
fullName() {
console.log('get被调用了')
return this.firstName + '-' + this.lastName
}
}
})
</script>