深入理解JavaScript和TypeScript中的class

 更新时间:2018年04月22日 15:28:26   作者:misaky  
class 声明创建一个基于原型继承的具有给定名称的新类,下面这篇文章主要给大家介绍了关于JavaScript和TypeScript中class的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下。

前言

对于一个前端开发者来说,很少用到 class ,因为在 JavaScript 中更多的是 函数式 编程,抬手就是一个 function,几乎不见 class 或 new 的踪影。所以 设计模式 也是大多数前端开发者的一个短板。

最近在学习 Angular 的过程中发现其大量的运用了 class,不得不佩服,Angular 确实是一个优秀的、值得深入研究的 框架。

本文将简单的介绍一下 JavaScript 和 TypeScript 中的 class。

基本概念

在介绍 class 之前,要先介绍一些基本的概念。

1、静态成员

类自身的成员,可以继承,但实例无法访问,一般多见于工具类,比如在jQuery时代最常见的 $.ajax ,ajax 便是 $ 的静态方法,使用方便,不需要再通过 new 或者函数调用的得到一个新实例。

2、私有成员

类内部的成员,一般是不能继承的,只能在内部使用,实例无法访问,有一点点像闭包内部的变量,但是还是一定的差别,目前 JavaScript 无法直接定义私有成员,只能通过其它方式辅助实现。

3、getter/setter

存取器属性,当我们访问或者修改一个实例的属性的时候,我们可通过存取器属性拦截这两个操作,从而做一些其它的事情,vue正是通过这个api来实现对数据变化的追踪。

4、实例成员

指 new 出来的实例所具有的成员,可以被继承,也是通过这个特性实现了代码的复用。

5、抽象类,抽象方法

抽象类指不可以被实例化的类,通过 new 关键字调用会报错,一般都被设计成父类。

抽象方法,只提供方法的名称,参数和返回值,不负责实现,具体的实现由子类去完成,如果一个子类继承于抽象类,那么这个子类必须实现父类所有的抽象方法,否则会报错。

这两个概念在 JavaScript 都无法直接实现,但在 TypeScript 或 其它面向对象语言中可以轻松实现,另外这个特性也是用于实现 多态 的重要手段。

案例介绍

为了更好的介绍 class,本文将采用三个 类 来做例子,分别是 Person、Chinese、American。从字面上可以很快的知道: Person 是 父类(基类) ,Chinese 和 American 是 子类(派生类) 。

Person 有 name、age、gender 三个属性,sayHello 方法和 fullName 存取器属性。同时 Person 还有一些 静态成员 和 私有成员 ,由于实在太难想例子了,所以就用 foo、bar、x、y、z 这些来代替吧。

作为子类的 Chinese 和 American 继承了 Person 的实例成员和静态成员。同时它们自身也有一些自己的方法和属性:

Chinese 有 kungfu 属性,会习武 martial。

American 有 twitter,还可以 sendTwitter。

接下来我们就分别使用 JavaScript 和 TypeScript 来实现这个案例。

JavaScript 中的 class

JavaScript 中的 class 要分开说,在 ES6 中提供了两个关键字 class 和 extends ,虽然它们只是语法糖,底层还是再利用 prototype 实现继承的,但是不能否认,这中写法确实让代码更清晰,更易读。

ES6 中的 class

class Person {
 // #x = '私有属性x';
 // static x = '静态属性x';
 // name;
 // age;
 // gender;

 // 上面的写法还在提案中,并没有成为正式标准,不过变化的可能性已经不大了。
 // 顺便吐槽一下,用 # 表示私有成员,真的是很无语.

 /**
  * Person的静态方法,可以被子类继承
  * 可以通过 this 访问静态成员
  */
 static foo() {
  console.log(`类 ${this.name} 有一个 ${this.x}`);
 }

 constructor(name, age, gender) {
  this.name = name;
  this.age = age;
  this.gender = gender;
 }

