本文讨论Typescript中的Class同ES5构造函数的对应关系,涉及TypeScript的诸多语法、构造函数、面向对象以及原型对象等相关知识点细节,本文只简单对比并不进行深入展开。

TypeScript 是JavaScript的超集,包含ES5、ES6、ES7+...和扩展。

我们知道在TS中,Class并不是什么新鲜的东西,它的本质其实就是ES那套构造函数·原型对象·实例对象的老古董。在下面的篇幅中,我会简单的对比Class的ES5写法,从最简单到复杂。

最简单的类

最简单的类,除名字外其它一无所有。

/* 文件名:01.ts_class_and_es5.ts */
class Class_test{}

我们通过tsc 01.ts_class_and_es5.ts命令对该文件进行编译,得到的是下面的JavaScript代码。

/* 文件名:01.ts_class_and_es5.js */
var Class_test = /** @class */ (function () {
function Class_test() {}
return Class_test;
}());

从编译后的结果可以看到对应的代码就是个空构造函数,精巧的地方在于这个函数被放到一个自调用函数中并返回,这里使用了一个同名的变量来接收内部的构造函数,多么经典的闭包应用啊。

+ 属性

我们尝试在上面代码的基础上,在这个类中加入属性的概念。

class Class_test {
name:string;
age:number;
constructor(name:string,age:number){
this.name = name;
this.age = age;
}
}

我们为Class_test类新添加了属性的概念( 分别是 nameage ) , 这里其实如果不想写constructor构造函数的话其实可以给 nameage 设置一个初始值也是没问题的。

var Class_test = /** @class */ (function () {
function Class_test(name, age) {
this.name = name;
this.age = age;
}
return Class_test;
}());

可以发现写法跟ES5构造函数的写法一致,注意Class成员的几个修饰符:public protected private 在ES5代码的结构中没有意义,这里不做额外说明。

+ 方法(静态)

我们在上面代码的基础上再加上方法、静态属性、静态方法。

/* 设计类 */
class Class_test {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
getInfo():void{
console.log(`Name === ${this.name} Age === ${this.age}`);
}
/* 静态属性:构造函数(类)自己的属性,访问示例:类名.静态属性; */
/* 静态方法:构造函数(类)自己的方法,访问示例:类名.静态方法名称(); */
static className: string = "Class_test";
static getClassName():void{
console.log("ClassName === "+this.className); //this=>Class_Test
}
} /* 实例化对象 */
let c:Class_test = new Class_test("文顶顶",18);
console.log(c.name,c.age);
c.getInfo();
console.log(Class_test.className);
Class_test.getClassName();

我们为代码添加了静态属性 className、静态方法 getClassName() 、实例方法 getInfo(),通过查看对应的ES5代码,我们会发现这也没什么特别的。

var Class_test = /** @class */ (function () {
function Class_test(name, age) {
this.name = name;
this.age = age;
}
Class_test.prototype.getInfo = function () {
console.log("Name === " + this.name + " Age === " + this.age);
};
Class_test.getClassName = function () {
console.log("ClassName === " + this.className); //this=>Class_Test
};
/* 静态属性:构造函数(类)自己的属性,访问示例:类名.静态属性; */
/* 静态方法:构造函数(类)自己的方法,访问示例:类名.静态方法名称(); */
Class_test.className = "Class_test";
return Class_test;
}());
var c = new Class_test("文顶顶", 18);
console.log(c.name, c.age); /* 文顶顶 18 */
c.getInfo(); /* Name === 文顶顶 Age === 18 */
console.log(Class_test.className); /* Class_test */
Class_test.getClassName(); /* ClassName === Class_test */

关键在于搞清楚成员原型成员静态成员实例成员构造函数原型对象以及实例化的关系。

+ 继承

  /* 设计类(父类) */
