- 为什么要有类
- 不同对象的属性重复了,就有了类
为什么要有继承
- 不同的类的属性重复了,就有了继承
- 大部分编程技巧都是为了解决重复
- 两个对象的属性重复了
let person1 = {
name: "ories",
age: 18,
sayHi() {},
};
let person2 = {
name: "jack",
age: 23,
sayHi() {},
};
- 于是就有了类和构造函数
class Person {
name;
age;
sayHi() {}
constructor(name, age) {
this.name = name;
this.age = age;
}
}
let person1 = new Person("ories", 18);
let person2 = new Person("jack", 23);
- 我们发现 js 的 class 特别麻烦,声明 name,写了四次,于是改写 TypeScript
class Person{
sayHi(): void {} // sayHi 的返回值为空
constructor(public name: string, public age: number){}
// 由于name前面有public,所以name会变为this.name
}
let person1 = new Person('ories', 18)
let person2 = new Person('jack', 23)
细节
- name 和 age 怎么赋值的
- 类型怎么写的
- 如何运行上面代码, ts-node
更加配置化运行 TypeScript
- 加入 tsconfig.json
- 常见配置 compilerOptions/noImplicitAny:true,禁用隐式的 any 类型
- 总结
类
- 类就是把对象的属性提前写好,避免重复
- 类里面的字段会编程对象的属性
为了节约内存,所有函数<font color=orange>都是共用的</font>(存疑 1)
- person1.sayHi === person2.sayHi
- 而<font color=orange>非函数属性是各个对象自有的</font>(存疑 2)
- 使用 <font color=skyblue>console.dir(person1)</font>可以看出来
构造函数
- 属性名虽然可以提前写好,但是属性值不行
- 所以需要构造函数接收函数,初始化属性值
- 构造函数不需要写 return,默认会 return 新对象,即 constructor 默认 return this
语法
- 函数都是共用的?
- 给每个对象自身加个函数行不
class Person {
mySayhi = () => {}; // 自用
sayHi(): void {} // 共用
}
let person1 = new Person();
let person2 = new Person();
console.dir(person1);
console.dir(person2);
这玩意儿除了浪费内存还有啥用
- React 里面非常有用
import React from "react";
import ReactDOM from "react-dom";
class App extends React.Component {
name = "Frank";
sayHi = () => {
console.log(this);
console.log(`Hi, I'm ${this.name}`);
};
render() {
return (
<div>
<button onClick={this.sayHi}>say hi</button>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
- 非函数属性是各个对象自有的
- 创建一个共用的属性行吗
Person.prototype.kind = "人类";
let person1 = new Person();
let person2 = new Person();
person1.kind === person2.kind;
// 不用原型做不到(class 做不到)
- 但是可以在 class 身上加属性
class Person {
static kind = "人类";
}
console.log(Person.kind); // 人类
- 小结
类
- 声明对象的自身属性(非函数)
- 声明对象的共有函数
- 声明对象的自身函数
- 声明类的自身属性(可以是函数)
- 继承
现在要给 Person 添加功能
- person1.on('dire',fn)
- person1.emit('die')
- person1.off('die',fn)
- 让 Person 实例具有发布订阅功能,怎么做?
- 加代码
class Person {
constructor() {}
sayHi() {}
cache = [];
on() {}
off() {}
emit() {}
}
let person1 = new Person();
// 这样person1 就既是人类,有能发布订阅
- 除了人类,还有另一个类:报社
class 报社{
constructor(public name){
print(){}
cache = []
on(){}
off(){}
emit(){}
}
}
// 这样 报社1 就既是报社,又能发布订阅
消除重复
Person 和报社有重复属性
- 把重复属性抽出来,单独写一个类 EventEmitter
- 然后让 Person 和报社继承 EventEmitter
细节
- constructor 要调用 super()
- 以保证 EventEmitter 实例被初始化
- 继承
class EventEmitter {
constructor(){}
cache = []
on(){}
off(){}
emit(){}
}
class Person extends EventEmitter{
constructor(public name){
super() // super的作用是调用EventEmitter中的constructor
}
sayHi(){}
}
class 报社 extends EventEmitter{
constructor(public name){
super()
}
print(){}
}
- 继承的其他功能
重写
- 子类重写父类的所有属性,以实现多态
- 多态的意思是不同的子类对同一个消息有不同的反应
class Person extends EventEmitter{
constructor(public name){ super() }
sayHi(){}
on(eventName, fn){
console.log('我要监听啦')
super.on(eventName, fn)
}
}
- 继承有什么问题?
- 如果我需要更多功能怎么办?
两个选择
- 让 EvenEmitter 继承其他类
- 让 Person 继承两个类(多继承)
- 两个方法都不好
- 组合
- 组合并没有固定的写法,只演示一种写法
- 让 Person 实现发布订阅
class Person {
eventEmitter = new EventEmitter(); // 人类拥有事件发布功能
name;
sayHi() {}
on(eventName, fn) {
this.eventEmitter.on(eventName, fn);
}
off(eventName, fn) {
this.eventEmitter.off(eventName, fn);
}
emit(eventName, data) {
this.eventEmitter.emit(eventName, data);
}
}
- 优化代码
class Person {
name;
sayHi() {}
}
let person1 = new Person("frank");
mixin(person1, new EventEmitter()); // 把EventEmitter的on ,off, emit 拷贝到person1
function mixin(to, from) {
for (let key in from) {
to[key] = from[key];
}
// 注意,这是最简化的mixin,实际会复杂点
}
- 实际上要这么写
function createEventEmitter() {
const cache = [];
return {
on() {},
off() {},
emit() {},
};
}
// 组合的意思是要什么复制什么,只把地址复制过去
- 如果要更多功能
class Person {
name;
sayHi() {}
}
let person1 = new Person("ories");
mixin(person1, new EventEmitter());
mixin(person1, new Flyer());
mixin(person1, new Killer());
- 有了组合,可能不需要 class
- 直接用函数+必包
- 举例,猫,狗,动物
dog
.wangwang()
.poop();
cat
.miaomiao()
.poop();
animal // 动物父类
.poop()
dog // 狗子类
.wangwang()
cat // 猫子类
.miaomiao()
- 机器人
cleaningRobot
.run()
.clean()
animal
.poop()
dog.wangwang()
cat.miaomiao()
robot // 机器人父类
.run()
murderRobot // 子类杀人机器人
.kill()
cleaningRobot // 子类清扫机器人
.clean()
- 碰到需求:狗形状各杀人机器人
robot
.run()
murderRobot
.kill()
cleaningRobot
.clean()
animal
.poop() // 这行不需要
dog
.wangwang() // 这行需要,所以通过继承搞出狗型杀人机器人基本是不可能的
cat
.miaomiao()
于是用组合
- dog = poop()+wangwang()
- cat = poop()+miaomiao()
- cleaningRobot = run()+clean()
- murderRobot = run()+kill()
- 狗型杀人机器人 = run()+kill()+wangwang()
- 代码怎么写, 不用 class 写 dog
const createWang = (state) => ({
wangwang: () => {
console.log(`汪汪,我是${stage.name}`);
},
});
const createRun = (state) => ({
run: () => {
state.position += 1;
},
});
const createDog = (name) => {
const state = { name, position: 0 }; // 这个state是一个闭包
return Object.assign({}, createWang(state), createRun(state));
// 空对象最后就剩下wangwang,run的地址,
// {}是唯一暴露的出口
// 对象是穷人的必包,java没有闭包只能用对象封装功能
// 如果有闭包,就用对象,闭包,对象暴露api,api去操作这个对象
};
const dog = createDog("小白");
- 最早写前端的一批人由 java 转入,他们带来了 class 继承,多态
- 前端写着写着发现代码越写越复杂
- 后端有些数据结构相对固定,但是事实上前端的需求很灵活,
- 这样子前端就开始想是不是面向对象的问题,继承为什么难用,一旦出现交叉就搞不过来
- 所以往其他方向探索,一大分支就是函数式编程,以上就是函数来搞定
- 狗型杀人机器人怎么写
const createMurderRobotDog = (name) => {
const state = { name, position: 0 };
return Object.assign(
{},
createWang(state),
createRun(state),
createKill(stage)
);
};
const murderRobotDog = createMurderRobotDog("小黑");
- 组合的总结
- 组合的缺点,写法太灵活
- 组合优于继承
- 从新再写一遍组合
- 用 wangwang 和 poop 创造出 dog
// dog = poop + wangwang
const createPoop = (state) => ({
// 这里不加括号会有bug,js会以为是代码块,不返回对象
poop() {
state.weight -= 1;
console.log(`我在拉屎,体重变为${state.weight}`);
},
});
p = createPoop({ weight: 100 });
p.poop();
const createWang = (state) => ({
wangwang() {
console.log(`汪汪,我叫${state.name}`);
},
});
w = createWang({ name: "小白" });
const createDog = (name) => {
const state = { name: name, weight: 100 };
return Object.assign({}, createWang(state), createPoop(state));
};
dog = createDog("小白");
dog.wangwang();
dog.poop();
- 什么时候用继承
场景
- 开发者不会用组合
- 功能没有太多交叉的地方,一眼就能看出继承关系
- 前端库比较喜欢用继承
举例
- const vm = new Vue({...})
// 共有属性上有on emit off
// 为代码其就是
mixin(Vue.prototype, eventemitter) // 组合
- Vue 混入了 EventEmitter
- class App extends React.Component{...}
- React.Component 内置了方便的复用代码(钩子)
- 什么时候用组合
场景
- 功能组合非常灵活,无法一眼看出继承关系
- 插件系统,Vue 的插件就是将功能复制到 Vue 的原型
// Vue的源代码
// Vue.use 接受一个插件,或者对象
有一句installedPlugins.push(plugin) // 把插件安装到被安装的插件里
// 详情见Vue文档开发插件3,把created复制到当前实例
// 或者4.在Vue.prototype去添加属性
- mixin 模式
源代码中的mergeOptions,其实就是复制属性到Vue的options
举例
- Vue.mixin()
- Vue.use(MyPlugin)
- React 接收组件的组件,由于 react 用函数,组件组合起来更方便
function App() {
return <div className="App">{connect(A, B)}</div>;
}
function A(props) {
return <div>我是A,我的儿子是{props.children}</div>;
}
function B(props) {
return <div>我是B</div>;
}
function connect(Component1, component2) {
return (
<Fragment>
<Component1></Component1> | <Component2 />
</Fragment>
);
}
- 总结
- 如果开发基础库,用继承
- 业务开发使用组合
- 不一定对,需要灵活选择
- 组合更占内存么?
- 通过将函数写在外面解决,会产生新的函数
const f1 = () => {};
createF1 = (state) => ({ f1: f1 });
const f2 = () => {};
createF2 = (state) => ({ f2: f2 });
createDog = (name) => {
const state = { name };
return Object.assign({}, createF1(), createF2());
};
dog1 = createDog(1);
dog2 = createDog(2);
dog1.f1 === dog2.f1;
- 然而面向对象也会产生先的对象
- 在原理上没太多区别
- 觉得对你有帮助的朋友,请关注公众号!
本文由博客一文多发平台 OpenWrite 发布!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。