 /**
  * 数据存储器,可以访问实例成员,子类的实例可以继承
  * 以通过 this 访问实例成员
  */
 get fullName() {
  const suffix = this.gender === '男' ? '先生' : '女士';
  return this.name + suffix;
 }

 set fullName(value) {
  console.log(`你已改名为 ${value} `);
 }

 /**
  * Person的实例方法,可以被子类的实例继承
  * 可以通过 this 访问实例成员
  */
 sayHello() {
  console.log(`你好我是 ${this.fullName} ,我 ${this.age} 岁了`);
 }
}
Person.x = '静态属性x';
class Chinese extends Person {
 static bar() {
  console.log(`类 ${this.name} 的父类是 ${super.name}`);
  super.foo();
 }

 constructor(name, age, gender, kungfu) {
  super(name, age, gender);
  this.kungfu = kungfu;
 }

 martial() {
  console.log(`${this.name} 正在修炼 ${this.kungfu} `);
 }
}
class American extends Person {

 // static y = '静态属性y';

 static bar() {
  console.log(`类 ${this.name} 有自己的 ${this.y} ,还继承了父类 ${super.name} 的 ${super.x}`);
 }

 constructor(name, age, gender, twitter) {
  super(name, age, gender);
  this.twitter = twitter;
 }

 sendTwitter(msg) {
  console.log(`${this.name} : `);
  console.log(` ${msg}`);
 }
}
American.y = '静态属性y';
Person.x;  // 静态属性x
Person.foo(); // 类 Person 有一个 静态属性x

Chinese.x;  // 静态属性x
Chinese.foo(); // 类 Chinese 有一个 静态属性x
Chinese.bar(); // 类 Chinese 的父类是 Person

American.x;  // 静态属性x
American.y;  // '静态属性y
American.foo(); // 类 American 有一个 静态属性x
American.bar(); // 类 American 有自己的 静态属性y ,还继承了父类 Person 的 静态属性x

const p = new Person('Lucy', 20, '女');
const c = new Chinese('韩梅梅', 18, '女', '咏春拳');
const a = new American('特朗普', 72, '男', 'Donald J. Trump');

c.sayHello(); // 你好我是 韩梅梅女士 ,我 18 岁了
c.martial(); // 韩梅梅 正在修炼 咏春拳 
a.sayHello(); // 你好我是 特朗普先生 ,我 72 岁了
a.sendTwitter('推特治国'); // 特朗普 : 推特治国

ES6 之前的 class

ES5 的继承,实质是先创造子类的实例对象 this,

然后再将父类的方法添加到 this 上面 Parent.apply(this) 。

ES6 的继承机制完全不同,实质是先创造父类的实例对象 this,所以必须先调用 super 方法,

然后再用子类的构造函数修改this。

为了实现继承,我们需要先实现一个 extendsClass 函数,它的作用是让子类继承父类的静态成员和实例成员。

function extendsClass(parent, child) {
 // 防止子类和父类相同名称的成员被父类覆盖
 var flag = false;

 // 继承静态成员
 for (var k in parent) {
  flag = k in child;
  if (!flag) {
   child[k] = parent[k];
  }
 }

 // 继承父类prototype上的成员
 // 用一个新的构造函数切断父类和子类之间的数据共享
 var F = function () { }
 F.prototype = parent.prototype;
 var o = new F();
 for (var k in o) {
  flag = k in child.prototype;
  if (!flag) {
   child.prototype[k] = o[k];
  }
 }
}
function Person(name, age, gender) {
 this.name = name;
 this.age = age;
 this.gender = this.gender;
 // 如果将 getter/setter 写在 prototype 会获取不到
 Object.defineProperty(this, 'fullName', {
  get: function () {
   var suffix = this.gender === '男' ? '先生' : '女士';
   return this.name + suffix;
  },
  set: function () {
   console.log('你已改名为 ' + value + ' ');
  },
 });
}