class Class_super {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
getInfo():void{
console.log(`Name === ${this.name} Age === ${this.age}`);
}
static className: string = "Class_test";
static getClassName():void{
console.log("ClassName === "+this.className); //this=>Class_Test
}
} /* 设计类(子类) */
class Class_child extends Class_super{
money:number;
constructor(name: string, age: number,money:number) {
super(name,age);
this.money = money;
}
getMoney():void{
console.log(`Money === ${this.money}`)
}
static child_static_func():void{
console.log("测试该方法能否被Class_super方法调用?No")
}
} /* 实例化(对象) */
let child:Class_child = new Class_child("zs",19,100);
console.log(child,child.name,child.age,child.money);
child.getInfo();
child.getMoney();
console.log(Class_child.className);
Class_child.getClassName();
Class_child.child_static_func();
// Class_super.child_static_func(); 错误的示范

如果我们实现了类的继承,那么问题就会变得复杂得多,至少看起来如此。

var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; })
|| function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]};
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})(); /* 设计类(父类) */
var Class_super = /** @class */ (function () {
function Class_super(name, age) {
this.name = name;
this.age = age;
}
Class_super.prototype.getInfo = function () {
console.log("Name === " + this.name + " Age === " + this.age);
};
Class_super.getClassName = function () {
console.log("ClassName === " + this.className); //this=>Class_Test
};
Class_super.className = "Class_test";
return Class_super;
}());
/* 设计类(子类) */
var Class_child = /** @class */ (function (_super) {
__extends(Class_child, _super);
function Class_child(name, age, money) {
var _this = _super.call(this, name, age) || this;
_this.money = money;
return _this;
}
Class_child.prototype.getMoney = function () {
console.log("Money === " + this.money);
};
Class_child.child_static_func = function () {
console.log("测试该方法能否被Class_super方法调用?No");
};
return Class_child;
}(Class_super));
/* 实例化(对象) */
var child = new Class_child("zs", 19, 100);
console.log(child, child.name, child.age, child.money);
/* Class_child { name: 'zs', age: 19, money: 100 } 'zs' 19 100 */
child.getInfo(); /* Name === zs Age === 19 */
child.getMoney(); /* Money === 100 */
console.log(Class_child.className); /* Class_test */
Class_child.getClassName(); /* ClassName === Class_test */
Class_child.child_static_func(); /* 测试该方法能否被Class_super方法调用?No */
// Class_super.child_static_func(); 错误的示范

蒽,是的。看上去,TS ( ES6 ) 中感觉Class继承的实现稍显复杂。接下来,我们试着挑出关键的部分,并把比较重要(表面感觉不太看得懂)的那部分代码加上些注释。

关键 ①

var _this = _super.call(this, name, age) || this;

这行代码的目的是获取父类的实例成员,具体采用的技术手段是通过借用构造函数调用( 在子构造函数中以call方法来调用父构造函数,并绑定this)的方式来获取。此处,拿到的是name age两个属性(当我们通过子类来实例化创建对象的时候,得到的实例化对象中因此拥有了nameage 属性)。

关键 ②

__extends(Class_child, _super);

这行代码是一个函数调用,其中__extends是函数名称,而Class_child, _super是传递给函数的具体实参。

该函数的作用是,通过特定的方式来获取父类(构造函数)的静态成员(包括静态属性以及静态方法)并让实例对象可以通过原型链来访问父类原型对象上面的成员(属性和方法)。

/* @Description: __extends主要用于处理(静态成员)的继承
* @return: 函数形态 function f(_child,_super){} */
var __extends = (this && this.__extends) || (function() {
/* @Description: extendStatics用于扩展静态成员(静态属性和静态方法)
* param __child 子类(Class_child)
* param __super 父类(Class_super)*/
var extendStatics = function(__child, __super) {
/* 1.确定函数 */
/* extendStatics变量的值总是为一个函数,该函数的目的是_super的属性和方法(静态)*/
/* 核心策略 */
/* ① 尝试直接使用 Object.setPrototypeOf函数 类似于__child.__proto__ = __super */
/* ② 直接通过__child.__proto__ = __super来设置 */
/* ③ 通过遍历的方式来拷贝_super对象的实例成员 */
extendStatics = Object.setPrototypeOf
||({ __proto__: [] } instanceof Array
&& function(__child, __super) {
__child.__proto__ = __super;
})
||function(d, b) {
for (var p in b) {
if (b.hasOwnProperty(p)) d[p] = b[p]; }
};
/* 2.调用函数(完成继承) */
return extendStatics(__child, __super);
};
return function(_child, _super) {
/* [1] 完成对父类(构造函数)静态成员(属性+方法)的拷贝工作 */
extendStatics(_child, _super); /* [2] 完成对父类(构造函数)原型成员(主要是方法)的原型链继承 */
/* 内部的this取决于__()函数如何调用,使用new调用则this指向的是内部新创建的实例对象 */
function __() { this.constructor = _child; } /* 如果_spuer为null,那么就创建一个新对象*/
/* 设置该对象的原型对象为_super[let o = {}; o.__prototype = _super] */
/* 并把最终结果赋值给_child.prototype, 否则就间接通过__()这个函数来设置原型对象*/
/* 并创建实例化对象,并设置 _child.prototype = new __() */
_child.prototype =
_super === null ?
Object.create(_super) :
(__.prototype = _super.prototype, new __());
};
})();

