组件的data必须是一个函数

官方解释

在Vue当中,组件其实就是可复用的Vue实例,如果说直接返回一个data,组件之间就会相互影响,
通过函数去返回一个对象的副本,起到了相互隔离的效果。

其他场景,比如jQuerycallbacks返回了一个self对象

  1. var callbacks = function () {
  2. var self = {}
  3. return self;
  4. }
  5. callbacks() == callbacks() // false
  6. // callbacks 有add和fire方法

源码里面的判断
image.png

Vue当中,怎么去区分是根实例,还是组件

源码当中,通过vm去判断,没有就是组件,组件的vNode没有children属性

响应式原理

三步骤

  • 数据监听 observe
  • 依赖收集 watcher
  • 触发更新 patch

Vue2

Object.defineProperty
关于Object.defineProperty的说法

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。

  1. /**
  2. * [target] 要在其上定义属性的对象。
  3. * [key]要定义或修改的属性的名称。
  4. * [descriptor]将被定义或修改的属性描述符。
  5. */
  6. // ES5语法,不支持IE8及以下版本
  7. Object.defineProperty(target, key, {
  8. writable: false, // 默认 是否可修改
  9. configurable: false, // 默认 是否可被del
  10. enumerable: false, // 默认 是否可遍历
  11. set() {
  12. // 默认:undefined
  13. },
  14. get() {
  15. // 默认:undefined
  16. }
  17. })
  • 基础使用
  1. let obj = {
  2. name: 'vue'
  3. };
  4. // 通过中间变量去get和set
  5. let value = obj.name;
  6. Object.defineProperty(obj, 'name', {
  7. enumerable: true, // 可遍历
  8. configurable: true, // 可删除
  9. get() {
  10. // 此处省略收集依赖
  11. // 收集对应的变量再哪些地方用到了
  12. // 响应式系统使用响应式数据的getter方法对观察者进行依赖收集(Collect as Dependency)
  13. return value;
  14. },
  15. set(newValue) {
  16. value = newValue;
  17. // 省略了触发依赖,
  18. // 读取视图模板,生产语法树
  19. // 使用响应式数据的setter方法通知(notify)所有观察者进行更新,此时观察者 Watcher 会触发组件的渲染函数(Trigger re-render),
  20. // 组件执行的 render 函数,生成一个新的 Virtual DOM Tree,此时 Vue 会对新老 Virtual DOM Tree 进行 Diff,查找出需要操作的真实 DOM 并对其进行更新。
  21. that.render(newValue);
  22. }
  23. });

不足:只能监听一个属性,并且要通过中间变量

  • 遍历对象的属性
  1. // observe观察数据
  2. Vue.prototype.observe = function (data) {
  3. if (!data || typeof data !== 'object') {
  4. return false;
  5. }
  6. // 遍历data,将原来所有属性改成set和get的形式
  7. // 先获取到数据的key和value
  8. Object.keys(data).forEach((key) => {
  9. if (typeof data[key] === 'object') {
  10. // 如果是对象,则继续去遍历他的属性
  11. // data[key]充当一个中间变量
  12. this.observe(data[key]);
  13. } else {
  14. this.defineReactive(data, key, data[key]);
  15. }
  16. });
  17. };
  18. // 添加数据监听
  19. // 由于Object.defineProperty只能作用于Object,
  20. // 所以数组的监听,使用了伪装者模式
  21. Vue.prototype.defineReactive = function (target, key, value) {
  22. let that = this;
  23. // ES5语法,不支持IE8及以下版本
  24. Object.defineProperty(target, key, {
  25. enumerable: true, // 可遍历
  26. configurable: true, // 可删除
  27. get() {
  28. // 此处省略收集依赖
  29. // 收集对应的变量再哪些地方用到了
  30. console.log('get', value);
  31. return value;
  32. },
  33. set(newValue) {
  34. console.log('set', newValue);
  35. value = newValue;
  36. // 数据改变,触发dom渲染
  37. // 触发收集依赖后的更新
  38. that.render(newValue);
  39. }
  40. });
  41. };
  • 数组的监听实现

使用装饰者模式

  1. // 拿出数组原型链并拷贝
  2. var arrayPro = Array.prototype;
  3. var arrayOb = Object.create(arrayPro);
  4. // 去重写以下的方法
  5. var arrFun = ['push', 'pull', 'shift'];
  6. arrFun.forEach((methods, index) => {
  7. arrayOb[methods] = function() {
  8. // 执行对应的数组操作,并执行视图的更新
  9. var ret = arrayPro[method].apply(this, arguments);
  10. // 触发视图更新
  11. dep.notify();
  12. return ret;
  13. }
  14. });