Person.x = '静态属性x';
Person.foo = function () {
 console.log('类 ' + this.name + ' 有一个 ' + this.x);
}

Person.prototype = {
 constructor: Person,
 // get fullName() { },
 // set fullName(value) { },
 sayHello: function () {
  console.log('你好我是 ' + this.fullName + ' ,我 ' + this.age + ' 了');
 },
};
function Chinese(name, age, gender, kungfu) {
 // 用call改变this指向,实现继承父类的实例属性
 Person.call(this, name, age, gender);
 this.kungfu = kungfu;
}

Chinese.bar = function () {
 console.log('类 ' + this.name + ' 的父类是 ' + Person.name);
 Person.foo();
}

Chinese.prototype = {
 constructor: Chinese,
 martial: function () {
  console.log(this.name + ' 正在修炼 ' + this.kungfu + ' ');
 }
};

extendsClass(Person, Chinese);
function American(name, age, gender, twitter) {
 Person.call(this, name, age, gender);
 this.twitter = twitter;
}

American.y = '静态属性y';
American.bar = function () {
 console.log('类 ' + this.name + ' 有自己的 ' + this.y + ' ,还继承了父类 ' + Person.name + ' 的 ' + Person.x);
}

American.prototype = {
 constructor: American,
 sendTwitter: function (msg) {
  console.log(this.name + ' : ');
  console.log(' ' + msg);
 }
};

extendsClass(Person, American);

TypeScript 中的 class

讲完了 JavaScript 中的类,还是没有用到 抽象类,抽象方法,私有方法这三个概念,由于 JavaScript 语言的局限性,想要实现这三种概念是很困难的,但是在 TypeScript 可以轻松的实现这一特性。

首先我们稍微修改一下例子中的描述,Person 是抽象类,因为一个正常的人肯定是有国籍的,Person 的 sayHello 方法是抽象方法,因为每个国家打招呼的方式不一样。另外一个人的性别是只能读取,不能修改的,且是确定的是,不是男生就是女生,所以还要借助一下枚举。

enum Gender {
 female = 0,
 male = 1
};
abstract class Person {
 private x: string = '私有属性x,子类和实例都无法访问';
 protected y: string = '私有属性y,子类可以访问,实例无法访问';

 name: string;
 public age: number;
 public readonly gender: Gender; // 用关键字 readonly 表明这是一个只读属性

 public static x: string = '静态属性x';
 public static foo() {
  console.log(`类 ${this.name} 有一个 ${this.x}`);
 }

 constructor(name: string, age: number, gender: Gender) {
  this.name = name;
  this.age = age;
  this.gender = gender;
 }

 get fullName(): string {
  const suffix = this.gender === 1 ? '先生' : '女士';
  return this.name + suffix;
 }

 set FullName(value: string) {
  console.log(`你已改名为 ${value} `);
 }

 // 抽象方法,具体实现交由子类完成
 abstract sayHello(): void;
}
class Chinese extends Person {
 public kungfu: string;
 public static bar() {
  console.log(`类 ${this.name} 的父类是 ${super.name}`);
  super.foo();
 }

 public constructor(name: string, age: number, gender: Gender, kungfu: string) {
  super(name, age, gender);
  this.kungfu = kungfu;
 }

 public sayHello(): void {
  console.log(`你好我是 ${this.fullName} ,我 ${this.age} 岁了`);
 }

 public martial() {
  console.log(`${this.name} 正在修炼 ${this.kungfu} `);
 }
}
class American extends Person {
 static y = '静态属性y';
 public static bar() {
  console.log(`类 ${this.name} 有自己的 ${this.y} ,还继承了父类 ${super.name} 的 ${super.x}`);
 }

 public twitter: string;

 public constructor(name: string, age: number, gender: Gender, twitter: string) {
  super(name, age, gender);
  this.twitter = twitter;
 }

 public sayHello(): void {
  console.log(`Hello, I am ${this.fullName} , I'm ${this.age} years old`);
 }