我在代码中穿插并加上了一些说明性的注释 ,看上去似乎比较复杂,稍微总结下:

__extends() 函数做了两件事情:
[1] 完成对父类(构造函数)静态成员(属性+方法)的拷贝工作
[2] 完成对父类(构造函数)原型成员(主要是方法)的原型链继承
如何做的?
[1] child.__proto__ = super
[2] child.prototype = super.prototype; + 解决共享问题(中间加了间隔层)

+ Interface

接口( interface )是 typescript 中的重要概念,而且接口也能继承且可以约束类,因此这里简单的比较下,下面给出示例代码以及对应的JavaScript代码。

/* 1.定义接口(父) */
interface Test_Class_Super_Interface {
name: string;
age: number;
getName: () => void;
getAge: () => void;
} /* 2.定义接口(子) */
interface Test_Class_Child_Interface extends Test_Class_Super_Interface{
id:number;
getId:()=>void;
} /* 3.设计类:该类实现指定的接口 */
class Test_Class implements Test_Class_Child_Interface {
id: number = 0;
name: string;
age: number;
getName(): void {
console.log("getName() " + this.name)
};
getAge(): void {
console.log("getAge() " + this.age)
};
getId():void{
console.log(this.id);
}
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
/* 4.创建实例化对象 */
let instance: Test_Class = new Test_Class("ls", 18);
console.log(instance);

上面代码设计了两个接口,分别是Test_Class_Super_InterfaceTest_Class_Child_Interface,它们是继承关系,Test_Class 类实现了Test_Class_Child_Interface接口。

var Test_Class = /** @class */ (function() {
function Test_Class(name, age) {
this.id = 0;
this.name = name;
this.age = age;
}
Test_Class.prototype.getName = function() {
console.log("getName() " + this.name);
};;
Test_Class.prototype.getAge = function() {
console.log("getAge() " + this.age);
};;
Test_Class.prototype.getId = function() {
console.log(this.id);
};
return Test_Class;
}());
var instance = new Test_Class("ls", 18);
console.log(instance);

我们发现转换后 JavaScript版本的代码里面好像没有任何与接口相关的信息,接口是 typescript 中特有的类型, 在编译后会自动消失。

javaScript系列 [43]-TS、Class and ES5的更多相关文章

  1. 深入理解JavaScript系列(43):设计模式之状态模式

