前端必备框架--vue
- 开始使用Vue
- vue的响应式-1
- vue的响应式-2
- 扩展_剖析Vue响应式原理
- Vue相关指令
- 条件渲染
- v-bind 指令
- v-on指令
- v-on指令的修饰符
- 列表渲染
- v-model指令
- 计算属性
- 侦听器
- vue-resource
- Axios
- template 选项
- Vue生命周期
- 组件_Prop
- 组件_Prop验证
- 组件_单向数据流
- 组件_非Prop特性
- 组件_监听组件事件
- 组件_插槽
- 组件_动态组件
- 组件_处理边界情况
- 组件_通信
- 混入
- 自定义指令
- 过滤器
- 安装脚手架
- 练习_树形组件
- 利用脚手架搭建项目
- 渲染函数
- JSX
- 函数式组件
- 过渡_单元素过渡
- 过渡_多元素过渡
- 过渡_列表过渡
- 过渡_复用过渡
- 组件_异步组件
- VueRouter_基础
开始使用Vue
-
引入vue.js
官网:vuejs.org
开发版本:包含完整的警告和调试模式
生产版本:删除了警告,体积更小
-
引入vue.js后,给我们提供了一个构造函数 Vue
-
在js中,
new Vue()
-
new Vue()
后会返回一个vue实例对象,我们用变量接着它 -
const vm = new Vue()
-
传递一个配置对象{} -- >
const vm = new Vue({})
el
类型: 字符串
全称:element(元素)
作用:配置控制的元素,表示Vue要控制的区域,值为css选择器
<!-- 被Vue控制的区域,我们称之为模板 -->
<div id="app"></div>
const vm = new Vue({
el: '#app' // 控制id为app的元素
})
$mount
- 作用和el一致,都是配置控制的元素,使用哪个都可以,二选一
<div id="app"></div>
const vm = new Vue({}) vm.$mount('#app');
- 问:和el有什么不同?
答:本质上没什么不同,$mount为手动挂载,在项目中有时要进行延迟挂载,比如有时要在挂载之前进行一些其他的操作,比如判断等等(但是,这样做的时候很少,emmmmm)
data
- 类型:对象
- 作用:存放要用到的数据,数据为响应式的
const vm = new Vue({ el: '#app', data: { 'mrDeng': '风姿绰约、花枝招展' } })
插值表达式
-
使用方法: {{ }}
-
可以将vue中的数据填在插值表达式中,如:
<div id="app">{{ mrDeng }}</div>
const vm = new Vue({ el: '#app', data: { mrDeng: '哥:风姿绰约、花枝招展' } })
-
除了填写data之外,还可以直接填写数据值(数字、字符串、布尔值、undefined、null、数组、对象),如:
<div id="app"> {{ 5201314 }} {{ '婀娜多姿、亭亭玉立' }} {{ true }} {{ ['邓明', '小刘', '王小宝'] }} {{ {name: '邓明', age: 80, height: '140cm', weight: '100kg'} }} </div>
-
注意:在插值表达式中直接书写对象类型值时,不要将三个{}连在一起,这样会报错,如:
<div id="app"> <!-- 这样可是不行滴 --> {{{name: '邓明', age: 80, height: '140cm', weight: '100kg'}}} </div>
-
还可在插值表达式中写表达式,如:
<div id="app"> <!-- 运算表达式 --> {{ 'you' + 'me' }} {{ 10 - 5 }} {{ 100 * 7 }} {{ 1000 / 12 }} <!-- 逻辑表达式 --> {{ liu || li }} {{ deng && liu }} {{ !wang }} <!-- 三元表达式 --> {{ 1 + 1 === 3 ? '邓明' : '正常人' }} <!-- 函数调用也是表达式,也可以使用,这个以后再学哈... --> </div>
-
还可以填写其他的吗?不可以,No,以下这些都是不行滴:
<div id="app"> <!-- 这是语句,不可以写在插值表达式中 --> {{ var Deng = 'shuaige'; console.log(deng) }} <!-- 流程控制也不可以 --> {{ if(Deng.looks === 'shuai'){ console.log('不可能')} }} </div>
-
记住:插值表达式中,可以写:data、js数据、表达式,其他的想都不要想。
-
注意,只要插值表达式中使用了数据,必须在data中声明过,否则会报错
<!-- 此时就报错啦,因为mrCheng,未在data中声明过 --> <div id="app"> {{ mrCheng }} </div>
const vm = new Vue({ el: '#app', data: { mrDeng: '哥:风姿绰约、花枝招展' } })
-
还有另外一种可能,使用了未被声明过的数据,不报错:
<!-- 此时不报错啦,why? --> <!-- 在作用域上找不到,报错 --> <!-- 在原型链上找不到,值为undefined --> <!-- undefined为js基本类型值,所以就不报错啦 --> <div id="app"> {{ mrDeng.wife }} </div>
const vm = new Vue({ el: '#app', data: { mrDeng: { name: '邓明', age: 80, height: '140cm', weight: '100kg' } } })
vue的响应式-1
-
数据变化,页面就会重新渲染
-
怎么更改数据?so easy
<div id="app">
{{ mrDeng }}
</div>
const vm = new Vue({
el: '#app',
data: {
mrDeng: '风姿绰约、花枝招展'
}
});
vm.mrDeng = '手如柔荑、肤如凝脂'; // 见证奇迹的时刻,页面变化啦
- 问:为什么data会直接出现在vm实例对象中咧?
答:当创建vue实例时,vue会将data中的成员代理给vue实例,目的是为了实现响应式,监控数据变化,执行某个监听函数(提示:Object.defineProperty,试着实现一下)
- 问:实例中除了data数据外,其他东西是啥子?
为了防止名称冲突。因为会将data中数据代理给vue,假如说我们自己写的data名称和vue中自带的属性冲突了,那么就会覆盖vue内部的属性,所以vue会把自己内部的属性成员名称前加上$或_,如果加上的是$,代表是我们可以使用的,如果加上的是_,是vue自己内部使用的方法或属性,我们不需要调用
- 更改的数据必须是存在的数据,否则不能重新渲染页面,因为他监听不到,如:
<!-- 即使更改了数据,也不会重新渲染页面 -->
<div id="app">
{{ mrDeng.wife }}
</div>
const vm = new Vue({
el: '#app',
data: {
mrDeng: {
name: '邓明',
age: 80,
height: '140cm',
weight: '100kg'
}
}
})
vm.mrDeng.wife = 'liu';
- 更改的数据必须已渲染过的数据,否则从性能角度考虑,不会重新渲染页面,如:
<!-- 即使更改了数据,也不会重新渲染页面 -->
<div id="app">
{{ mrDeng.wife }}
</div>
const vm = new Vue({
el: '#app',
data: {
msg: '风姿绰约、花枝招展',
mrDeng: {
name: '邓明',
age: 80,
height: '140cm',
weight: '100kg'
}
}
})
vm.mrDeng.wife = 'liu';
vm.msg = '手如柔荑、肤如凝脂';
- 更改数据后,页面会立刻重新渲染吗?
vue更新DOM的操作是异步执行的,只要侦听到数据变化,将开启一个异步队列,如果一个数据被多次变更,那么只会被推入到队列中一次,这样可以避免不必要的计算和DOM操作。
同步执行栈执行完毕后,会执行异步队列
<div id="app">{{ msg }}</div>
const vm = new Vue({
el: '#app',
data: {
msg: '杉杉'
}
})
vm.msg = '杉杉超美的';
console.log(vm.msg); // 杉杉超美的,此时数据已更改
console.log(vm.$el.innerHTML); // 杉杉。此时页面还未重新渲染
vm.$el
- 值为被Vue控制的元素(或者说,Vue挂载的元素)
vm.$nextTick & Vue.nextTick
- 如何在更改数据后,看到渲染后的页面上的值?
答:利用vm.$nextTick或Vue.nextTick,在页面重新渲染,DOM更新后,会立刻执行vm.$nextTick
<div id="app">{{ msg }}</div>
const vm = new Vue({
el: '#app',
data: {
msg: '杉杉'
}
})
vm.msg = '杉杉超美的';
console.log(vm.msg); // 杉杉超美的,此时数据已更改
// 1. 使用vm.$nextTick
vm.$nextTick(() => {
console.log(vm.$el.innerHTML); // 杉杉超美的
})
// 2. 使用Vue.nextTick
Vue.nextTick(() => {
console.log(vm.$el.innerHTML); // 杉杉超美的
})
- vm.nextTick和Vue.nextTick还可以作为Promise使用
<div id="app">{{ msg }}</div>
const vm = new Vue({
el: '#app',
data: {
msg: '杉杉'
}
})
vm.msg = '杉杉超美的';
// 1. 使用vm.$nextTick
vm.$nextTick().then(() => {
console.log(vm.$el.innerHTML); // 杉杉超美的
})
// 2. 使用Vue.nextTick
Vue.nextTick().then(() => {
console.log(vm.$el.innerHTML); // 杉杉超美的
})
- vm.$nextTick 和 Vue.nextTick的区别?
Vue.nextTick内部函数的this指向window
Vue.nextTick(function () {
console.log(this); // window
})
vm.$nextTick内部函数的this指向Vue实例对象
vm.$nextTick(function () {
console.log(this); // vm实例
})
- 好奇nextTick是怎么实现的吗?
- 异步任务分为宏任务(macro)和微任务(micro)
- 宏任务比较慢(如setTimeout等),微任务比较快(如Promise.then()等)
- 微任务在前,宏任务在后(eventloop,事件环)
// 控制台打印顺序:promise > timeout setTimeout(() => { console.log('timeout'); }, 0) Promise.resolve().then(() => { console.log('promise'); })
- 在nextTick的实现源码中,会先判断是否支持微任务,不支持后,才会执行宏任务
if(typeof Promise !== 'undefined') { // 微任务 // 首先看一下浏览器中有没有promise // 因为IE浏览器中不能执行Promise const p = Promise.resolve(); } else if(typeof MutationObserver !== 'undefined') { // 微任务 // 突变观察 // 监听文档中文字的变化,如果文字有变化,就会执行回调 // vue的具体做法是:创建一个假节点,然后让这个假节点稍微改动一下,就会执行对应的函数 } else if(typeof setImmediate !== 'undefined') { // 宏任务 // 只在IE下有 } else { // 宏任务 // 如果上面都不能执行,那么则会调用setTimeout }
- 曾经vue用过的宏任务
- MessageChannel 消息通道 宏任务
vue的响应式-2
-
除了未被声明过和未被渲染的数据外,还有什么数据更改后不会渲染页面?
1. 利用索引直接设置一个数组项时:
<!-- 即使向数组中添加了第4项,数组仍然显示3项 --> <!-- 咳咳,一家三口,有第4个人也不能摆出来给大家看呀~ --> <div id="app">{{ list }}</div>
const vm = new Vue({ el: '#app' data: { dengFamily: ['邓哥', '小刘', '王小宝'] } }) vm.dengFamily[3] = '铁锤妹妹'; // 不是响应式的
2. 修改数组的长度时:
<!-- 更改了数组长度后,数组仍然显示1项 --> <div id="app">{{ list }}</div>
const vm = new Vue({ el: '#app' data: { dengWife: ['小刘'] } }) vm.dengWife.length = 0; // 不是响应式的
3. 添加或删除对象:
<!-- 身高还是那个身高,媳妇也只有一个,不要痴心妄想 --> <div id="app">{{ deng }}</div>
const vm = new Vue({ el: '#app' data: { deng: { wife: '小刘', son: '王小宝', weight: '100kg', height: '140cm', age: 60 } } }) vm.deng.secondWife = '铁锤妹妹'; // 不是响应式的 delete vm.deng.height; // 不是响应式的
-
问:要如何响应式的更新数组和对象?
更改数组:
1. 利用数组变异方法:push、pop、shift、unshift、splice、sort、reverse
2. 利用vm.$set/Vue.set实例方法
3. 利用vm.$set或Vue.set删除数组中的某一项vm.$set是Vue.set的别名
使用方法:Vue.set(object, propertyName, value),也就是这个意思:Vue.set(要改谁,改它的什么,改成啥)vm.$delete是Vue.delete的别名
使用方法:Vue.delete(object, target),也就是这个意思:Vue.delete(要删除谁的值,删除哪个)<!-- 从此,一家三口过上了愉快生活 --> <div id="app">{{ list }}</div>
const vm = new Vue({ el: '#app' data: { dengFamily: ['邓哥', '小刘', '王小宝'] } }) // 使用数组变异方法 vm.dengFamily.push('铁锤妹妹'); // 使用vm.$set vm.$set(vm.dengFamily, 3, '铁锤妹妹');
<div id="app">{{ list }}</div>
const vm = new Vue({ el: '#app' data: { dengWife: ['小刘'] } }) // 更改长度时,可以用数组的splice方法 vm.dengWife.splice(100);
更改对象:
1. 添加利用vm.$set/Vue.set实例方法
2. 删除利用vm.$delete/Vue.delete方法<div id="app">{{ deng }}</div>
const vm = new Vue({ el: '#app' data: { deng: { wife: '小刘', son: '王小宝', weight: '100kg', height: '140cm', age: 60 } } }) // 添加 vm.$set(vm.deng, 'secondWife', '铁锤妹妹'); // 删除 vm.$delete(vm.deng, 'height')
-
总结:
更改数组用变异方法,就够了
更改对象就用vm.$set和vm.$delete -
问题解决了,但是为什么会这样呢?
Object.defineProperty的锅
扩展_剖析Vue响应式原理
const data = {
name: 'shanshan',
age: 18,
shan: {
name: 'shanshan',
age: 18,
obj: {}
},
arr: [1, 2, 3]
}
const arrayProto = Array.prototype;
const arrayMethods = Object.create(arrayProto);
['push', 'pop', 'shift', 'unshift' ,'sort', 'splice', 'reverse'].forEach(method => {
arrayMethods[method] = function () {
arrayProto[method].call(this, ...arguments);
render();
}
})
function defineReactive (data, key, value) {
observer(value);
Object.defineProperty(data, key, {
get () {
return value;
},
set (newVal) {
if(value === newVal) {
return;
}
value = newVal;
render();
}
})
}
function observer (data) {
if(Array.isArray(data)) {
data.__proto__ = arrayMethods;
return;
}
if(typeof data === 'object') {
for(let key in data) {
defineReactive(data, key, data[key])
}
}
}
function render () {
console.log('页面渲染啦');
}
function $set (data, key, value) {
if(Array.isArray(data)) {
data.splice(key, 1, value);
return value;
}
defineReactive(data, key, value);
render();
return value;
}
function $delete(data, key) {
if(Array.isArray(data)