Vue3

Proxy

  • 基础使用
  1. let obj = {
  2. name: 'vue'
  3. }
  4. // 相对于vue2省去了一个for in循环
  5. // 不用去污染源对象
  6. // 写法更优雅了
  7. obj = new Proxy(obj, {
  8. get: function(target, key, receiver) {
  9. // receiver:代理对象
  10. console.log(arguments);
  11. return target[key];
  12. },
  13. set: function(target, key, value, receiver) {
  14. console.log(arguments);
  15. // 触发视图更新
  16. dep.notify();
  17. return Reflect.set(target, key, value);
  18. }
  19. })
  20. obj.name = 'proxy';
  21. console.log(obj.name);
  • Proxy的应用
  1. 类型校验
  2. 私有变量
  3. observe

  1. var data = {
  2. key: 'value',
  3. }
  4. for (var key in data) {
  5. Object.defineProperty(data, key, {
  6. get: function () {
  7. },
  8. set: function () {
  9. }
  10. })
  11. }

watcher

patch

组件传值

  • props
  • $emit,$on
  • $children,$parent,$refs
  • eventBus(公共的Vue实例)
  • Vuex(全局的状态管理)

嵌套组件的生命周期

总共有八大生命周期,依次是:

  1. beforeCreate
  2. created
  3. beforeMount
  4. mounted
  5. beforeDestory
  6. destoryed
  7. beforeUpdate
  8. updated

子组件先插入,先完成patch及insert,父组件后插入

father beforeCreate
father created
father beforeMount
child beforeCreate
child created
child beforeMount
child mounted
father mounted

修改不存在的属性,或者修改数组的下标

  1. this.$set(target, key, value);
  2. // Object
  3. this.$set(obj, 'key', 'value');
  4. // Array
  5. this.$set(arr, 'index', 'value');

Diff算法

v-model原理

  • 数据双向绑定v-model的实现原理

模板编译
image.png

computed与watch的区别

nextTick的使用

Vue多页面

v-if与v-show的区别,及源码中v-if的实现

Vue组件库的二次封装

实现一个自动loading的按钮。

attrs属性介绍

vm-attrs

包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind=”$attrs” 传入内部组件——在创建高级别的组件时非常有用。

组件代码

通过 v-bind="$attrs",去继承父组件传过来的属性。
或者通过 $props 去接受所有的参数。

  1. <!--
  2. * @Name: MuButton.vue
  3. * @Author: forguo
  4. * @Date: 2021/9/23 20:46
  5. * @Description: MuButton
  6. -->
  7. <template>
  8. <Button :loading="loadingStatus" v-bind="$attrs" @click="handleClick"><slot /></Button>
  9. </template>
  10. <script>
  11. import { Button } from 'vant';
  12. export default {
  13. name: "MuButton",
  14. /**
  15. props: {
  16. ...Button.props,
  17. autoLoading: {
  18. type: Boolean,
  19. default: false
  20. }
  21. },
  22. */
  23. props: {
  24. autoLoading: {
  25. type: Boolean,
  26. default: false
  27. }
  28. },
  29. data () {
  30. return {
  31. loadingStatus: false,
  32. }
  33. },
  34. components: {
  35. Button
  36. },
  37. methods: {
  38. handleClick() {
  39. if (this.autoLoading) {
  40. // 判断是否开启自动loading
  41. this.loadingStatus = true;
  42. }
  43. // 点击的回调
  44. this.$emit('click', () => {
  45. // 在异步完成之后,去除loading
  46. this.loadingStatus = false;
  47. })
  48. }
  49. }
  50. }
  51. </script>
  52. <style scoped>
  53. </style>

页面中使用

  1. <template>
  2. <div class="home">
  3. <MyButton type="primary" round :autoLoading="true" :loading-text="'提交中...'" @click="handleClick">提交</MyButton>
  4. </div>
  5. </template>
  6. <script>
  7. import { mapState } from 'vuex';
  8. import MyButton from '@/components/MyButton';
  9. export default {
  10. name: "Home",
  11. data() {
  12. return {
  13. }
  14. },
  15. computed: {
  16. ...mapState('router', [
  17. 'routers'
  18. ])
  19. },
  20. components: {
  21. MyButton
  22. },
  23. methods: {
  24. handleClick (done) {
  25. setTimeout(() => {
  26. // 执行该回调,去关闭loading
  27. done();
  28. }, 1500)
  29. }
  30. }
  31. }
  32. </script>

Vue和React的区别

Vue2与Vue3的区别