组件的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, // 默认 是否可被delenumerable: false, // 默认 是否可遍历set() {// 默认:undefined},get() {// 默认:undefined}})
- 基础使用
let obj = {name: 'vue'};// 通过中间变量去get和setlet 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和valueObject.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(全局的状态管理)
嵌套组件的生命周期
总共有八大生命周期,依次是:
beforeCreatecreatedbeforeMountmountedbeforeDestorydestoryedbeforeUpdateupdated
子组件先插入,先完成patch及insert,父组件后插入
father beforeCreate
father created
father beforeMount
child beforeCreate
child created
child beforeMount
child mounted
father mounted
修改不存在的属性,或者修改数组的下标
this.$set(target, key, value);// Objectthis.$set(obj, 'key', 'value');// Arraythis.$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) {// 判断是否开启自动loadingthis.loadingStatus = true;}// 点击的回调this.$emit('click', () => {// 在异步完成之后,去除loadingthis.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(() => {// 执行该回调,去关闭loadingdone();}, 1500)}}}</script>