    介绍 状态模式(State)允许一个对象在其内部状态改变的时候改变它的行为,对象看起来似乎修改了它的类. 正文 举个例子,就比如我们平时在下载东西,通常就会有好几个状态,比如准备状态(ReadySta ...

  2. 深入理解JavaScript系列

    转自http://www.cnblogs.com/TomXu/archive/2011/12/15/2288411.html 深入理解JavaScript系列(1):编写高质量JavaScript代码 ...

  3. 深入理解JavaScript系列(1):编写高质量JavaScript代码的基本要点

    深入理解JavaScript系列(1):编写高质量JavaScript代码的基本要点 2011-12-28 23:00 by 汤姆大叔, 139489 阅读, 119 评论, 收藏, 编辑 才华横溢的 ...

  4. 深入理解JavaScript系列(转自汤姆大叔)

    深入理解JavaScript系列文章,包括了原创,翻译,转载,整理等各类型文章,如果对你有用,请推荐支持一把,给大叔写作的动力. 深入理解JavaScript系列(1):编写高质量JavaScript ...

  5. [转]深入理解JavaScript系列

    文章转自:汤姆大叔-深入理解JavaScript系列文章 深入理解JavaScript系列文章,包括了原创,翻译,转载,整理等各类型文章,如果对你有用,请推荐支持一把,给大叔写作的动力. 深入理解Ja ...

  6. javascript系列之this

    原文:javascript系列之this 引言 在这篇文章里我们将会讨论与执行上下文直接相关的更多细节.讨论的主题就是this关键字.实践证明,这个主题是足够难的并且在不同的执行上下文中判定this的 ...

  7. javascript系列之变量对象

    原文:javascript系列之变量对象 引言 一般在编程的时候,我们会定义函数和变量来成功的构造我们的系统.但是解析器该如何找到这些数据(函数,变量)呢?当我们引用需要的对象时,又发生了什么了? 很 ...

  8. JavaScript 系列博客(三)

    JavaScript 系列博客(三) 前言 本篇介绍 JavaScript 中的函数知识. 函数的三种声明方法 function 命令 可以类比为 python 中的 def 关键词. functio ...

  9. JavaScript 系列博客(一)

    JavaScript 系列博客(一) 前言 本系列博客为记录学习 JavaScript 的学习笔记,会从基础开始慢慢探索 js.今天的学习笔记主要为 js 引入.定义变量以及 JavaScript 中 ...

随机推荐

  1. oracle keep

    语法: min | max(column1) keep (dense_rank first | last order by column2) over (partion by column3); -- ...

  2. JPA和事务管理

    JPA和事务管理 很重要的一点是JPA本身并不提供任何类型的声明式事务管理.如果在依赖注入容器之外使用JPA,事务处理必须由开发人员编程实现. 123456789101112UserTransacti ...

  3. 【编程思想】【设计模式】【结构模式Structural】桥梁模式/桥接模式bridge

    Python版 https://github.com/faif/python-patterns/blob/master/structural/bridge.py #!/usr/bin/env pyth ...

  4. KVM配置

    安装依赖包(因最小化安装) [root@slave-master ~]# yum install -y vim wget tree lrzsz gcc gcc-c++ automake pcre pc ...

  5. ExecutorService 线程池详解

    1.什么是ExecutorService,为什么要使用线程池? 许多服务器应用程序都面向处理来自某些远程来源的大量短小的任务,每当一个请求到达就创建一个新线程,然后在新线程中为请求服务,但是频繁创建新 ...

  6. C/C++语言结构体指针的使用

    C/C++语言结构体指针的使用 主要内容 结构体的使用 - 定义,赋值,结构体指针 结构体作为函数参数的使用 指针的使用 代码内容重点 结构体的使用 - 定义,赋值,结构体指针 结构体作为函数参数的使 ...

  7. centos k3s部署

    目录 一.k3s介绍 二.在线安装 三.离线安装 四.高可用安装 五.配置k3s镜像仓库 六.Kubernetes 仪表盘 七.常用命令 八.参考 一.k3s介绍 1.k3s是一个轻量级的 Kuber ...

  8. jenkins实例 nodejs项目

    目录 一.案例1 二.案例2 一.案例1 使用shell方式 #清理上一次版本,拉取新代码 rm -rf /server/admin-web cd /server git clone http://g ...

  9. Axios的正确食用方法

    这里分享出我个人封装的一个axios,我会尽量每行注释,希望对大家有所帮助. 1. 安装 全局执行代码 npm install axios; 2. 编写全局axios文件(附件里有代码) 在src目录 ...

  10. 使用Redis+自定义注解实现接口防刷

    最近开发了一个功能,需要发送短信验证码鉴权,考虑到短信服务需要收费,因此对此接口做了防刷处理,实现方式主要是Redis+自定义注解(需要导入Redis的相关依赖,完成Redis的相关配置,gs代码,这 ...