当前位置:网站首页>class 继承的重点

class 继承的重点

2022-08-11 11:29:00 51CTO

在 class 出现之前,JavaScript 实现继承是件麻烦事,构造函数继承有加上原型上的函数不能复用的问题;原型链继承又存在引用值属性的修改不独立的问题;组合继承又存在两次调用构造函数的问题,寄生组合继承,写起来又太麻烦了,总之,在 class 出现前,JavaScipt 实现继承真是件麻烦事儿。

然而,class 的出现真的改变这一现状了吗?

不如往下看。

class 继承的重点_赋值

写法

与函数类型相似,定义类也有两种主要方式:类声明和类表达式。

// 类声明 class Person {}

// 类表达式 const Animal = class {};

不过,与函数定义不同的是,虽然函数声明可以提升,但类定义不能。

与函数构造函数一样,多数编程风格都建议类名的首字母要大写,以区别于通过它创建的实例。

类可以包含:

  • 构造函数方法
  • 实例方法
  • 获取函数
  • 设置函数
  • 静态类方法

这些项都是可选的

constructor

      
      
class Person {
constructor(name) {
this.name = name
console.log('person ctor');
}
}

let p1 = new Person("p1")
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

constructor 会告诉解释器 在使用 new 操作符创建类的新实例时,应该调用这个函数。

等同于

      
      
function Person(name){
this.name = name
console.log('person ctor')
}

let p1 = new Person("p1")
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

类构造函数与构造函数的主要区别是,这样写会报错:

      
      
class Animal {}

let a = Animal(); // TypeError: class constructor Animal cannot be invoked without 'new'
  • 1.
  • 2.
  • 3.

所以,new 操作符是强制要写的;

使用 new 时,原理与 new 一个对象也是一样的,因为太重要了,再强调一遍:

(1) 在内存中创建一个新对象。(2) 这个新对象内部的[[Prototype]]指针被赋值为构造函数的 prototype 属性。 (3) 构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)。 (4) 执行构造函数内部的代码(给新对象添加属性)。 (5) 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象。

特性

从各方面来看,ECMAScript 类就是一种特殊函数。

我们可以用 typeof 打印试试:

      
      
class Person {}

console.log(typeof Person); // function
  • 1.
  • 2.
  • 3.

也可以用 instanceof 检查它的原型链

      
      
class Person {}

let p = new Person()

console.log(p instanceof Person); // true
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

通过 class 构造的每个实例都对应一个唯一的成员对象,这意味着所有成员都不会在原型上共享;

      
      
class Person {
constructor() {
this.name = new String('Jack');
this.sayName = () => console.log(this.name);
}
}
let p1 = new Person();
let p2 = new Person();

console.log(p1.name === p2.name) // false
console.log(p1.sayName === p2.sayName) // false
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

如果想要共享,就改写成方法,写在 constructor 外面:

      
      
class Person {
constructor() {
this.name = new String('Jack');
}
sayName(){
console.log(this.name);
}
}
let p1 = new Person();
let p2 = new Person();

console.log(p1.sayName === p2.sayName) // true
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

我们可以在方法前面加 static 关键字,实现:静态类成员。我们不能在类的实例上调用静态方法,只能通过类本身调用。不做赘述。

继承

ECMAScript 6 新增特性中最出色的一个就是原生支持了类继承机制。虽然类继承使用的是新语法,但背后依旧使用的是原型链

让我们再回顾构造函数继承和原型链继承 2 个经典的问题:

① 构造函数继承的问题:构造函数外在原型上定义方法,不能重用

      
      
function SuperType(){}
SuperType.prototype.sayName = ()=>{console.log("bob")}

function SubType(){
SuperType.call(this) // 构造函数继承
}

let p1 = new SubType()
console.log(p1.sayName()) // Uncaught TypeError: p1.sayName is not a function
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

而原型链继承可以解决这一点:

      
      
function SuperType(){}
SuperType.prototype.sayName = ()=>{console.log("bob")}

function SubType(){}
SubType.prototype = new SuperType() // 原型链继承

let p1 = new SubType()
console.log(p1.sayName()) // bob
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

② 原型链继承的问题:原型中包含的引用值会在所有实例间共享。

      
      
function SuperType(){
this.name = ["bob","tom"];
}

function SubType(){}
SubType.prototype = new SuperType() // 原型链继承

let p1 = new SubType()
p1.name.push("jerry")

let p2 = new SubType()
console.log(p2.name) // ['bob', 'tom', 'jerry']
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

而构造函数继承可以解决这一点:

      
      
function SuperType(){
this.name = ["bob","tom"];
}

function SubType(){
SuperType.call(this) // 构造函数继承
}

let p1 = new SubType()
p1.name.push("jerry")

let p2 = new SubType()
console.log(p2.name) // ['bob', 'tom']
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

class 继承有这两个问题吗??

代码一试便知:

      
      
class SuperType{}
SuperType.prototype.sayName = ()=>{console.log("bob")}

class SubType extends SuperType{
}

let p1 = new SubType()
p1.sayName() // bob
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

问题①,没有问题,在构造函数外写的原型继承,公共方法还是能访问的!!

      
      
class SuperType{
constructor(){
this.name=["bob","tom"]
}
}

class SubType extends SuperType{
}

let p1 = new SubType()
let p2 = new SubType()
p1.name.push("Jerry")
console.log(p2.name) // ['bob', 'tom']
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

问题②,没有问题,在 constructor 的引用值属性,修改不会产生干涉!!

class 继承完美的解决了构造函数继承的问题,和原型链继承的问题,写起来也没有组合继承、寄生继承那么麻烦,如果非得用 JS 模拟面向对象编程,class 必不可少!!

题外话

其实写 Class C 和 C.prototype 一起写是很危险的:明明都在操作面向对象的类了,还要操作原型链。类操作和原型操作是两种不同的设计思路,有兴趣可见本瓜一年前的一篇文章:​ ​“类”设计模式和“原型”设计模式——“复制”和“委托”的差异​

OK,以上便是本篇分享。点赞关注评论,为好文助力

我是掘金安东尼 100 万阅读量人气前端技术博主 INFP 写作人格坚持 1000 日更文 关注我,陪你一起度过漫长编程岁月

原网站

版权声明
本文为[51CTO]所创,转载请带上原文链接,感谢
https://blog.51cto.com/u_13961087/5565883