// -------- Classes(类) --------
// 传统的javascript依赖在函数和原型链(prototype)作为基础概念来构建可重用的部件
// 但是对于习惯了面对对象编程(class继承功能,对象由class来生成)的程序员来说,还是有点麻烦
// 从ES6开始,下一个版本的javascript,js程序员也可以使用面对对象编程
// 在typescript,我们允许开发者现在就使用这些技术,然后把他们编译成javascript,在各个浏览器和平台上运行,而不用等待下一个版本的javascript
// 让我们来看下基于class的例子
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello," + this.greeting;
}
}
var greeter = new Greeter("world");
// 如果你用过c#或者java,你会觉得语法很类似.我们声明一个叫"Greeter"的新class.
// 这个class有3个成员,分别是一个叫"greeting"的属性,一个构造函数和一个'greet'的方法
// 你会发现在class里,当我们引用一个成员的时候,会在前面加上'this'.这表示一个成员操作
// 在最后一行,我们使用'new'关键字来构造一个Greet的实例.
// 它会调用我们早些定义的constructor,创建一个Greet的'Shape'的对象,然后用constructor初始化它
// -------- Inheritance --------
// 在typescript中,我们可以使用通用的面对对象模式.
// 面对对象编程中,一个根本模式是通过继承,从一个已有的class生成一个新的class
// 例子
class Animal {
name: string;
constructor(theName: string) { this.name = theName; }
move(meters: number = 0) {
alert(this.name + " moved " + meters + "m.");
}
}
class Snake extends Animal {
constructor(name: string) { super(name); }
move(meters = 5) {
alert("Slithering...");
super.move(meters);
}
}
class Horse extends Animal {
constructor(name: string) { super(name); }
move(meter = 45) {
alert("Galloping");
super.move(meter);
}
}
var sam = new Snake("sammy the python");
var tom: Animal = new Horse("tommy the palomino");
sam.move();
sam.move(34);
// 这个例子涵盖了相当多的typescript的继承特性,这些特性也在其他语言中非常通用
// 这里我们使用'extends'关键字来建立subclass(子类)
// 你可以看到'Horse'和'Snake'从'Animal'这个class里获取它的特性
// 这个例子也展示了如何'覆盖'(override)父类的方法,而后在子类里重写
// 'Snake'和'Horse'建立了个move方法来重写父类'Animal'方法,从而给与各自class以特定功能
// -------- Private/Public modifiers(私有/公有 修饰符) --------
// 默认是public
// 你可能已经注意到,我们没有使用'public'关键字来使class的成员可见(向外暴露)
// 类似在c#语言中,class的成员如果要可见,则必须显式的用上'public'这个标签来修饰
// 而在typescript中,默认每个成员就是public
// 你依旧可以标记成员为private,这样你就控制class的什么成员是对外可见.
// 我们可以这样重写Animal类
class Animal2 {
private name: string;
constructor(theName: string) { this.name = theName; }
move(meters: number) {
alert(this.name + " moved " + meters + "m.");
}
}
// -------- Understanding private(理解私有) --------
// typescript是个结构化类型的系统
// 当我们比较2个不同的类型,不考虑他们来自哪里,只要他们的类型是compatible(兼容的),我们就说他们是compatible
// 当我们比较带有'private'标签的成员的类型时候,我们不同对待
// 两个类型被认为是compatible,如果其中一个有private成员,那么另一个必须有同样声明的private成员
// 例子
class Animal3 {
private name: string;
constructor(theName: string) { this.name = theName; }
}
class Rhino extends Animal3 {
constructor() { super("Rhino"); }
}
class Employee {
private name: string;
constructor(theName: string) { this.name = theName; }
}
var animal = new Animal3("Goat");
var rhino = new Rhino();
var employee = new Employee("Bob");
animal = rhino;
// animal = employee; //error: Animal and Employee are not compatible
// 在这个例子里,我们有2个类'Animal'和'Rhino',其中'Rhino'是'Animal'的子类.
// 我们还有一个看上去跟'Animal'完全一致的class'Employee'
// 我们创建他们的实例,来相互赋值,看看会发生什么
// 'Animal'跟'Rhino'共享了他们在'Animal'中定义的私有变量name,所以他们是兼容的
// 但是,这不适用于'Employee'
// 当我们想把从'Employee'中生成的实例赋予'Animal'类型变量时,我们得到'不可兼容'的错误
// 即便'Employee'有着跟'Animal'一样的私有成员name,但是他不是一个从'Animal'生成的
// -------- Parameter properties(参数属性) --------
// 使用public和private关键字来修饰构造函数的参数,可以一步定义和初始化类成员
// 这里有对上述例子的重构
// 请注意, 通过'private name:string',将这个'name'运用到构造函数里, 'theName'被简化了
class Animal5 {
constructor(private name: string) { }
move(meters: number) {
alert(this.name + " moved " + meters + "m.");
}
}
// 这样的方式使用private关键字就生成一个private成员,public同理
// -------- Accessors(访问器) --------
// typescript支持getters/setters来操作对象成员,这提供了更好的细粒度操作
// 让我们通过一个简单的class来过以下get和set
// 首先让我们来看一个没有get和set的例子
class Employee2 {
fullName: string;
}
var employee2 = new Employee2();
employee2.fullName = "Bob Smith";
if (employee2.fullName) {
alert(employee2.fullName);
}
// 这样让人们直接的设置'fullName'固然便捷,但是人们随意改变值也会带来麻烦
// 在这个版本里,我们会检测密码当他们想修改值的时候
// 我们通过set来添加了检测密码的功能,从而改变直接设置'fullName'这种方式
// 我们增加了get,让上述例子工作更天衣无缝
var passcode = "secret passcode";
class Employee3 {
private _fullName: string;
get fullName(): string {
return this._fullName;
}
set fullName(newName: string) {
if (passcode && passcode == "secret passcode") {
this._fullName = newName;
}
else {
alert("Error: Unauthorized update of employee!");
}
}
}
var employee3 = new Employee3();
employee3.fullName = "Bob Smith";
if (employee3.fullName) {
alert(employee3.fullName);
}
/*
编译结果
var Employee3 = (function () {
function Employee3() {
}
Object.defineProperty(Employee3.prototype, "fullName", {
get: function () {
return this._fullName;
},
set: function (newName) {
if (passcode && passcode == "secret passcode") {
this._fullName = newName;
}
else {
alert("Error: Unauthorized update of employee!");
}
},
enumerable: true,
configurable: true
});
return Employee3;
})();
*/
// 要验证我们的访问器是不是在检测密码,我们可以修改passcode变量使之不匹配,从而应该看到警告框提醒我们没有修改employee
// 备注:访问器的实现需要你的js编译器不低于ES5
// 笔者:typescript默认js编译器为ES3,所以编译时候也写成 tsc class.ts -t ES5
// -------- Static Properties(静态属性) --------
// 目前为止,我们只讨论了实例对象的实例成员
// 我们也可以建立class的静态成员,静态成员对class可见,而不是实例(即只能用class去访问)
// 这个例子里,我们用'static'来修饰origin成员,这样origin变量的值对于所有Grid的实例都是一样的
// 每个实例对象在访问它的时候,前面要加上类名
// 类似的用'this.'来访问实例成员,这里我们用"Grid."来访问静态成员
class Grid{
static origin = {x:0,y:0};
calculateDistanceFromOrigin(point:{x:number,y:number}){
var xDist = (point.x - Grid.origin.x);
var yDist = (point.y - Grid.origin.y);
return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
}
constructor(public scale:number){}
}
var grid1 = new Grid(1.0);
var grid2 = new Grid(5.0);
alert(grid1.calculateDistanceFromOrigin({x: 10, y: 10}));
alert(grid2.calculateDistanceFromOrigin({x: 10, y: 10}));
// -------- Advanced Techniques(进阶) --------
// 当你定义了一个typescript的class,你也同时做了许多声明
// 第一个就是class的实例的类型
class Greeter1 {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
var greeter: Greeter1;
greeter = new Greeter1("world");
alert(greeter.greet());
// 这里,当我们说'var greeter: Greeter',我们在用'Greeter'作为Greeter这个类的实例的类型
// 这对于面对对象编程的程序员来说,是第二天性
// 我们也用'constructor'函数去创建了另外一个值
// 当我们new出一个class的实例的时候,这个函数就会被调用
// 看看在实战它会成为个什么样子,让我们看看上述代码生成的js代码
/*
var Greeter = (function () {
function Greeter(message) {
this.greeting = message;
}
Greeter.prototype.greet = function () {
return "Hello, " + this.greeting;
};
return Greeter;
})();
var greeter;
greeter = new Greeter("world");
alert(greeter.greet());
*/
// 这里,'var Greeter'将被指向constructor函数
// 当我们调用new,来执行这个函数的时候,我们得到这个class的实例
// 这个constructor函数也包含了所有这个class的静态成员
// 另一种来考虑class的方式是,class包含了它的实例部分和静态部分
// 让我们修改下例子,来展示差别
class Greeter2 {
static standardGreeting = "Hello, there";
greeting: string;
greet() {
if (this.greeting) {
return "Hello, " + this.greeting;
}
else {
return Greeter2.standardGreeting;
}
}
}
var greeter1: Greeter2;
greeter1 = new Greeter2();
alert(greeter1.greet());
var greeterMaker: typeof Greeter2 = Greeter2;
greeterMaker.standardGreeting = "Hey there!";
var greeter2:Greeter2 = new greeterMaker();
alert(greeter2.greet());
// 这个例子里,'greeter1'跟我们前面实例化'Greet'一样,使用this这个我们前面见过的object
// 接下来,我们直接使用这个class
// 这里我们创建一个变量'greeterMaker',这个变量指向class自己,或者说指向它的constructor
// 这里,我们使用'typeof Greeter',意思是给我Greeter这个class的类型,而不是实例的类型
// 或者更加精确的说,'给我Greeter的类型',也就是constructor函数的类型
// 这个类型包括了Greeter类的所有静态成员连同所有用constructor生成的实例成员
// 我们new一个greeterMaker来展示
// -------- Using a class as an interface(把类当作接口用) --------
// 如我们在上一节说的,class定义做了两件事情
// 1 一个用来代表class实例的类型
// 2 一个constructor函数
// 因为class创建了类型,所以你在用class的地方,你也可以使用接口来代替
class Point {
x: number;
y: number;
}
interface Point3d extends Point {
z: number;
}
var point3d: Point3d = {x: 1, y: 2, z: 3};
typescript[3]-class
最新推荐文章于 2024-07-08 14:20:24 发布