关于javascript:高级前端养成40js专精08之继承和组合

  1. 为什么要有类
  • 不同对象的属性反复了,就有了类
  • 为什么要有继承

    • 不同的类的属性反复了,就有了继承
  • 大部分编程技巧都是为了解决反复
  1. 两个对象的属性反复了
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 类型
  1. 总结
    • 类就是把对象的属性提前写好,防止反复
    • 类外面的字段会编程对象的属性
    • 为了节约内存,所有函数<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
  • 语法

    • JS 的所有语法能够在 MDN 查看
    • TS 的所有语法能够在 TS 英文/中武官网查看
    • 学会触类旁通,不要妄图零碎把握
  1. 函数都是共用的?
  • 给每个对象本身加个函数行不
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);
  1. 非函数属性是各个对象自有的
  • 创立一个共用的属性行吗
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); // 人类
  1. 小结
    • 申明对象的本身属性(非函数)
    • 申明对象的共有函数
    • 申明对象的本身函数
    • 申明类的本身属性(能够是函数)
  1. 继承
  • 当初要给 Person 增加性能

    • person1.on(‘dire’,fn)
    • person1.emit(‘die’)
    • person1.off(‘die’,fn)
    • 让 Person 实例具备公布订阅性能,怎么做?
  1. 加代码
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(){}
}
  1. 继承的其余性能
  • 重写

    • 子类重写父类的所有属性,以实现多态
    • 多态的意思是不同的子类对同一个音讯有不同的反馈
class Person extends EventEmitter{
  constructor(public name){ super() }
  sayHi(){}
  on(eventName, fn){
    console.log('我要监听啦')
    super.on(eventName, fn)
  }
}
  1. 继承有什么问题?
  • 如果我须要更多功能怎么办?
  • 两个抉择

    • 让 EvenEmitter 继承其余类
    • 让 Person 继承两个类(多继承)
    • 两个办法都不好
  1. 组合
  • 组合并没有固定的写法,只演示一种写法
  • 让 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());
  1. 有了组合,可能不须要 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("小白");
  1. 最早写前端的一批人由 java 转入,他们带来了 class 继承,多态
  • 前端写着写着发现代码越写越简单
  • 后端有些数据结构绝对固定,然而事实上前端的需要很灵便,
  • 这样子前端就开始想是不是面向对象的问题,继承为什么难用,一旦呈现穿插就搞不过去
  • 所以往其余方向摸索,一大分支就是函数式编程,以上就是函数来搞定
  1. 狗型杀人机器人怎么写
const createMurderRobotDog = (name) => {
  const state = { name, position: 0 };
  return Object.assign(
    {},
    createWang(state),
    createRun(state),
    createKill(stage)
  );
};
const murderRobotDog = createMurderRobotDog("小黑");
  1. 组合的总结
  • 组合的毛病,写法太灵便
  • 组合优于继承
  1. 从新再写一遍组合
  • 用 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();
  1. 什么时候用继承
  • 场景

    • 开发者不会用组合
    • 性能没有太多穿插的中央,一眼就能看出继承关系
    • 前端库比拟喜爱用继承
  • 举例

    • const vm = new Vue({…})
// 共有属性上有on emit off
// 为代码其就是
mixin(Vue.prototype, eventemitter) // 组合
  • Vue 混入了 EventEmitter
  • class App extends React.Component{…}
  • React.Component 内置了不便的复用代码(钩子)
  1. 什么时候用组合
  • 场景

    • 性能组合非常灵活,无奈一眼看出继承关系
    • 插件零碎,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>
  );
}
  1. 总结
  • 如果开发根底库,用继承
  • 业务开发应用组合
  • 不肯定对,须要灵便抉择
  1. 组合更占内存么?
  • 通过将函数写在里面解决,会产生新的函数
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;
  • 然而面向对象也会产生先的对象
  • 在原理上没太多区别
  1. 感觉对你有帮忙的敌人,请关注公众号!

本文由博客一文多发平台 OpenWrite 公布!

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理