为什么我突然关注起了extends呢?
最近,在支援flutter项目的时候,由于我们是多人开发,有些模块的逻辑又是可以共用的,于是,T7大佬说,你用我这个extends一下再override一下就好了,你不用写这块逻辑了
extends ? Dart中的继承和Vue有啥区别、我Vue很少用extends啊,我一般用mixin,于是便有了这篇文章!
设计模式:继承&组合
我们先来搞清楚两个概念,继承和组合,在面向对象编程中,继承和组合是两种常用的代码复用方式,但随Vue、React等框架的发展,作为前端工程师的我,对类的概念逐渐模糊,而转化成了业务场景中的:组件封装、逻辑复用。
继承
子类继承父类的特征和行为,使得子类具有和父类一样的属性和方法
- 优点
代码复用。
多态性:继承父类的方法,子类可以根据自己的需求重写父类方法
is-a关系,可以用来约束父类和子类的关系。 - 缺点
类间的层次变深会影响代码的可读性和可维护性。
子类会继承了父类的所有属性和方法,即使没有使用也会继承,容易造成自身属性的膨胀
组合
组合是对现有对象进行拼装组合实现更复杂的功能,
- 优点
has-a的关系,表明自身这个类包含其他类的关系。
继承的特性可以通过组合、接口、委托实现。解决层次过深、过复杂的继承关系影响代码可维护性的问题。
组合的层级关系更少,类间的耦合性更低,便于代码维护和阅读。 - 缺点
将继承改为组合,意味着需要进行更细粒度的拆分,势必会产生更多的类和接口,类的数量增加会增加代码维护成本。
在《设计模式之美》一书中,认为“多用组合,少用继承”
接下来我们对比Dart和Vue
Dart
Dart作为面向对象语言,和Java类似
extends
- 单继承、多态性
- 子类重写超类的方法,用@override
- 子类调用超类的方法,用super
当你使用 extends 继承一个抽象类时没有必要覆写抽象类中所有的方法, 但是抽象类中的 getter 和 setter 是必须覆写的
import 'Person.dart';
class Student extends Perosn{
// 覆写父类的计算属性
bool get adult => this.age > 15;
void study(){
print("Student studying...");
}
void run() {
// 调用父类的方法
super.run();
print("student running...");
}
}
void main(){
Student student = new Student();
student.age = 16;
student.run(); // Person running... student running...
print(student.adult); // true
}
implements
dart中没有专门的interface去创建接口,一般是用抽象类创建接口
注意:使用接口,必须覆写父类中每一个方法
abstract class InterfaceOne{
void one();
}
abstract class InterfaceTwo{
void two();
}
class Example implements InterfaceOne,InterfaceTwo{
void one() {
}
void two() {
}
}
mixin
混入,多个类可以混入到一个类中,这样就可以实现类似多继承的效果。
mixin存在冲突的部分,后面会覆盖前面的,没有冲突的则会保留,所以可以存在后面的mixin修改了前面的mixin的一部分逻辑的情况,不需要直接继承即可实现覆盖,避免了更复杂的继承关系
// 一、直接继承Object
abstract class A {
void initInstances() {
print("A——initInstances");
}
}
mixin B on A{
void initInstances() {
print("B——initInstances");
super.initInstances();
}
}
// 二、间接继承extends关键字后面的类
class C extends B with A,D{
//
}
Vue
extends
全局API,使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。
只能单次扩展一个组件
在官方给的例子中
<div id="mount-point"></div>
<script>
// 创建构造器
var Profile = Vue.extend({
template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
data: function () {
return {
firstName: 'Walter',
lastName: 'White',
alias: 'Heisenberg'
}
}
})
// 创建 Profile 实例,并挂载到一个元素上。
new Profile().$mount('#mount-point')
</script>
什么时候使用?
常用于独立组件开发场景,比如elementUI中
- Vue.extend + vm.$mount 组合
// 假设我们已经实现了loading组件
import Vue from 'vue'
import Loading from './loading'
// 通过Vue的extend方法继承这个引入的 loading 组件,继承后会返回一个vue子类,需要使用实例化即可
const LoadingConstructor = Vue.extend(Loading)
// 创建实例并且挂载到 div上
const loading = new LoadingConstructor().$mount(document.createElement('div'))
// 显示loading效果
function showLoad (options) {
// 初始化调用传递过来的参数赋值更改组件内内部值
for (const key in options) {
loading[key] = options[key]
}
// 让其显示
loading.showLoading = true
// 并将 Vue.extend 创建的 dom 元素插入body中
document.getElementById('app').appendChild(loading.$el)
}
// 关闭loading效果
function hideLoad () {
loading.showLoading = false
}
// 将控制 loading 的方法挂载到 Vue 原型
Vue.prototype.$showLoad = showLoad
Vue.prototype.$hideLoad = hideLoad
- Vue.extend + vm.$on 组合
// 假设我们已经实现了toast组件
import Vue from 'vue'
import Toast from './toast.vue'
// 创建Toast构造器
const ToastConstructor = Vue.extend(Toast)
let instance
function toast (options = {}) {
// 设置默认参数为对象,如果参数为字符串,参数中message属性等于该参数
if (typeof options === 'string') {
options = {
message: options,
}
}
// 创建实例
instance = new ToastConstructor({
data: options,
})
// 注册组件的监听事件
instance.$on('close-event', () => {
console.log('success')
})
// 将实例挂载到body下
document.body.appendChild(instance.$mount().$el)
}
// 将Toast组件挂载到vue原型上
Vue.prototype.$toast = toast
mixin
- 多个组件共享数据和方法,可以局部混入和全局混入
- 一个组件中改动了mixin的数据,另一个组件不会受影响
- 命名冲突、不好追溯源,排查复杂
使用(以局部混入为例)
1、定一个mixin
export const mixins = {
data() {
return {};
},
computed: {},
created() {},
mounted() {},
methods: {},
};
2、在文件中混入
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png" />
<button @click="clickMe">点击我</button>
</div>
</template>
<script>
import { mixins } from "./mixin/index";
export default {
name: "App",
mixins: [mixins],
components: {},
created(){
console.log("组件调用minxi数据",this.msg);
},
mounted(){
console.log("我是组件的mounted生命周期函数")
}
};
</script>
选项合并
1、data数据冲突:组件中的data数据会覆盖mixin中的数据,无冲突的部分自然合并
2、methods、components冲突:组件覆盖mixin,无冲突自然合并
不过 我们可以自定义合并规则,这里不延伸
3、生命周期钩子:两者都会执行,并且mixin中先执行
最后,通过对比Vue和Dart,我对extends的使用方法了解的更加深刻了!