本文讨论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 中特有的类型, 在编译后会自动消失。

前端开发系列130-进阶篇之TS、Class and ES5的更多相关文章

  1. openlayers5-webpack 入门开发系列一初探篇(附源码下载)

    前言 openlayers5-webpack 入门开发系列环境知识点了解: node 安装包下载webpack 打包管理工具需要依赖 node 环境,所以 node 安装包必须安装,上面链接是官网下载 ...

  2. leaflet-webpack 入门开发系列一初探篇(附源码下载)

    前言 leaflet-webpack 入门开发系列环境知识点了解: node 安装包下载webpack 打包管理工具需要依赖 node 环境,所以 node 安装包必须安装,上面链接是官网下载地址 w ...

  3. 【Windows10 IoT开发系列】配置篇

    原文:[Windows10 IoT开发系列]配置篇 Windows10 For IoT是Windows 10家族的一个新星,其针对不同平台拥有不同的版本.而其最重要的一个版本是运行在Raspberry ...

  4. ESP8266开发之旅 进阶篇② 闲聊Arduino IDE For ESP8266烧录配置

    授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...

  5. 【webpack 系列】进阶篇

    本文将继续引入更多的 webpack 配置,建议先阅读[webpack 系列]基础篇的内容.如果发现文中有任何错误,请在评论区指正.本文所有代码都可在 github 找到. 打包多页应用 之前我们配置 ...

  6. iOS开发系列--Swift进阶

    概述 上一篇文章<iOS开发系列--Swift语言>中对Swift的语法特点以及它和C.ObjC等其他语言的用法区别进行了介绍.当然,这只是Swift的入门基础,但是仅仅了解这些对于使用S ...

  7. 旨在脱离后端环境的前端开发套件 - IDT Server篇

    IDT,一个基于Nodejs的,旨在脱离后端环境的前端开发套件,目的就是能让前端开发完全脱离后端的环境,无论后端是什么模板引擎(主流),都能应付自如. IDT主要包括两大部分:Server + Bui ...

  8. 前端开发【第2篇:CSS】

    鸡血 样式的属性多达几千个,但别担心,按照80-20原则,常用的也就几十个,你完全可以掌握它. Css初识 HTML的诞生 早期只有HTML的时候为了让HTML更美观一点,当时页面的开发者会把颜色写到 ...

  9. [置顶]【实用 .NET Core开发系列】- 导航篇

    前言 此系列从出发点来看,是 上个系列的续篇, 上个系列因为后面工作的原因,后面几篇没有写完,后来.NET Core出来之后,注意力就转移到了.NET Core上,所以再也就没有继续下去,此是原因之一 ...

  10. openlayers4 入门开发系列之风场图篇

    前言 openlayers4 官网的 api 文档介绍地址 openlayers4 api,里面详细的介绍 openlayers4 各个类的介绍,还有就是在线例子:openlayers4 官网在线例子 ...

随机推荐

  1. Greenplum数据库时间操作汇总

    Greenplum数据库时间操作与mysql有一些区别,汇总以往笔记记录下来. greenplum时间格式:'yyyy-mm-dd hh24:mi:ss.us'.'yyyy-mm-dd hh:mi:s ...

  2. python,指定目录下创建自定义名称文件夹的方法

    比如,我们需要在D盘TU目录下创建名称为"test"的文件夹 脚本如下: import os path = r'D://tu' a = "test" os.mk ...

  3. 康谋分享 | 确保AD/ADAS系统的安全:避免数据泛滥的关键!

    为确保AD/ADAS系统的安全性,各大车企通常需要收集.处理和分析来自于摄像头.激光雷达等传感器的数据,以找出提高系统安全性和性能的方法.然而在数据收集过程中,不可避免地会出现大量无价值数据,造成数据 ...

  4. 如何处理 MySQL 的主从同步延迟?

    如何处理 MySQL 的主从同步延迟? MySQL 的主从同步延迟通常是由主库与从库之间的网络延迟.从库的处理能力.主库的写操作量等因素导致的.长时间的同步延迟可能会影响系统的稳定性和数据一致性.以下 ...

  5. macOS终端修改DNS

    以WiFi为例 networksetup -listallnetworkservices networksetup -setdnsservers Wi-Fi 8.8.8.8 networksetup ...

  6. git仓库常用git命令&极速入门教程

    简易的命令行入门教程: Git 全局设置: git config --global user.name "yourName" git config --global user.em ...

  7. jstree上手文档 [初始化时默认选中、全部展开、获取实例的数据等问题]

    jstree官网:https://www.jstree.com/ ------------------- 实例化tree .jstree() 默认样式: var container = $('#xxx ...

  8. qwen3 惊喜发布,用 ollama + solon ai (java) 尝个鲜

    qwen3 惊喜发布了,帅!我们用 ollama 和 solon ai (java) 也来尝个鲜. 1.先用 ollama 拉取模型 听说,在个人电脑上用 4b 的参数,效果就很好了. ollama ...

  9. 【HUST】网安|软件安全课设|记录

    仓库链接 clone之后点开html文件即可使用. 效果如下图: 文章目录 进程通信设计 共享内存(Windows) 初始化共享内存 修改和读取共享内存的内容 共享内存(linux) (尝试使用,但使 ...

  10. 消息验证码(MAC)的介绍

    目录 认证流程 MAC 的分类 认证加密 MAC的攻击手段 重发攻击 密钥推测攻击 消息认证码无法解决的问题 Reference 消息验证码(MAC)也可以称为消息认证码. 定义: 消息验证码(Mes ...