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高级程序设计(二)继承
会持续更新中…
原创不易,期待您的点赞关注与转发评论😜😜

目录
相关文章
|
1天前
|
JavaScript 前端开发
js之class继承|27
js之class继承|27
|
1天前
|
JSON JavaScript 前端开发
js原型继承|26
js原型继承|26
|
1天前
|
JavaScript 前端开发 开发者
JavaScript 类继承
JavaScript 类继承
5 1
|
3天前
|
JavaScript 前端开发
JS的几种继承方式
JavaScript中的几种继承方式视频。
9 0
|
27天前
|
开发者 图形学 iOS开发
掌握Unity的跨平台部署与发布秘籍,让你的游戏作品在多个平台上大放异彩——从基础设置到高级优化,深入解析一站式游戏开发解决方案的每一个细节,带你领略高效发布流程的魅力所在
【8月更文挑战第31天】跨平台游戏开发是当今游戏产业的热点,尤其在移动设备普及的背景下更为重要。作为领先的游戏开发引擎,Unity以其卓越的跨平台支持能力脱颖而出,能够将游戏轻松部署至iOS、Android、PC、Mac、Web及游戏主机等多个平台。本文通过杂文形式探讨Unity在各平台的部署与发布策略,并提供具体实例,涵盖项目设置、性能优化、打包流程及发布前准备等关键环节,助力开发者充分利用Unity的强大功能,实现多平台游戏开发。
49 0
|
1月前
|
JavaScript 前端开发 开发者
揭开JavaScript的神秘面纱:原型链背后隐藏的继承秘密
【8月更文挑战第23天】原型链是JavaScript面向对象编程的核心特性,它使对象能继承另一个对象的属性和方法。每个对象内部都有一个[[Prototype]]属性指向其原型对象,形成链式结构。访问对象属性时,若当前对象不存在该属性,则沿原型链向上查找。
25 0
|
1月前
|
JavaScript 前端开发
JS的6种继承方式
JS的6种继承方式
|
1月前
|
设计模式 JavaScript 前端开发
js对原型和继承的理解
了解JavaScript中原型和继承的概念对于编写优雅高效的代码、理解库和框架的内部机制以及执行高级设计模式都有着重要的意义。
38 0
|
3月前
|
JavaScript 前端开发
JavaScript 中,可以使用类继承来创建子类
JavaScript 中,可以使用类继承来创建子类
25 0
|
3月前
|
JavaScript 前端开发
JS的6种继承方式
JS的6种继承方式
23 0