JavaScript高级程序设计继承(二)

简介: 有时候可能需要定义这样一个类,它可供其他类继承,但本身不会被实例化。虽然 ECMAScript 没 有专门支持这种类的语法 ,但通过 new.target 也很容易实现。new.target 保存通过 new 关键字调 用的类或函数。通过在实例化时检测 new.target 是不是抽象基类,可以阻止对抽象基类的实例化:

👦个人简介:张清悠,字澄澈,号寻梦客,爱好旅行、运动,主攻前端方向技术研发,副攻Unity 3D、C++、Python人工智能等
📝个人寄语:学会不为过程的缓慢而焦虑,即使暂时未能如你所愿,但只要你在努力,你就在不断成长!
🙏个人公众号:清悠小猿(海量源码尽在其中,欢迎关注)

1. 抽象基类

有时候可能需要定义这样一个类,它可供其他类继承,但本身不会被实例化。虽然 ECMAScript 没
有专门支持这种类的语法 ,但通过 new.target 也很容易实现。new.target 保存通过 new 关键字调
用的类或函数。通过在实例化时检测 new.target 是不是抽象基类,可以阻止对抽象基类的实例化:

抽象基类

class Vehicle {
     
 constructor() {
     
 console.log(new.target);  
 if (new.target === Vehicle) {
     
 throw new Error('Vehicle cannot be directly instantiated'); 
class Bus extends Vehicle {
   }  
new Bus(); // class Bus {}  
new Vehicle(); // class Vehicle {}  
// Error: Vehicle cannot be directly instantiated

另外,通过在抽象基类构造函数中进行检查,可以要求派生类必须定义某个方法。因为原型方法在
调用类构造函数之前就已经存在了,所以可以通过 this 关键字来检查相应的方法:
抽象基类

class Vehicle {
     
 constructor() {
     
 if (new.target === Vehicle) {
     
 throw new Error('Vehicle cannot be directly instantiated');  
 }  
 if (!this.foo) {
     
 throw new Error('Inheriting class must define foo()');  
 }  
 console.log('success!');  
 }  
}

派生类

class Bus extends Vehicle {
     
 foo() {
   }  
}

派生类

class Van extends Vehicle {
   }  
new Bus(); // success!  
new Van(); // Error: Inheriting class must define foo()

2. 继承内置类型

ES6 类为继承内置引用类型提供了顺畅的机制,开发者可以方便地扩展内置类型:

  洗牌算法 
  class SuperArray extends Array {
     
 shuffle() {
     
 for (let i = this.length - 1; i > 0; i--) {
     
 const j = Math.floor(Math.random() * (i + 1));  
 [this[i], this[j]] = [this[j], this[i]]; 
 }  
 }  
}  
let a = new SuperArray(1, 2, 3, 4, 5);  
console.log(a instanceof Array); // true  
console.log(a instanceof SuperArray); // true 8.4 类 263  
console.log(a); // [1, 2, 3, 4, 5]  
a.shuffle();  
console.log(a); // [3, 1, 4, 5, 2]

有些内置类型的方法会返回新实例。默认情况下,返回实例的类型与原始实例的类型是一致的:

class SuperArray extends Array {
   }  
let a1 = new SuperArray(1, 2, 3, 4, 5);  
let a2 = a1.filter(x => !!(x%2))  
console.log(a1); // [1, 2, 3, 4, 5]  
console.log(a2); // [1, 3, 5]  
console.log(a1 instanceof SuperArray); // true  
console.log(a2 instanceof SuperArray); // true

如果想覆盖这个默认行为,则可以覆盖 Symbol.species 访问器,这个访问器决定在创建返回的
实例时使用的类:

class SuperArray extends Array {
     

 **static get [Symbol.species]() {
   **  

 **return Array;**  

 **}**  

}  
let a1 = new SuperArray(1, 2, 3, 4, 5);  
let a2 = a1.filter(x => !!(x%2))  
console.log(a1); // [1, 2, 3, 4, 5]  
console.log(a2); // [1, 3, 5]  
console.log(a1 instanceof SuperArray); // true  
**console.log(a2 instanceof SuperArray); // false**

3. 类混入

把不同类的行为集中到一个类是一种常见的 JavaScript 模式。虽然 ES6 没有显式支持多类继承,但
通过现有特性可以轻松地模拟这种行为。
注意 Object.assign()方法是为了混入对象行为而设计的。只有在需要混入类的行为
时才有必要自己实现混入表达式。如果只是需要混入多个对象的属性,那么使用
Object.assign()就可以了。
在下面的代码片段中,extends 关键字后面是一个 JavaScript 表达式。任何可以解析为一个类或一
个构造函数的表达式都是有效的。这个表达式会在求值类定义时被求值:

class Vehicle {
   }  
function getParentClass() {
     
 console.log('evaluated expression');  
 return Vehicle;  
 class Bus extends **getParentClass()** {
   }  
}

可求值的表达式

混入模式可以通过在一个表达式中连缀多个混入元素来实现,这个表达式最终会解析为一个可以被
继承的类。如果 Person 类需要组合 A、B、C,则需要某种机制实现 B 继承 A,C 继承 B,而
再继承 C,从而把 A、B、C 组合到这个超类中。实现这种模式有不同的策略。
一个策略是定义一组“可嵌套”的函数,每个函数分别接收一个超类作为参数,而将混入类定义为
这个参数的子类,并返回这个类。这些组合函数可以连缀调用,最终组合成超类表达式:

class Vehicle {
   }  
let FooMixin = (Superclass) => class extends Superclass {
     
 foo() {
     
 console.log('foo');  
 }  
};  
let BarMixin = (Superclass) => class extends Superclass {
     
 bar() {
     
 console.log('bar');  
 }  
};  
let BazMixin = (Superclass) => class extends Superclass {
     
 baz() {
     
 console.log('baz');  
 }  
};  
class Bus extends FooMixin(BarMixin(BazMixin(Vehicle))) {
   }  
let b = new Bus();  
b.foo(); // foo  
b.bar(); // bar  
b.baz(); // baz

通过写一个辅助函数,可以把嵌套调用展开:

class Vehicle {
   }  
let FooMixin = (Superclass) => class extends Superclass {
     
 foo() {
     
 console.log('foo');  
 }  
};  
let BarMixin = (Superclass) => class extends Superclass {
     
 bar() {
     
 console.log('bar');  
 }  
};  

let BazMixin = (Superclass) => class extends Superclass {
     
 baz() {
     
 console.log('baz');  
 }  
};  

**function mix(BaseClass, ...Mixins) {
   **  
 **return Mixins.reduce((accumulator, current) => current(accumulator), BaseClass);**  
**}**  

**class Bus extends mix(Vehicle, FooMixin, BarMixin, BazMixin) {
   }**  
let b = new Bus();  
b.foo(); // foo  
b.bar(); // bar  
b.baz(); // baz

注意 很多 JavaScript 框架(特别是 React)已经抛弃混入模式,转向了组合模式(把方法

提取到独立的类和辅助对象中,然后把它们组合起来,但不使用继承)。这反映了那个众

所周知的软件设计原则:“组合胜过继承(composition over inheritance)。”这个设计原则被

很多人遵循,在代码设计中能提供极大的灵活

总结

```
以上就是今天的JavaScript高级程序设计(二)继承
会持续更新中…
原创不易,期待您的点赞关注与转发评论😜😜

目录
相关文章
|
6天前
|
JavaScript 前端开发
如何在 JavaScript 中使用 __proto__ 实现对象的继承?
使用`__proto__`实现对象继承时需要注意原型链的完整性和属性方法的正确继承,避免出现意外的行为和错误。同时,在现代JavaScript中,也可以使用`class`和`extends`关键字来实现更简洁和直观的继承语法,但理解基于`__proto__`的继承方式对于深入理解JavaScript的面向对象编程和原型链机制仍然具有重要意义。
|
15天前
|
JavaScript 前端开发
Javascript如何实现继承?
【10月更文挑战第24天】JavaScript 中实现继承的方式有很多种,每种方式都有其优缺点和适用场景。在实际开发中,我们需要根据具体的需求和情况选择合适的继承方式,以实现代码的复用和扩展。
|
9天前
|
JavaScript 前端开发
如何使用原型链继承实现 JavaScript 继承?
【10月更文挑战第22天】使用原型链继承可以实现JavaScript中的继承关系,但需要注意其共享性、查找效率以及参数传递等问题,根据具体的应用场景合理地选择和使用继承方式,以满足代码的复用性和可维护性要求。
|
9天前
|
JavaScript 前端开发 开发者
js实现继承怎么实现
【10月更文挑战第26天】每种方式都有其优缺点和适用场景,开发者可以根据具体的需求和项目情况选择合适的继承方式来实现代码的复用和扩展。
23 1
|
2月前
|
自然语言处理 JavaScript 前端开发
一文梳理JavaScript中常见的七大继承方案
该文章系统地概述了JavaScript中七种常见的继承模式,包括原型链继承、构造函数继承、组合继承、原型式继承、寄生式继承、寄生组合继承等,并探讨了每种模式的实现方式及其优缺点。
一文梳理JavaScript中常见的七大继承方案
|
2月前
|
JavaScript 前端开发
js之class继承|27
js之class继承|27
|
2月前
|
JSON JavaScript 前端开发
js原型继承|26
js原型继承|26
|
2月前
|
JavaScript 前端开发 开发者
JavaScript 类继承
JavaScript 类继承
19 1
|
2月前
|
JavaScript 前端开发
JS的几种继承方式
JavaScript中的几种继承方式视频。
14 0
|
3月前
|
开发者 图形学 iOS开发
掌握Unity的跨平台部署与发布秘籍,让你的游戏作品在多个平台上大放异彩——从基础设置到高级优化,深入解析一站式游戏开发解决方案的每一个细节,带你领略高效发布流程的魅力所在
【8月更文挑战第31天】跨平台游戏开发是当今游戏产业的热点,尤其在移动设备普及的背景下更为重要。作为领先的游戏开发引擎,Unity以其卓越的跨平台支持能力脱颖而出,能够将游戏轻松部署至iOS、Android、PC、Mac、Web及游戏主机等多个平台。本文通过杂文形式探讨Unity在各平台的部署与发布策略,并提供具体实例,涵盖项目设置、性能优化、打包流程及发布前准备等关键环节,助力开发者充分利用Unity的强大功能,实现多平台游戏开发。
97 0