组件的data必须是一个函数
在Vue当中,组件其实就是可复用的Vue实例,如果说直接返回一个data,组件之间就会相互影响,
通过函数去返回一个对象的副本,起到了相互隔离的效果。
其他场景,比如jQuery
的callbacks
返回了一个self
对象
var callbacks = function () {
var self = {}
return self;
}
callbacks() == callbacks() // false
// callbacks 有add和fire方法
源码里面的判断
Vue当中,怎么去区分是根实例,还是组件
源码当中,通过vm
去判断,没有就是组件,组件的vNode没有children属性
响应式原理
三步骤
- 数据监听 observe
- 依赖收集 watcher
- 触发更新 patch
Vue2
Object.defineProperty
关于Object.defineProperty
的说法
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
/**
* [target] 要在其上定义属性的对象。
* [key]要定义或修改的属性的名称。
* [descriptor]将被定义或修改的属性描述符。
*/
// ES5语法,不支持IE8及以下版本
Object.defineProperty(target, key, {
writable: false, // 默认 是否可修改
configurable: false, // 默认 是否可被del
enumerable: false, // 默认 是否可遍历
set() {
// 默认:undefined
},
get() {
// 默认:undefined
}
})
- 基础使用
let obj = {
name: 'vue'
};
// 通过中间变量去get和set
let value = obj.name;
Object.defineProperty(obj, 'name', {
enumerable: true, // 可遍历
configurable: true, // 可删除
get() {
// 此处省略收集依赖
// 收集对应的变量再哪些地方用到了
// 响应式系统使用响应式数据的getter方法对观察者进行依赖收集(Collect as Dependency)
return value;
},
set(newValue) {
value = newValue;
// 省略了触发依赖,
// 读取视图模板,生产语法树
// 使用响应式数据的setter方法通知(notify)所有观察者进行更新,此时观察者 Watcher 会触发组件的渲染函数(Trigger re-render),
// 组件执行的 render 函数,生成一个新的 Virtual DOM Tree,此时 Vue 会对新老 Virtual DOM Tree 进行 Diff,查找出需要操作的真实 DOM 并对其进行更新。
that.render(newValue);
}
});
不足:只能监听一个属性,并且要通过中间变量
- 遍历对象的属性
// observe观察数据
Vue.prototype.observe = function (data) {
if (!data || typeof data !== 'object') {
return false;
}
// 遍历data,将原来所有属性改成set和get的形式
// 先获取到数据的key和value
Object.keys(data).forEach((key) => {
if (typeof data[key] === 'object') {
// 如果是对象,则继续去遍历他的属性
// data[key]充当一个中间变量
this.observe(data[key]);
} else {
this.defineReactive(data, key, data[key]);
}
});
};
// 添加数据监听
// 由于Object.defineProperty只能作用于Object,
// 所以数组的监听,使用了伪装者模式
Vue.prototype.defineReactive = function (target, key, value) {
let that = this;
// ES5语法,不支持IE8及以下版本
Object.defineProperty(target, key, {
enumerable: true, // 可遍历
configurable: true, // 可删除
get() {
// 此处省略收集依赖
// 收集对应的变量再哪些地方用到了
console.log('get', value);
return value;
},
set(newValue) {
console.log('set', newValue);
value = newValue;
// 数据改变,触发dom渲染
// 触发收集依赖后的更新
that.render(newValue);
}
});
};
- 数组的监听实现
使用装饰者模式
// 拿出数组原型链并拷贝
var arrayPro = Array.prototype;
var arrayOb = Object.create(arrayPro);
// 去重写以下的方法
var arrFun = ['push', 'pull', 'shift'];
arrFun.forEach((methods, index) => {
arrayOb[methods] = function() {
// 执行对应的数组操作,并执行视图的更新
var ret = arrayPro[method].apply(this, arguments);
// 触发视图更新
dep.notify();
return ret;
}
});
Vue3
Proxy
- 基础使用
let obj = {
name: 'vue'
}
// 相对于vue2省去了一个for in循环
// 不用去污染源对象
// 写法更优雅了
obj = new Proxy(obj, {
get: function(target, key, receiver) {
// receiver:代理对象
console.log(arguments);
return target[key];
},
set: function(target, key, value, receiver) {
console.log(arguments);
// 触发视图更新
dep.notify();
return Reflect.set(target, key, value);
}
})
obj.name = 'proxy';
console.log(obj.name);
- Proxy的应用
var data = {
key: 'value',
}
for (var key in data) {
Object.defineProperty(data, key, {
get: function () {
},
set: function () {
}
})
}
watcher
patch
组件传值
- props
- $emit,$on
- $children,$parent,$refs
- eventBus(公共的Vue实例)
- Vuex(全局的状态管理)
嵌套组件的生命周期
总共有八大生命周期,依次是:
beforeCreate
created
beforeMount
mounted
beforeDestory
destoryed
beforeUpdate
updated
子组件先插入,先完成patch及insert,父组件后插入
father beforeCreate
father created
father beforeMount
child beforeCreate
child created
child beforeMount
child mounted
father mounted
修改不存在的属性,或者修改数组的下标
this.$set(target, key, value);
// Object
this.$set(obj, 'key', 'value');
// Array
this.$set(arr, 'index', 'value');
Diff算法
v-model原理
- 数据双向绑定v-model的实现原理
computed与watch的区别
nextTick的使用
Vue多页面
v-if与v-show的区别,及源码中v-if的实现
Vue组件库的二次封装
实现一个自动loading的按钮。
attrs属性介绍
包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind=”$attrs” 传入内部组件——在创建高级别的组件时非常有用。
组件代码
通过 v-bind="$attrs"
,去继承父组件传过来的属性。
或者通过 $props
去接受所有的参数。
<!--
* @Name: MuButton.vue
* @Author: forguo
* @Date: 2021/9/23 20:46
* @Description: MuButton
-->
<template>
<Button :loading="loadingStatus" v-bind="$attrs" @click="handleClick"><slot /></Button>
</template>
<script>
import { Button } from 'vant';
export default {
name: "MuButton",
/**
props: {
...Button.props,
autoLoading: {
type: Boolean,
default: false
}
},
*/
props: {
autoLoading: {
type: Boolean,
default: false
}
},
data () {
return {
loadingStatus: false,
}
},
components: {
Button
},
methods: {
handleClick() {
if (this.autoLoading) {
// 判断是否开启自动loading
this.loadingStatus = true;
}
// 点击的回调
this.$emit('click', () => {
// 在异步完成之后,去除loading
this.loadingStatus = false;
})
}
}
}
</script>
<style scoped>
</style>
页面中使用
<template>
<div class="home">
<MyButton type="primary" round :autoLoading="true" :loading-text="'提交中...'" @click="handleClick">提交</MyButton>
</div>
</template>
<script>
import { mapState } from 'vuex';
import MyButton from '@/components/MyButton';
export default {
name: "Home",
data() {
return {
}
},
computed: {
...mapState('router', [
'routers'
])
},
components: {
MyButton
},
methods: {
handleClick (done) {
setTimeout(() => {
// 执行该回调,去关闭loading
done();
}, 1500)
}
}
}
</script>