JavaScript 和 TypeScript 中的 class
对于一个前端开发者来说,很少用到 class ,因为在 JavaScript 中更多的是 函数式
编程,抬手就是一个 function
,几乎不见 class 或 new 的踪影。所以 设计模式
也是大多数前端开发者的一个短板。
最近在学习 Angular 的过程中发现其大量的运用了 class,不得不佩服,Angular 确实是一个优秀的、值得深入研究的 框架。
本文将简单的介绍一下 JavaScript
和 TypeScript
中的 class。
基本概念
在介绍 class 之前,要先介绍一些基本的概念。
静态成员
类自身的成员,可以继承,但实例无法访问,一般多见于工具类,比如在jQuery时代最常见的
$.ajax
,ajax
便是$
的静态方法,使用方便,不需要再通过new
或者函数调用的得到一个新实例。私有成员
类内部的成员,一般是不能继承的,只能在内部使用,实例无法访问,有一点点像闭包内部的变量,但是还是一定的差别,目前 JavaScript 无法直接定义私有成员,只能通过其它方式辅助实现。
getter/setter
存取器属性,当我们访问或者修改一个实例的属性的时候,我们可通过存取器属性拦截这两个操作,从而做一些其它的事情,
vue
正是通过这个api来实现对数据变化的追踪。实例成员
指
new
出来的实例所具有的成员,可以被继承,也是通过这个特性实现了代码的复用。抽象类,抽象方法
抽象类指不可以被实例化的类,通过
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('推特治国'); // 特朗普 : 推特治国
JavaScript 和 TypeScript 中的 class的更多相关文章
- JavaScript 、TypeScript 中的 Boolean
boolean 是 JavaScript 中一种有趣的原始数据类型.在TypeScript中,非严格模式下("strictNullChecks": false),它总共允许4个值 ...
- 在TypeScript中扩展JavaScript基础对象的功能
最近工作中用到,记录一下:假设我们需要一个功能,把一个数字比如10000输出为下面的字符串格式“10,000”,一般是写一个方法,那么我希望更方便一点,直接向Number类型添加一个格式化方法,比如叫 ...
- 细数Javascript技术栈中的四种依赖注入
作为面向对象编程中实现控制反转(Inversion of Control,下文称IoC)最常见的技术手段之一,依赖注入(Dependency Injection,下文称DI)可谓在OOP编程中大行其道 ...
- 从 JavaScript 到 TypeScript
本文首发在我的个人博客:http://muyunyun.cn/posts/66a54fc2/ 文中的案例代码已经上传到 TypeScript TypeScript 并不是一个完全新的语言, 它是 Ja ...
- 从 JavaScript 到 TypeScript 系列
随着应用的庞大,项目中 JavaScript 的代码也会越来越臃肿,这时候许多 JavaScript 的语言弊端就会愈发明显,而 TypeScript 的出现,就是着力于解决 JavaScript 语 ...
- JavaScript 和 TypeScript 交叉口 —— 类型定义文件(*.d.ts)
在 <从 JavaScript 到 TypeScript 系列> 文章我们已经学习了 TypeScript 相关的知识. TypeScript 的核心在于静态类型,我们在编写 TS 的时候 ...
- TypeScript 中的方法重载
方法重载(overload)在传统的静态类型语言中是很常见的.JavaScript 作为动态语言, 是没有重载这一说的.一是它的参数没有类型的区分,二是对参数个数也没有检查.虽然语言层面无法自动进行重 ...
- 十分钟教你理解TypeScript中的泛型
转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者.原文出处:https://blog.bitsrc.io/understanding-generics-in-t ...
- TypeScript中使用getElementXXX()
如果只是看解决方法,可以直接跳到第二小节 简述 Angular 1.x版本是用JavaScript编写的,我们在百度Angular经常会搜索到AngularJS,并不是JavaScript的什么衍生版 ...
随机推荐
- Matlab绘图基础——绘制三维表面
%绘制三维表面 ------------------------------------- %1.绘制线框图:mesh:每一条曲线称为mesh line %首先利用meshgrid函数产生平面区域内的 ...
- 测试驱动开发实践3————testSave之新增用户
内容指引 1.确定新增用户的业务规则 2.根据业务规则设计测试用例 3.为测试用例赋值并驱动开发 一.确定新增用户的规则 1.注册用户允许通过"用户名+密码"."手机号+ ...
- 查看http的并发请求数与其TCP连接状态
[root@new-web7 ~ ::]#netstat -na | awk '/^tcp/ {++S[$NF]} END {for(i in S) print i, S[i]}' TIME_WAIT ...
- Android API
http://www.cnblogs.com/over140/tag/Android%20API%20%E4%B8%AD%E6%96%87/
- 韩天峰博客 php基础知识学习记录
http://rango.swoole.com 写好PHP代码真的不容易,给大家几个建议: 慎用全局变量,全局变量不好管理的,会导致你的代码依赖于全局变量,而耦合度太高. 一定不要复制粘贴代码,可重用 ...
- 关于JAVA开发工具IDEA使用
安装IntelliJ IDEA 一.安装JDK 1 下载最新的jdk,这里下的是jdk-8u66 2 将jdk安装到默认的路径C:\Program Files\Java目录下 二.安装IntelliJ ...
- Java读取word中表格
因为要新建一个站,公司要把word表格的部分行列存到数据库中.之前用java操作过excel,本来打算用java从word表格中读取数据,再存到数据库中,结果因为权限不够,无法访问公司要写的那个数据库 ...
- 201621123068 Week04-面向对象设计与继承
1. 本周学习总结 1.1 写出你认为本周学习中比较重要的知识点关键词 答:继承.多态.重载.关键字.父类与子类 1.2 尝试使用思维导图将这些关键词组织起来. 2. 书面作业 1. 面向对象设计(大 ...
- Java课程设计报告——购物车
1.码云GIT提交 Git地址 2基本框架 3.基本界面 1.主界面: 2.购物车界面: 3.添加商品界面: 4.删除商品界面: 5.修改商品界面: 6.商城界面: 7.购物车显示界面: 4.代码解释 ...
- python的PEP8 代码风格指南
PEP8 代码风格指南 这篇文章原文实际上来自于这里:https://www.python.org/dev/peps/pep-0008/ 知识点 代码排版 字符串引号 表达式和语句中的空格 注释 版本 ...