chst365's blog chst365's blog
首页
  • Git
  • 网络
  • 操作系统
  • 浏览器
  • webpack
  • JavaScript
  • TypeScript
  • 性能
  • 工程化
  • React
  • 编程题
  • React技术揭秘
  • 算法
  • Node
  • 编码解码
  • NodeJS系列
  • Linux系列
  • JavaScript系列
  • HTTP系列
  • GIT系列
  • ES6系列
  • 设计模式系列
  • CSS系列
  • 小程序系列
  • 数据结构与算法系列
  • React系列
  • Vue3系列
  • Vue系列
  • TypeScript系列
  • Webpack系列
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

chst365

DIV工程师
首页
  • Git
  • 网络
  • 操作系统
  • 浏览器
  • webpack
  • JavaScript
  • TypeScript
  • 性能
  • 工程化
  • React
  • 编程题
  • React技术揭秘
  • 算法
  • Node
  • 编码解码
  • NodeJS系列
  • Linux系列
  • JavaScript系列
  • HTTP系列
  • GIT系列
  • ES6系列
  • 设计模式系列
  • CSS系列
  • 小程序系列
  • 数据结构与算法系列
  • React系列
  • Vue3系列
  • Vue系列
  • TypeScript系列
  • Webpack系列
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • NodeJS系列

  • Linux系列

  • JavaScript系列

  • HTTP系列

  • GIT系列

  • ES6系列

  • 设计模式系列

  • CSS系列

  • 小程序系列

  • 数据结构与算法系列

  • React系列

  • Vue3系列

  • Vue系列

    • vue项目本地开发完成后部署到服务器后报404是什么原因呢?
    • Vue项目中有封装过axios吗?主要是封装哪方面的?
    • 你了解axios的原理吗?有看过它的源码吗?
    • 双向数据绑定是什么
    • Vue组件之间的通信方式都有哪些?
    • Vue中组件和插件有什么区别?
    • Vue项目中你是如何解决跨域的呢?
    • 为什么data属性是一个函数而不是一个对象?
    • 动态给vue的data添加一个新的属性时会发生什么?怎样解决?
    • 你了解vue的diff算法吗?说说看
    • 你有写过自定义指令吗?自定义指令的应用场景有哪些?
    • 你是怎么处理vue项目中的错误的?
    • Vue中的过滤器了解吗?过滤器的应用场景有哪些?
    • SPA首屏加载速度慢的怎么解决?
    • v-if和v-for的优先级是什么?
    • 说说你对keep-alive的理解是什么?
    • 你知道vue中key的原理吗?说说你对它的理解
    • 请描述下你对vue生命周期的理解?在created和mounted这两个生命周期中请求数据有什么区别呢?
    • 说说你对vue的mixin的理解,有什么应用场景?
      • 一、mixin是什么
        • Vue中的mixin
        • 局部混入
        • 全局混入
        • 注意事项:
      • 二、使用场景
      • 三、源码分析
        • 替换型
        • 合并型
        • 队列性
        • 叠加型
        • 小结:
      • 参考文献
    • Vue常用的修饰符有哪些有什么应用场景
    • Vue实例挂载的过程
    • Vue中的$nextTick有什么作用?
    • Vue.observable你有了解过吗?说说看
    • vue要做权限管理该怎么做?如果控制到按钮级别的权限怎么做?
    • v-show和v-if有什么区别?使用场景分别是什么?
    • 说说你对slot的理解?slot使用场景有哪些?
    • 你对SPA单页面的理解,它的优缺点分别是什么?如何实现SPA应用呢
    • SSR解决了什么问题?有做过SSR吗?你是怎么做的?
    • 说下你的vue项目的目录结构,如果是大型项目你该怎么划分结构和划分组件呢?
    • 什么是虚拟DOM?如何实现一个虚拟DOM?说说你的思路
    • 有使用过vue吗?说说你对vue的理解
    • vue3有了解过吗?能说说跟vue2的区别吗?
  • TypeScript系列

  • Webpack系列

  • 面试官
  • Vue系列
chst365
2023-06-28
目录

说说你对vue的mixin的理解,有什么应用场景?

# 面试官:说说你对vue的mixin的理解,有什么应用场景?

# 一、mixin是什么

Mixin是面向对象程序设计语言中的类,提供了方法的实现。其他类可以访问mixin类的方法而不必成为其子类

Mixin类通常作为功能模块使用,在需要该功能时“混入”,有利于代码复用又避免了多继承的复杂

# Vue中的mixin

先来看一下官方定义

mixin(混入),提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。

本质其实就是一个js对象,它可以包含我们组件中任意功能选项,如data、components、methods、created、computed等等

我们只要将共用的功能以对象的方式传入 mixins选项中,当组件使用 mixins对象时所有mixins对象的选项都将被混入该组件本身的选项中来

