1.数据代理(2022.5.1)
1.1数据代理 通过一个对象代理对另一个对象中属性的操作(读写)**
<script>
let obj = {
x: 100
};
let obj2 = {
y: 200
}
Object.defineProperty(obj2, 'x', {
//当有人访问obj2.x属性,调用get() getter函数,且返回obj.x的值
get() {
console.log('我跟随obj.x 的修改');
return obj.x
},
//当有人修改obj2.x属性,调用set() setter函数,且返回value的值
set(value) {
console.log('我修改了obj2.x的值,并返回给obj.x');
//obj.x = value;
}
})
1.2 Object.defineProperty()
- Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象
- 参数 Object.defineProperty(obj, prop, descriptor)
1.obj 要定义属性的对象
2.prop 要定义或修改的属性的名称
3.descriptor 要定义或修改的属性描述符(配置项) - 返回值 传递给方法的对象
<script>
let number = 18;
let person = {
name: '尚硅谷',
sex: '男',
//age: 18
}
//Object.defineProperty 向对象添加属性
// object 属性名 {value}
Object.defineProperty(person, 'age', {
//value: 18,
//enumerable: true, //控制属性是否可以被枚举(遍历),默认是false
//writable: true, //控制属性是否可以被修改,默认是false
//configurable: true, //控制属性是否可以被删除,默认是false
//当有人访问person.age属性,调用get() getter函数,且返回age的值
get: function() {
console.log('有人读取age属性了');
return number
},
//当有人修改person.age属性,调用set() setter函数,且返回value的值
set(value) {
console.log('有人要修改age的值,且值是' + value);
number = value;
}
})
/* for (let k in person) {
console.log(person[k]);
}*/
</script>
1.3vue中的数据代理
Vue实例化的时候vm_data = option.data
数据代理时 name adderss 代理 vm_data
1.Vue中的数据代理:
通过vm对象来代理data对象中属性的操作(读/写)
2.Vue中数据代理的好处:
更加方便的操作data中的数据
3.基本原理:
通过Object.defineProperty()把data对象中所有属性添加到vm上。
为每一个添加到vm上的属性,都指定一个getter/setter。
vm_data=option.data
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el: '.root',
data: {
name: '尚硅谷',
adderss: '北京科技园'
},
});
</script>
2022.05.03-08点34分
2.事件处理
- 使用v-on:xxx 或 @xxx 绑定事件,其中xxx是事件名
- 事件的回调需要配置在methods对象中,最终会在vm上
- methods中配置的函数,都是被Vue所管理的函数,this的指向是vm 或 组件实例对象
- 不写参数,默认传递的是event; showInfo2($event,66),此时第一个参数是事件对象,第二个用户自定义
<body>
<div class="root">
<!-- <button v-on:click="showInfo">点击我提示信息(不传参)</button>-->
<!-- 简写 -->
<button @click="showInfo">点我提示信息</button>
<!-- <button v-on:click="showInfo2($event,66)">点击我提示信息2</button> -->
<button @click="showInfo2($event,66)">点击我提示信息2(传参)</button>
</div>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el: '.root',
data: {
name: '',
},
//回调函数
methods: {
showInfo(event) {
alert('你好,vue');
},
showInfo2(event, number) {
console.log(number);
alert('你好,vue22');
},
}
});
</script>
Vue中的事件修饰符
prevent:阻止默认事件(常用)
stop:阻止事件冒泡(常用)
once:事件只触发一次(常用)
<body>
<div class="root">
<!-- 事件修饰符 -->
<!-- 1.阻止事件的默认行为 -->
<a href="www.baidu.com" @click.prevent="showInfo">点击我</a>
<!-- 2.阻止事件冒泡 -->
<div class="demo" @click="showInfo2">
<div class="demo1" @click.stop="showInfo2"></div>
</div>
<!-- 事件可以连写 -->
<!-- <div class="demo1" @click.prevent.stop="showInfo2"></div> -->
<!-- 3.事件只触发一次 -->
<button @click.once="showInfo3">点击我</button>
</div>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el: '.root',
data: {
name: '',
},
methods: {
showInfo() {
alert('阻止事件的默认行为');
},
showInfo2() {
alert('阻止事件冒泡');
},
showInfo3() {
alert('事件值触发一次');
}
}
});
</script>
</body>
键盘事件
<body>
<!-- 键盘事件语法糖:@keydown,@keyup
Vue中常用的按键别名:
回车 => enter
删除 => delete
退出 => esc
空格 => space
换行 => tab (特殊,必须配合keydown去使用)
@keydown.keycode(自定义配置按键 不推荐)
Vue.config.keyCodes.键名 = 键码,可以定制按键
可以组合按键enter.x 触发事件-->
<div class="root">
<label for="">欢迎来到{{name}}学习</label><br>
<input type="text" @keydown.enter="showInfo">
</div>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el: '.root',
data: {
name: '尚硅谷',
},
methods: {
showInfo(e) {
console.log(e.target.value);
}
}
});
</script>
</body>
3.计算属性
计算属性
定义: 要用的属性不存在, 要通过已有属性计算得来
原理: 底层借助了Objcet.defineProperty方法提供的getter和setter
get函数什么时候执行?(1).初次读取时会执行一次(2).当依赖的数据发生改变时会被再次调用
methods 每次读取都会调用一次,计算属性会把fullName缓存到内部,只要值不发生改变,就不会调用get
优势: 与methods实现相比, 内部有缓存机制( 复用), 效率更高, 调试方便
备注:
计算属性最终会出现在vm上, 直接读取使用即可
如果计算属性要被修改, 那必须写set函数去响应修改, 且set中要引起计算时依赖的数据发生改变
<body>
<div class="root">
姓:<input type="text" v-model:value='firstName'> <br> 名:
<input type="text" v-model="lastName"> <br> 姓名:
<!-- 插值语法实现姓名案例 -->
<!-- <span>{{firstName.slice(0,3)}}- {{lastName}}</span> -->
<!-- 用回调函数实现姓名案例 -->
<!-- 计算属性实现姓名案例 -->
<!-- vm.fullName.get()会报错,与methods中函数不同 -->
<span>{{fullName}}</span><br>
<!-- <span>{{fullName}}</span><br>
<span>{{fullName}}</span><br>
<span>{{fullName}}</span><br>
<span>{{fullName}}</span><br> -->
</div>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el: '.root',
data: {
firstName: '张',
lastName: '三',
},
/*methods: {
fullName() {
console.log('methods被调用了');
return this.firstName + '-' + this.lastName
}
}*/
//计算属性
// 定义: 要用的属性不存在, 要通过已有属性计算得来
// 原理: 底层借助了Objcet.defineProperty方法提供的getter和setter
// get函数什么时候执行?(1).初次读取时会执行一次(2).当依赖的数据发生改变时会被再次调用
// 优势: 与methods实现相比, 内部有缓存机制( 复用), 效率更高, 调试方便
// methods 每次读取都会调用一次,计算属性会把fullName缓存到内部,只要值不发生改变,就不会调用get
// 备注:
// 计算属性最终会出现在vm上, 直接读取使用即可
// 如果计算属性要被修改, 那必须写set函数去响应修改, 且set中要引起计算时依赖的数据发生改变
computed: {
fullName: {
get() {
console.log('get被调用了');
//此时this 经过vue处理指向vm
return this.firstName + '-' + this.lastName
},
//此时的参数是修改后的新值
set(value) {
console.log('set', value)
//split 按照间隔符- 将字符串分成两个数组
const arr = value.split('-')
this.firstName = arr[0]
this.lastName = arr[1]
}
}
}
});
</script>
</div>
</body>
4.监视属性
- 用计算属性实现的监视属性也可以实现
<body>
<div class="root">
<span>今天的天气好{{Info}}</span><br>
<button @click="changeWeather">点击切换天气</button>
</div>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el: '.root',
data: {
isHOT: true,
},
methods: {
changeWeather() {
this.isHOT = !this.isHOT;
//console.log(this.isHOT);
}
},
computed: {
Info() {
return this.isHOT ? '炎热' : '凉快'
}
},
//监视某一个属性值的变化 计算属性也可以监视
//handler什么时候调用?当isHot发生改变时。
watch: {
isHOT: {
//初始化时执行一次handler 默认值是false
immediate: true,
handler(newvalue, oldvalue) {
console.log(newvalue, oldvalue);
}
}
}
});
//监视属性的第二种写法 开始不确定监视的属性
/* vm.$watch('isHot', {
immediate: true,
handler(newvalue, oldvalue) {
console.log(newvalue, oldvalue);
}
})*/
</script>
5.绑定class样式
<style>
.basic {
width: 200px;
height: 100px;
border: 1px solid black;
}
.happy {
background-color: pink;
}
.sad {
background-color: green;
}
.normal {
background-color: blue;
}
.atguigu1 {
background-color: pink;
}
.atguigu2 {
border-radius: 30px;
}
.atguigu3 {
border: 10px solid skyblue;
}
</style>
</head>
<body>
<div class="root">
<!-- 绑定class样式--字符串写法,适用于class的类名不确定,需要动态指定 -->
<!-- <div class="basic" :class="mood" @click="changeMood">{{name}}</div><br><br> -->
<!-- 绑定class样式--数组写法,适用于:要绑定的样式个数不确定、名字也不确定 -->
<!-- 可以使用数组方法 arr.shift arr.push 增加或者删除样式 -->
<div class="basic" :class="moodArr">{{name}}</div><br><br>
<!-- 绑定class样式--对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用 -->
<div class="basic" :class="moodObj">{{name}}</div><br><br>
</div>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el: '.root',
data: {
name: '尚硅谷',
mood: 'normal',
moodArr: ['atguigu1', 'atguigu2', 'atguigu3'],
moodObj: {
atguigu1: false,
atguigu2: false,
atguigu3: true
}
},
methods: {
changeMood() {
const MoodArr = ['happy', 'sad', 'normal'];
const index = Math.floor(Math.random() * 3);
console.log(index);
this.mood = this.MoodArr[index];
},
},
watch: {}
});
</script>
</body>
6.条件渲染
1.1 v-if
写法:
(1).v-if=“表达式”
(2).v-else-if=“表达式”
(3).v-else=“表达式”
-
适用于:切换频率较低的场景
-
特点:不展示的DOM元素直接被移除
-
注意:v-if可以和:v-else-if、v-else一起使用,但要求结构不能被“打断”
<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>
1.2 v-show
- 写法:v-show=“表达式”
- 适用于:切换频率较高的场景
- 特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉(display:none)
<button @click="n++">点击让n++</button>
<button @click="n--">点击让n--</button>
<div class="test" v-show="n==2">欢迎来到{{name}}</div>
1.3 列表渲染
v-for指令
- 用于展示列表数据
- 语法:v-for=“(item, index) in xxx” :key=“yyy”
- 可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)
- v-for 的两个参数 一个是索引值 一个是索引号
<body>
<div class="root">
<!-- v-for 的两个参数 一个是索引值 一个是索引号 -->
<!-- 遍历数组 -->
<ul>{{name}}
<li v-for="(p,index) in persons" :key="index">{{p.name}}--{{index}}</li>
</ul>
<!-- 遍历对象 第一个参数是value 第二个是键码 key -->
<ul>
<li v-for="(car,key) in cars">{{key}}--{{car}}</li>
</ul>
</div>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el: '.root',
data: {
name: '人员列表',
persons: [{
name: '张三',
id: 001,
age: 18
}, {
name: '历史',
id: 002,
age: 19
}, {
name: '王五',
id: 003,
age: 20
}],
cars: {
name: '奥迪A8',
price: '70万',
color: '黑色'
},
str: 'hello'
},
methods: {},
watch: {}
});
</script>
</body>
虚拟DOM中key的作用
-
key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】, 随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:
-
旧虚拟DOM中找到了与新虚拟DOM相同的key:
-
①.若虚拟DOM中内容没变, 直接使用之前的真实DOM!
-
②.若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。
-
旧虚拟DOM中未找到与新虚拟DOM相同的key
-
创建新的真实DOM,随后渲染到到页面。
用index作为key可能会引发的问题:
-
若对数据进行:逆序添加、逆序删除等破坏顺序操作:
-
会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
- `<!-- 准备好一个容器-->
<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">
Vue.config.productionTip = false
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)
}
},
});
</script>
初始数据
persons: [
{ id: ‘001’, name: ‘张三’, age: 18 },
{ id: ‘002’, name: ‘李四’, age: 19 },
{ id: ‘003’, name: ‘王五’, age: 20 }
]
vue根据数据生成虚拟 DOM
初始虚拟 DOM
<li key='0'>张三-18<input type="text"></li>
<li key='1'>李四-19<input type="text"></li>
<li key='2'>王五-20<input type="text"></li>
将虚拟 DOM 转为 真实 DOM
this.persons.unshift({ id: ‘004’, name: ‘老刘’, age: 40 })
在 persons 数组最前面添加上 { id: ‘004’, name: ‘老刘’, age: 40 }
新数据:
persons: [
{ id: ‘004’, name: ‘老刘’, age: 40 },
{ id: ‘001’, name: ‘张三’, age: 18 },
{ id: ‘002’, name: ‘李四’, age: 19 },
{ id: ‘003’, name: ‘王五’, age: 20 }
]
vue根据数据生成虚拟 DOM
新虚拟 DOM
<li key='0'>老刘-30<input type="text"></li>
<li key='1'>张三-18<input type="text"></li>
<li key='3'>李四-19<input type="text"></li>
<li key='4'>王五-20<input type="text"></li>
将虚拟 DOM 转为 真实 DOM
因为老刘被插到第一个,重刷了 key 的值,vue Diff 算法 根据 key 的值 判断 虚拟DOM 全部发生了改变,然后全部重新生成新的 真实 DOM。实际上,张三,李四,王五并没有发生更改,是可以直接复用之前的真实 DOM,而因为 key 的错乱,导致要全部重新生成,造成了性能的浪费。
结论:
最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值
如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的
正确使用key

2022-05-04-09点18分
7.获取表单元素的技巧
1.v-model默认收集的是value
2.没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值)
3.男女信息收集标准做法 name=“sex”
4.多组checkbox收集,data用数组
v-model的三个修饰符
- trim 去掉首尾的空格
- lasy 失去焦点在收集数据
- number 收集到数据是数字类型的 一般与 < input type=“number”>搭配使用
<!-- v-model的三个修饰符
trim 去掉首尾的空格
lasy 失去焦点在收集数据
number 收集到数据是数字类型的 一般与<input type="number">搭配使用 -->
<body>
<div class="root">
<form action="" @submit.prevent="demo">
<!-- v-model 默认收集的value -->
<label>账号: <input type="text" v-model.trim="account"> </label><br><br>
<label>密码: <input type="password" v-model.trim="password"> </label><br><br>
<label>年龄: <input type="number" v-model.number="age"> </label><br><br>
<label>性别: 男:<input type="radio" name="sex" checked="checked" value="male" v-model="sex">
女:<input type="radio" name="sex" value="female" v-model="sex"></label><br><br>
<!-- 多组checkbox收集,data用数组 -->
<label>爱好: 吃饭:<input type="checkbox" checked="checked" value="eat" v-model="hobbies">
睡觉:<input type="checkbox" value="sleep" v-model="hobbies">
打豆豆:<input type="checkbox" value="beatDou" v-model="hobbies">
</label><br><br>
<!-- select收集数据在v-model绑定在select 不要写在option上 -->
<select name="" id="" v-model="schAddress">
<option value="" >请选择你的校区</option>
<option value="beijing" >北京</option>
<option value="shanghai" >上海</option>
<option value="guangzhou" >广州</option>
<option value="shenzhen" >深圳</option>
</select><br><br>
<label>其他信息: <textarea name="" id="input" class="form-control" rows="3" required="required" v-model.lazy="otherInfo">
</textarea></label><br><br>
<!-- 此时不需要input 的value -->
<input type="checkbox" v-model="agree"><span>阅读并接受 <a href="">《用户协议》</a></span><br><br>
<button>提交</button>
</form>
</div>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el: '.root',
data: {
account: '',
password: '',
age: '',
sex: '',
hobbies: [],
schAddress: '',
otherInfo: '',
agree: ''
},
methods: {
demo() {
console.log(JSON.stringify(this._data));
}
},
computed: {},
watch: {}
});
</script>
2022-5-5-09点46分
8.内置指令
- v-text
v-text指令:(使用的比较少)
1.作用:向其所在的节点中渲染文本内容。
2.与插值语法的区别:v-text会替换掉节点中的内容,{{xx}}则不会。
3.v-text 不会解析html标签
<div class="root">
<!-- v-text指令:(使用的比较少) -->
<!-- 1.作用:向其所在的节点中渲染文本内容。 -->
<!-- 2.与插值语法的区别:v-text会替换掉节点中的内容,{{xx}}则不会。 -->
<!-- 3.v-text 不会解析html标签 -->
<div>你好,{{name}}</div>
<div v-text="name">你好</div>
-
v-html
1.作用:向指定节点中渲染包含html结构的内容。 2.与插值语法的区别: v-html会替换掉节点中所有的内容,{{xx}}则不会。 v-html可以识别html结构。 3.严重注意:v-html有安全性问题!!!! 在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击。 一定要在可信的内容上使用v-html,永不要用在用户提交的内容上!
//此时会解析str中html标签
<div v-html="str">这是我原来的结构</div>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el: '.root',
data: {
n: 0,
name: '尚硅谷',
str: '<h2>这是一个html 的h2标签</h2>'
},
methods: {},
computed: {},
watch: {}
});
</script>
- v-once
1. v-once所在节点在初次动态渲染后,就视为静态内容了。
2. 以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能
//此时div中 n的值是第一次的值
<div v-once>{{n}}</div>
<button @click="n++">点击我让n++---{{n}}</button>
- v-pre
v-pre指令:(比较没用)
跳过其所在节点的编译过程
可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译
<div v-pre>我没有使用vue</div>
<div>{{name}}</div>
9.自定义指令
配置项关键词 directives
简写形式不能进行复杂的调用
<body>
<div class="root">
<div v-text="n"></div>
<!-- 自定义指令 使n数值放大十倍 -->
<div v-big="n"></div>
<button @click="n++">点击让n++</button><br>
<input type="text" v-fbind:value="n">
</div>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el: '.root',
data: {
n: 1,
name: '',
},
methods: {},
computed: {},
watch: {},
directives: {
//big 函数何时被调用? 1.指令和元素绑定时(一上来,此时元素在内存中,还没有渲染到页面上)
// 2.指令所在的模板重新解析时(值发生变化就会重新解析)
big(element, binding) {
//参数1 真实dom元素 参数2指令
//console.log(a, b);
element.innerText = binding.value * 10;
},
/*fbind(element, binding) {
//此时input第一次不能获得焦点
//点击一次input被放到html 才能获得焦点
element.value = binding.value;
element.focus();
// console.log(binding);
}*/
fbind: {
//1.指令和元素绑定时
bing(element, binding) {
element.value = binding.value;
console.log("bind");
},
//2.指令所在元素被插入页面时
inserted(element, binding) {
element.focus();
console.log('inserted');
},
//3.指令所在的模板被重新解析时
update(element, binding) {
element.value = binding.value;
console.log('update');
}
}
}
});
</script>
</body>
- 定义全局指令
Vue.directive('fbind', {
// 指令与元素成功绑定时(一上来)
bind(element, binding){
element.value = binding.value
},
// 指令所在元素被插入页面时
inserted(element, binding){
element.focus()
},
// 指令所在的模板被重新解析时
update(element, binding){
element.value = binding.value
}
})
10.Vue的生命周期
Vue 实例有⼀个完整的⽣命周期,也就是从new Vue()、初始化事件(.once事件)和生命周期、编译模版、挂载Dom -> 渲染、更新 -> 渲染、卸载 等⼀系列过程,称这是Vue的⽣命周期。
引用一张尚硅谷老师的图
<!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>Document</title>
<script src="./js/vue.js"></script>
</head>
<body>
<div class="root">
<!-- <h2>{{name}}</h2> -->
<h3>{{n}}</h3>
<button @click="n++">点击让n++-{{n}}</button>
<button @click="stop">点击我销毁vm</button>
</div>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el: '.root',
data: {
//name: '尚硅谷',
n: 1
},
methods: {
stop() {
vm.$destroy();
}
},
computed: {},
watch: {},
//beforeCreate(创建前):数据监测(getter和setter)和初始化事件还未开始,
//此时 data 的响应式追踪、event/watcher 都还没有被设置,
//也就是说不能访问到data、computed、watch、methods上的方法和数据
beforeCreate() {
console.log('beforeCreate');
console.log(this);
//debugger;
},
/*created(创建后):实例创建完成,
实例上配置的 options 包括 data、computed、watch、methods 等都配置完成,
但是此时渲染得节点还未挂载到 DOM,所以不能访问到 $el属性。*/
created() {
console.log('create');
console.log(this);
//debugger;
},
/*beforeMount(挂载前):在挂载开始之前被调用,相关的render函数首次被调用。
此阶段Vue开始解析模板,生成虚拟DOM存在内存中,还没有把虚拟DOM转换成真实DOM,
插入页面中。所以网页不能显示解析好的内容。
此时可以操作dom,但最终不会生效*/
beforeMount() {
console.log('beforeMount');
console.log(this);
//debugger;
},
/*mounted(挂载后):在el被新创建的 vm.$el(就是真实DOM的拷贝)替换,
并挂载到实例上去之后调用(将内存中的虚拟DOM转为真实DOM,真实DOM插入页面)。
此时页面中呈现的是经过Vue编译的DOM,这时在这个钩子函数中对DOM的操作可以有效,
但要尽量避免。一般在这个阶段进行:开启定时器,发送网络请求,订阅消息,绑定自定义事件等等*/
mounted() {
console.log('mounted');
console.log(this);
//debugger;
},
/*beforeUpdate(更新前):响应式数据更新时调用,此时虽然响应式数据更新了,
但是对应的真实 DOM 还没有被渲染(数据是新的,但页面是旧的,页面和数据不同步 */
beforeUpdate() {
console.log('beforeUpdate');
console.log(this);
//debugger;
},
/*updated(更新后) :在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。
此时 DOM 已经根据响应式数据的变化更新了。
调用时,组件 DOM已经更新,所以可以执行依赖于DOM的操作。
然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用*/
updated() {
console.log('updated');
//debugger;
console.log(this);
},
/*beforeDestroy(销毁前):实例销毁之前调用。这一步,实例仍然完全可用,this 仍能获取到实例。
在这个阶段一般进行关闭定时器,取消订阅消息,解绑自定义事件*/
beforeDestroy() {
console.log('beforeDestroy');
},
/* destroyed(销毁后):实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,
所有的事件监听器(自定义)会被移除,原生事件不会被移除比如click,
所有的子实例也会被销毁。该钩子在服务端渲染期间不被调用。*/
destroyed() {
console.log('destroyed');
},
});
</script>
</body>
</html>
11.非单文件组件
什么是组件 实现局部代码和功能的集合
基本使用
Vue中使用组件的三大步骤:
- 定义组件(创建组件)
- 注册组件
- 使用组件(写组件标签)
定义组件
使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但也有点区别;
区别如下:
el不要写,为什么? ——— 最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。
data必须写成函数,为什么? ———— 避免组件被复用时,数据存在引用关系,引用关系错乱。
let data = {
x: 100,
y: 100
}
const a = data;
const b = data;
console.log(a);
console.log(b);
console.log(a === b); //true 此时返回的是地址, a===b 修改a或者b的都会导致 data的值改变
function data1() {
return {
x: 100,
y: 100
}
}
const c = data1();
const d = data1();
console.log(c === d); //false 此时返回的是对象, a===b 修改a或者b的都会不影响 data的值改变
组件的使用
<body>
<div class="root">
<!-- 第三步:使用组件 -->
<school></school>
<hr>
<student></student>
<hr>
<hello></hello>
<hello></hello>
<!-- <hello></hello> -->
</div>
<script>
// el不要写, 为什么?——— 最终所有的组件都要经过一个vm的管理, 由vm中的el决定服务哪个容器。
// data必须写成函数, 为什么?———— 避免组件被复用时, 数据存在引用关系
//局部组件
//第一步:注册组件
const school = Vue.extend({
template: ` <div>
<h2>{{name}}</h2>
<h2>{{address}}</h2>
</div> `,
data() {
return {
name: '尚硅谷',
address: '北京',
}
}
});
const student = Vue.extend({
template: ` <div>
<h2>{{name}}</h2>
<h2>{{age}}</h2>
</div> `,
data() {
return {
name: '张三',
age: 18,
}
}
});
const hello = Vue.extend({
template: ` <div>
<h2>{{msg}}</h2>
</div> `,
data() {
return {
msg: 'hello',
}
}
});
//全局组件绑定 参数1组件标签名字 参数 注册的组件变量
//所有的vue 实例 都可以使用hello 组件
Vue.component('hello', hello);
Vue.config.productionTip = false;
const vm = new Vue({
el: '.root',
/*data: {
name: '尚硅谷',
address: '北京',
name: '张三',
age: 18
},*/
//第二步:绑定组件
components: {
school,
student
},
methods: {},
computed: {},
watch: {}
});
</script>
</body>
组件的几个注意点
关于组件名:
可以使用name配置项在vue 中指定标签的名字
- 一个单词组成:
第一种写法(首字母小写): school
第二种写法(首字母大写): School - 两个单词组成:
第一种写法(kebab - case命名): my - school
第二种写法(CamelCase命名): MySchool(需要Vue脚手架支持)
备注:
(1).组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行。
(2).可以使用name配置项指定组件在开发者工具中呈现的名字
- 组件的简写
// 可以简写为
//此时vue 会调用一个函数 如果穿过去的是一个对象 会自动转化为 组件
const myschool = ({
template: `
<h2>{{name}}</h2>
`,
data() {
return {
name: '尚硅谷'
}
}
})
组件的嵌套
<!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>Document</title>
<script src="./js/vue.js"></script>
</head>
<body>
<div class="root">
<app></app>
</div>
<script>
const student = Vue.extend({
template: ` <div>
<h2>{{name}}</h2>
<h2>{{age}}</h2>
</div> `,
data() {
return {
name: '张三',
age: 18,
}
}
});
const school = Vue.extend({
template: `
<div>
<h2>{{name}}</h2>
<h2>{{address}}</h2>
<student></student>
</div>
`,
components: {
student
},
data() {
return {
name: '尚硅谷',
address: '北京昌平'
}
}
});
const hello = Vue.extend({
template: ` <div>
<h2>{{msg}}</h2>
</div> `,
data() {
return {
msg: 'hello',
}
}
});
//创建一个app组件用来管理所有的组件
const app = Vue.extend({
components: {
school,
hello
},
template: ` <div>
<school></school>
<hello></hello>
</div> `,
});
Vue.config.productionTip = false;
const vm = new Vue({
el: '.root',
data: {
name: '',
},
methods: {},
computed: {},
watch: {},
components: {
app
}
});
</script>
</body>
</html>
Vuecomponents
- school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的。
- 我们只需要写或,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行的:new VueComponent(options)。
- 特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent!!!!(这个VueComponent可不是实例对象)
- 关于this指向:
组件配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【VueComponent实例对象】。
new Vue(options)配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【Vue实例对象】。 - VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)。Vue的实例对象,以后简称vm。
一个重要的内置关系
<script>
function Demo() {
this.a = 1;
this.b = 2;
}
const d = new Demo();
console.log(Demo.prototype); //显示原型属性
console.log(d.__proto__); //隐式原型属性
console.log(Demo.prototype === d.__proto__);
Demo.prototype.x = 99;
console.log(Demo);
console.log(d);
//原型对象 原型链
//d 是Demo的实例对象 Demo{a:1,b:2} 隐式原型属性[[Prototype]]是 _ _proto_ _的标准名字, 谷歌中为_ _proto_ _
//对原型对象的理解
//Demo.protoType 找到的是Demo的原型对象(是一个对象,也有原型对象),d._ _proto_ _找到的是自己缔造者的原型对象
//此时再向下查找 找到了object的原型对象 也就是原型链的尽头
//一般通过Demo.protoType 向Demo的原型对象添加属性 通过new Demo() 创建的实例对象都可以使用这个属性
//一个重要的内置关系:VueComponent.prototype.proto === Vue.prototype
//vue使VueComponent的原型对象的隐式属性不指向object的原型对象,而是指向了Vue的原型对象
//为什么要有这个关系: 让组件实例对象( vc school) 可以访问到 Vue原型上的属性、 方法。
const school = Vue.extend({
template: `
<div>
<h2>{{name}}</h2>
<h2>{{address}}</h2>
</div>
`,
components: {
//student
},
data() {
return {
name: '尚硅谷',
address: '北京昌平'
}
}
});
Vue.config.productionTip = false;
const vm = new Vue({
el: '.root',
data: {
name: '',
},
methods: {},
computed: {},
watch: {},
components: {
school
}
});
</script>
2.脚手架 vue cli
使用前置:
第一步(没有安装过的执行):全局安装 @vue/cli
npm install -g @vue/cli
第二步:切换到要创建项目的目录,然后使用命令创建项目
vue create xxxxx 项目名称
第三步:启动项目
npm run serve
2.1脚手架文件结构
├── node_modules
├── public
│ ├── favicon.ico: 页签图标
│ └── index.html: 主页面
├── src
│ ├── assets: 存放静态资源
│ │ └── logo.png
│ │── component: 存放组件
│ │ └── HelloWorld.vue
│ │── App.vue: 汇总所有组件
│ │── main.js: 入口文件
├── .gitignore: git版本管制忽略的配置
├── babel.config.js: babel的配置文件
├── package.json: 应用包配置文件
├── README.md: 应用描述文件
├── package-lock.json:包版本控制文件
————————————————
2.2demo
- 入口文件- main.js
//引入Vue组件
import Vue from 'vue'
//引入App组件 App组件是所有组件的父组件
import App from './App.vue'
//配置Vue
Vue.config.productionTip = false
//创建一个vm,并选择id=app为容器
new Vue({
render: h => h(App),
}).$mount('#app')
- App组件 App.vue 所有组件的父组件
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
<MySchool></MySchool>
<MyStudent></MyStudent>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
import MyStudent from './components/MyStudent.vue'
import MySchool from './components/MySchool.vue'
export default {
name: 'App',
components: {
HelloWorld,
MyStudent,
MySchool
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
- 子组件 MySchool.vue
<template>
<div>
<h2>{{name}}</h2>
<h2>{{address}}</h2>
</div>
</template>
<script>
export default {
name:'MySchool',
data() {
return {
name: '尚硅谷',
address: '北京昌平'
}
}
}
</script>
<style>
</style>
- 脚手架的index
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<!-- 针对IE浏览器的一个特殊配置,含义是让IE浏览器以最高的渲染级别渲染页面 -->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- 开启移动端的理想视口 -->
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!-- 配置页签图标 -->
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<!-- 引入第三方样式 -->
<link rel="stylesheet" href="<%= BASE_URL %>css/bootstrap.css">
<!-- 配置网页标题 -->
<title>硅谷系统</title>
</head>
<body>
<!-- 当浏览器不支持js时noscript中的元素就会被渲染 -->
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<!-- 容器 -->
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
使用 import 导入第三方库的时候不需要 加 ‘./’
- 导入我们自己写的:
import App from ‘./App.vue’
-
导入第三方的
import Vue from 'vue‘ //不需要写具体路径 vue已经帮我们配置好了
不需要在 from ‘vue’ 加 ‘./’ 的原因是第三方库 node_modules 人家帮我们配置好了。
我们通过 import 导入第三方库,在第三方库的 package.json 文件中确定了我们引入的是哪个文件
通过 module 确定了我们要引入的文件。
详解main.js render 函数
之前的写法是这样:
import App from ‘./App.vue’
new Vue({
el:‘#root’,
template:<App></App>
,
components:{App},
})
如果这样子写,运行的话会引发如下的报错
报错的意思是,是在使用运行版本的 vue ,没有模板解析器。
从上面的小知识可以知道,我们引入的 vue 不是完整版的,是残缺的(为了减小vue的大小)。所以残缺的vue.js 只有通过 render 函数才能把项目给跑起来。
来解析一下render
// render最原始写的方式
// render是个函数,还能接收到参数a
// 这个 createElement 很关键,是个回调函数
new Vue({
render(createElement) {
console.log(typeof createElement);
// 这个 createElement 回调函数能创建元素
// 因为残缺的vue 不能解析 template,所以render就来帮忙解决这个问题
// createElement 能创建具体的元素
return createElement('h1', 'hello')
}
}).$mount('#app')
因为 render 函数内并没有用到 this,所以可以简写成箭头函数。
new Vue({
// render: h => h(App),
render: (createElement) => {
return createElement(App)
}
}).$mount('#app')
再简写:
new Vue({
// render: h => h(App),
render: createElement => createElement(App)
}).$mount('#app')
最后把 createElement 换成 h 就完事了。
来个不同版本 vue 的区别
vue.js与vue.runtime.xxx.js的区别:
- vue.js是完整版的Vue,包含:核心功能+模板解析器。
- vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。
- 因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的createElement函数去指定具体内容
修改脚手架的默认配置
- 使用vue inspect > output.js可以查看到Vue脚手架的默认配置。//此时修改不起作用
- 使用vue.config.js可以对脚手架进行个性化定制
vue组件的一些属性
ref属性
- 被用来给元素或子组件注册引用信息(id的替代者)
- 应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
- 使用方式:
- 打标识:
- < h1 ref=“xxx”>…或 < School ref=“xxx”>
- 获取:this.$refs.xxx //this此时是vc app的组件实例对象
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
<MySchool></MySchool>
<MyStudent ref="stu"></MyStudent>
<button @click="showDom">点击点击</button>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
import MyStudent from './components/MyStudent.vue'
import MySchool from './components/MySchool.vue'
export default {
name: 'App',
components: {
HelloWorld,
MyStudent,
MySchool
},
methods: {
showDom(){
console.log(document.querySelector('img'));
console.log(this.$refs.stu);// 此时获取到的是School组件的实例对象(vc)
}
},
}
</script>
props属性
-
功能:让组件接收外部传过来的数据
-
传递数据:< Demo name=“xxx”/>
-
接收数据:
1.第一种方式(只接收):props:[‘name’]
2.第二种方式(限制类型):props:{name:String}
3.第三种方式(限制类型、限制必要性、指定默认值):
props:{
name:{
type:String, //类型
required:true, //必要性
default:'老王' //默认值
}
}
备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。
mixin属性 混入
- 本质是一个对象 可以定义多个混入 引入方式是数组
- 混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。
mixin.js
//混合
//两个组件的某个方法属性一样,可以使用混合共同为组件添加属性方法
export default {
methods: {
showDom() {
// console.log(document.querySelector('img'));
// console.log(this);
// console.log(this.$refs.stu);
alert(this.name);
}
}
}
引入
在需要引入的组件配置 mixins:[ ] //可以是多个混入
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
<MySchool ></MySchool>
<hr>
<MyStudent ref="stu"></MyStudent>
<button @click="showDom">点击点击</button>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
import MyStudent from './components/MyStudent.vue'
import MySchool from './components/MySchool.vue'
import mixins from './mixin'
export default {
name: 'App',
components: {
HelloWorld,
MyStudent,
MySchool
},
data() {
return {
name:'aPP'
}
},
mixins:[mixins]//引入一个混入
}
</script>
选项合并
当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”。
比如,数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先。
此时引入minixs 如果没有 x y ,则添加属性 x y 若有,以组件的顺序优先.
export default {
data() {
return {
x: 100,
y: 200
}
},
methods: {
showDom() {
// console.log(document.querySelector('img'));
// console.log(this);
// console.log(this.$refs.stu);
alert(this.name);
}
}
}
Plugins 插件
本质是一个对象 必须包括install()
插件通常用来为 Vue 添加全局功能。插件的功能范围没有严格的限制
通过全局方法 Vue.use() 使用插件。它需要在你调用 new Vue() 启动应用之前完成:
- plugins.js
export default {
install(Vue) {
//console.log('plugins');
//console.log(vue);
//定义一个全局过滤器
Vue.filter('my-filter', function(value) {
return value.slice(0, 4);
})
//在Vue身上定义一个方法
Vue.prototype.hello = function() {
alert('你好啊啊啊啊啊')
}
}
}
- main.js
//引入Vue组件
import Vue from 'vue'
//引入App组件 App组件是所有组件的父组件
import App from './App.vue'
import plugins from './plugins'
Vue.use(plugins)
//配置Vue
Vue.config.productionTip = false
//创建一个vm,并选择id=app为容器
new Vue({
render: h => h(App),
}).$mount('#app')
scoped属性
- 作用:让样式在局部生效,防止冲突。
- 写法:< style scoped>
- 为 css限定一个作用范围,因为所有的样式最后都会汇总到一起,防止命名冲突