在React Hook时代,Object.assign这种混合写法还要用吗?

2,042 阅读4分钟

背景介绍

这是设计模式系列的第八节,学习的是patterns.dev里设计模式中混合模式内容,由于是资料是英文版,所以我的学习笔记就带有翻译的性质,但并不是翻译,记录的是自己的学习过程和理解

第一节:高并发造成的数据统计困难?看我单例模式一招制敌

第二节:JS和迪丽热巴一样有专业替身?没听过的快来补补课...

第三节:还在层层传递props?来学学非常实用的供应商模式吧

第四节:都知道JavaScript原型,但设计模式里的原型模式你会用吗?

第五节:React Hooks时代,怎么实现视图与逻辑分离呢?

第六节:是时候拿出高级的技术了————观察者模式

第七节:前端性能优化进阶篇——动态加载模块基础补遗

写在前面

前面我们在学原型模式时,学习了一些关于原型的知识,我们知道通过原型是可以给对象添加属性和方法。而通过Object.assign方法或者proto属性添加属性,也就是今天我们要讲的混合模式

极简释义

不使用继承的情况下给Object对象或者class类添加函数。

基本实现方式就是使用Object.assign方法给目标类的原型(prototype)上添加属性, 然后新创建的对象都可以访问新加的函数。

class Dog {
  constructor(name) {
    this.name = name;
  }
}

const dogFunctionality = {
  bark: () => console.log("Woof!"),
  wagTail: () => console.log("Wagging my tail!"),
  play: () => console.log("Playing!")
};

Object.assign(Dog.prototype, dogFunctionality);

通过直接给Dog这个类的原型添加方法,现在我们可以直接调用新增的方法了:

const pet1 = new Dog("Daisy");

pet1.name; // Daisy
pet1.bark(); // Woof!
pet1.play(); // Playing!

被混合的Object之间可以使用继承

混合模式允许给Class和Object不使用继承添加属性和方法,同时被混合的Object之间是可以使用继承的。

看下面的例子:

const animalFunctionality = {
  walk: () => console.log("Walking!"),
  sleep: () => console.log("Sleeping!")
};

const dogFunctionality = {
  bark: () => console.log("Woof!"),
  wagTail: () => console.log("Wagging my tail!"),
  play: () => console.log("Playing!"),
  walk() {
    super.walk();
  },
  sleep() {
    super.sleep();
  }
};

Object.assign(dogFunctionality, animalFunctionality);
Object.assign(Dog.prototype, dogFunctionality);

在上面的例子中,我们首先使用Object.assign给dogFunctionality添加了动物animalFunctionality的函数,然后又把dogFunctionality里的函数添加给了Dog这个类的原型

这个例子可以换种原型的写法Proto来实现,一个意思:

class Dog {
  constructor(name) {
    this.name = name;
  }
}


const animalFunctionality = {
  fly: ()=>console.log("fly"),
  walk: () => console.log("Walking!"),
  sleep: () => console.log("Sleeping!")
};


const dogFunctionality = {
  __proto__: animalFunctionality,
  bark: () => console.log("Woof!"),
  wagTail: () => console.log("Wagging my tail!"),
  play: () => console.log("Playing!"),
  walk() {
    super.walk();
  },
  sleep() {
    super.sleep();
  }
};

Object.assign(Dog.prototype, dogFunctionality);

const pet1 = new Dog("Daisy");

console.log(pet1.name); // Daisy
pet1.bark(); // Woof!
pet1.play(); // Playing!
pet1.walk(); // Walking!
pet1.sleep(); // Sleeping!

// 调试
pet1.fly(); // Error, pet1.fly is not a function

console.log(pet1.__proto__); //{bark...wagTail...play...walk...sleep}
console.log(Dog.prototype); //{bark...wagTail...play...walk...sleep}
console.log(dogFunctionality.__proto__); //{fly...walk...sleep}
console.log(animalFunctionality.__proto__); //{}

在线示例

需要注意的是,dogFunctionality 原型的方法fly,Dog类中无法调用;

给Dog添加的原型,并没有把所添加原型的原型里的方法给添加上;

Dog访问原型用prototype, Dog的实例对象或其他任意对象通过**_proto_**访问原型;

只支持函数,不支持其他属性;

案例分析

浏览器window对象

在实际的应用中一个比较明显的混合模式是浏览器界面里的window对象。window对象的很多属性实现自WindowOrWorkerGlobalScope 和 WindowEventHandler的混合,然后我们才能使用诸如setTimeout 和 setIntervalindexedDB, 和 isSecureContext这些属性。

因为混合模式下,只能实现给Object对象添加函数属性,而不能直接调用诸如WindowOrWorkerGlobalScope 这样的对象。

window.indexedDB.open("toDoList");


window.addEventListener("beforeunload", event => {
  event.preventDefault();
  event.returnValue = "";
});


window.onbeforeunload = function() {
  console.log("Unloading!");
};
// 可以调用混合后的属性
console.log(
  "From WindowEventHandlers mixin: onbeforeunload",
  window.onbeforeunload
);


console.log(
  "From WindowOrWorkerGlobalScope mixin: isSecureContext",
  window.isSecureContext
);

// 可以调用被混合的对象
console.log(
  "WindowEventHandlers itself is undefined",
  window.WindowEventHandlers
);


console.log(
  "WindowOrWorkerGlobalScope itself is undefined",
  window.WindowOrWorkerGlobalScope
);

ES6之前的React

在React引入ES6之前,经常使用混合模式来给React组件添加函数。

React团队并不推荐使用混合模式,因为这会给组件带来不必要的复杂度,并且难以维护和复用;而是推荐使用高阶函数,当然现在大都被hooks方式取代了。

总结

混合模式允许我们在不使用继承的情况下,用给对象原型注入函数的方式,从而轻松实现地给对象添加函数。但是修改对象原型往往并不是好的做法,因为这样可能会导致原型污染和一定程度的函数溯源混乱

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 1 天,点击查看活动详情