在Vue中我们可以局部混入跟全局混入

# 局部混入

定义一个mixin对象,有组件options的data、methods属性

var myMixin = {
  created: function () {
    this.hello()
  },
  methods: {
    hello: function () {
      console.log('hello from mixin!')
    }
  }
}
1
2
3
4
5
6
7
8
9
10

组件通过mixins属性调用mixin对象

Vue.component('componentA',{
  mixins: [myMixin]
})
1
2
3

该组件在使用的时候,混合了mixin里面的方法,在自动执行created生命钩子,执行hello方法

# 全局混入

通过Vue.mixin()进行全局的混入

Vue.mixin({
  created: function () {
      console.log("全局混入")
    }
})
1
2
3
4
5

使用全局混入需要特别注意,因为它会影响到每一个组件实例(包括第三方组件)

PS:全局混入常用于插件的编写

# 注意事项:

当组件存在与mixin对象相同的选项的时候,进行递归合并的时候组件的选项会覆盖mixin的选项

但是如果相同选项为生命周期钩子的时候,会合并成一个数组,先执行mixin的钩子,再执行组件的钩子

# 二、使用场景

在日常的开发中,我们经常会遇到在不同的组件中经常会需要用到一些相同或者相似的代码,这些代码的功能相对独立

这时,可以通过Vue的mixin功能将相同或者相似的代码提出来

举个例子

定义一个modal弹窗组件,内部通过isShowing来控制显示