 public sendTwitter(msg: string): void {
  console.log(`${this.name} : `);
  console.log(` ${msg}`);
 }
}
Person.x;  // 静态属性x
Person.foo(); // 类 Person 有一个 静态属性x

Chinese.x;  // 静态属性x
Chinese.foo(); // 类 Chinese 有一个 静态属性x
Chinese.bar(); // 类 Chinese 的父类是 Person

American.x;  // 静态属性x
American.y;  // '静态属性y
American.foo(); // 类 American 有一个 静态属性x
American.bar(); // 类 American 有自己的 静态属性y ,还继承了父类 Person 的 静态属性x

const c: Chinese = new Chinese('韩梅梅', 18, Gender.female, '咏春拳');
const a: American = new American('特朗普', 72, Gender.male, 'Donald J. Trump');

c.sayHello(); // 你好我是 韩梅梅女士 ,我 18 岁了
c.martial(); // 韩梅梅 正在修炼 咏春拳 
a.sayHello(); // Hello, I am 特朗普先生 , I'm 72 years old
a.sendTwitter('推特治国'); // 特朗普 : 推特治国

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。

相关文章

  • 微信小程序使用slider实现音频进度条

    微信小程序使用slider实现音频进度条

    这篇文章主要为大家详细介绍了微信小程序使用slider实现音频进度条,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-06-06
  • JS实现的文件拖拽上传功能示例

    JS实现的文件拖拽上传功能示例

    这篇文章主要介绍了JS实现的文件拖拽上传功能,涉及javascript事件触发、页面元素属性动态修改等相关操作技巧,需要的朋友可以参考下
    2018-05-05
  • JavaScript的History API使搜索引擎抓取AJAX内容

    JavaScript的History API使搜索引擎抓取AJAX内容

    这篇文章主要介绍了JavaScript的History API使搜索引擎抓取AJAX内容 的相关资料,需要的朋友可以参考下
    2015-12-12
  • 微信小程序页面与组件之间信息传递与函数调用

    微信小程序页面与组件之间信息传递与函数调用

    不管是vue还是react中,都在强调组件思想,所以下面这篇文章主要给大家介绍了关于微信小程序页面与组件之间信息传递与函数调用的相关资料,需要的朋友可以参考下
    2021-05-05
  • 前端必会的图片懒加载(三种方式)

    前端必会的图片懒加载(三种方式)

    在我们访问一个图片展示比较多的网页时,加载速度慢很多时候正是因为图片多导致,本文主要介绍了前端必会的图片懒加载(三种方式),具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-08-08
  • JS HTML5实现拖拽移动列表效果

    JS HTML5实现拖拽移动列表效果

    这篇文章主要为大家详细介绍了JS HTML5实现拖拽移动列表效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-07-07
  • 详解javascript 变量提升(Hoisting)

    详解javascript 变量提升(Hoisting)

    这篇文章主要介绍了详解javascript 变量提升(Hoisting),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2019-03-03
  • 原生js+canvas实现验证码

    原生js+canvas实现验证码

    这篇文章主要为大家详细介绍了原生js+canvas实现验证码,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-11-11
  • 微信小程序实现的数字滑块拼图效果

    微信小程序实现的数字滑块拼图效果

    滑块拼图(Slider Puzzle)是一种经典的智力游戏,通常由一个3x3或更大的格子组成,其中一个格子为空,玩家通过滑动拼图块来达到特定的图案或顺序,这篇文章主要介绍了微信小程序实现的数字滑块拼图,需要的朋友可以参考下
    2024-08-08
  • js与C#进行时间戳转换

    js与C#进行时间戳转换

    最近在做一个项目,需要JS时间戳转成C#里的时间,再把C#里的时间戳转成JS的时间,就仔细研究了下js与C#进行转换的注意要点,这里记录下来,有需要的小伙伴自己拿走。
    2014-11-11

最新评论