const Modal = {
  template: '#modal',
  data() {
    return {
      isShowing: false
    }
  },
  methods: {
    toggleShow() {
      this.isShowing = !this.isShowing;
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

定义一个tooltip提示框,内部通过isShowing来控制显示

const Tooltip = {
  template: '#tooltip',
  data() {
    return {
      isShowing: false
    }
  },
  methods: {
    toggleShow() {
      this.isShowing = !this.isShowing;
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

通过观察上面两个组件,发现两者的逻辑是相同,代码控制显示也是相同的,这时候mixin就派上用场了

首先抽出共同代码,编写一个mixin

const toggle = {
  data() {
    return {
      isShowing: false
    }
  },
  methods: {
    toggleShow() {
      this.isShowing = !this.isShowing;
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

两个组件在使用上,只需要引入mixin

const Modal = {
  template: '#modal',
  mixins: [toggle]
};
 
const Tooltip = {
  template: '#tooltip',
  mixins: [toggle]
}
1
2
3
4
5
6
7
8
9

通过上面小小的例子,让我们知道了Mixin对于封装一些可复用的功能如此有趣、方便、实用

# 三、源码分析

首先从Vue.mixin入手

源码位置:/src/core/global-api/mixin.js

export function initMixin (Vue: GlobalAPI) {
  Vue.mixin = function (mixin: Object) {
    this.options = mergeOptions(this.options, mixin)
    return this
  }
}
1
2
3
4
5
6

主要是调用merOptions方法

源码位置:/src/core/util/options.js

export function mergeOptions (
  parent: Object,
  child: Object,
  vm?: Component
): Object {

if (child.mixins) { // 判断有没有mixin 也就是mixin里面挂mixin的情况 有的话递归进行合并
    for (let i = 0, l = child.mixins.length; i < l; i++) {
    parent = mergeOptions(parent, child.mixins[i], vm)
    }
}

  const options = {} 
  let key
  for (key in parent) {
    mergeField(key) // 先遍历parent的key 调对应的strats[XXX]方法进行合并
  }
  for (key in child) {
    if (!hasOwn(parent, key)) { // 如果parent已经处理过某个key 就不处理了
      mergeField(key) // 处理child中的key 也就parent中没有处理过的key
    }
  }
  function mergeField (key) {
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key) // 根据不同类型的options调用strats中不同的方法进行合并
  }
  return options
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

从上面的源码,我们得到以下几点:

  • 优先递归处理 mixins
  • 先遍历合并parent 中的key,调用mergeField方法进行合并,然后保存在变量options
  • 再遍历 child,合并补上 parent 中没有的key,调用mergeField方法进行合并,保存在变量options
  • 通过 mergeField 函数进行了合并

下面是关于Vue的几种类型的合并策略

  • 替换型
  • 合并型
  • 队列型
  • 叠加型

# 替换型

替换型合并有props、methods、inject、computed

strats.props =
strats.methods =
strats.inject =
strats.computed = function (
  parentVal: ?Object,
  childVal: ?Object,
  vm?: Component,
  key: string
): ?Object {
  if (!parentVal) return childVal // 如果parentVal没有值,直接返回childVal
  const ret = Object.create(null) // 创建一个第三方对象 ret
  extend(ret, parentVal) // extend方法实际是把parentVal的属性复制到ret中
  if (childVal) extend(ret, childVal) // 把childVal的属性复制到ret中
  return ret
}
strats.provide = mergeDataOrFn
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

同名的props、methods、inject、computed会被后来者代替

# 合并型

和并型合并有:data

strats.data = function(parentVal, childVal, vm) {    
    return mergeDataOrFn(
        parentVal, childVal, vm
    )
};

function mergeDataOrFn(parentVal, childVal, vm) {    
    return function mergedInstanceDataFn() {        
        var childData = childVal.call(vm, vm) // 执行data挂的函数得到对象
        var parentData = parentVal.call(vm, vm)        
        if (childData) {            
            return mergeData(childData, parentData) // 将2个对象进行合并                                 
        } else {            
            return parentData // 如果没有childData 直接返回parentData
        }
    }
}

function mergeData(to, from) {    
    if (!from) return to    
    var key, toVal, fromVal;    
    var keys = Object.keys(from);   
    for (var i = 0; i < keys.length; i++) {
        key = keys[i];
        toVal = to[key];
        fromVal = from[key];    
        // 如果不存在这个属性,就重新设置
        if (!to.hasOwnProperty(key)) {
            set(to, key, fromVal);
        }      
        // 存在相同属性,合并对象
        else if (typeof toVal =="object" && typeof fromVal =="object") {
            mergeData(toVal, fromVal);
        }
    }    
    return to
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

mergeData函数遍历了要合并的 data 的所有属性,然后根据不同情况进行合并:

  • 当目标 data 对象不包含当前属性时,调用 set 方法进行合并(set方法其实就是一些合并重新赋值的方法)
  • 当目标 data 对象包含当前属性并且当前值为纯对象时,递归合并当前对象值,这样做是为了防止对象存在新增属性

# 队列性

队列性合并有:全部生命周期和watch

function mergeHook (
  parentVal: ?Array<Function>,
  childVal: ?Function | ?Array<Function>
): ?Array<Function> {
  return childVal
    ? parentVal
      ? parentVal.concat(childVal)
      : Array.isArray(childVal)
        ? childVal
        : [childVal]
    : parentVal
}

LIFECYCLE_HOOKS.forEach(hook => {
  strats[hook] = mergeHook
})

// watch
strats.watch = function (
  parentVal,
  childVal,
  vm,
  key
) {
  // work around Firefox's Object.prototype.watch...
  if (parentVal === nativeWatch) { parentVal = undefined; }
  if (childVal === nativeWatch) { childVal = undefined; }
  /* istanbul ignore if */
  if (!childVal) { return Object.create(parentVal || null) }
  {
    assertObjectType(key, childVal, vm);
  }
  if (!parentVal) { return childVal }
  var ret = {};
  extend(ret, parentVal);
  for (var key$1 in childVal) {
    var parent = ret[key$1];
    var child = childVal[key$1];
    if (parent && !Array.isArray(parent)) {
      parent = [parent];
    }
    ret[key$1] = parent
      ? parent.concat(child)
      : Array.isArray(child) ? child : [child];
  }
  return ret
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

生命周期钩子和watch被合并为一个数组,然后正序遍历一次执行

# 叠加型

叠加型合并有:component、directives、filters

strats.components=
strats.directives=

strats.filters = function mergeAssets(
    parentVal, childVal, vm, key
) {    
    var res = Object.create(parentVal || null);    
    if (childVal) { 
        for (var key in childVal) {
            res[key] = childVal[key];
        }   
    } 
    return res
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

叠加型主要是通过原型链进行层层的叠加

# 小结:

  • 替换型策略有props、methods、inject、computed,就是将新的同名参数替代旧的参数
  • 合并型策略是data, 通过set方法进行合并和重新赋值
  • 队列型策略有生命周期函数和watch,原理是将函数存入一个数组,然后正序遍历依次执行
  • 叠加型有component、directives、filters,通过原型链进行层层的叠加

# 参考文献

  • https://zhuanlan.zhihu.com/p/31018570
  • https://juejin.cn/post/6844904015495446536#heading-1
  • https://juejin.cn/post/6844903846775357453
  • https://vue3js.cn/docs/zh
#面试官
上次更新: 2025/04/11, 17:57:53
请描述下你对vue生命周期的理解?在created和mounted这两个生命周期中请求数据有什么区别呢?
Vue常用的修饰符有哪些有什么应用场景

← 请描述下你对vue生命周期的理解?在created和mounted这两个生命周期中请求数据有什么区别呢? Vue常用的修饰符有哪些有什么应用场景→

最近更新
01
面试官
03-27
02
this&指针&作用域&闭包
03-27
03
前端
03-27
更多文章>
Theme by Vdoing | Copyright © 2019-2025 chst365 | 豫ICP备17031889号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式