浅谈 TypeScript 特性

来源:互联网 发布:矩阵的几是定值吗 编辑:程序博客网 时间:2024/06/05 18:05

浅谈 TypeScript 特性


*readonly

用来约束一个对象的属性, 在对象刚创建时能赋值, 往后赋值均会报错, 在类中使用 readonly 只能在构造函数中赋初始值。

interface Point {
readonlyx:number;
readonlyy:number;
}
const p:Point = {x:10,y:20 };
p.x = 5; // error [ts] Cannot assign to 'x' because it is a constant or a read-only property.!

*ReadonlyArray<T>

创建一个不可变的数组, 谈 ReadonlyArray<T> 的目的只是说明一下 typescript 会内置了一些实用的接口供开发者使用。

let a:number[] = [1,2,3,4];
let ro:ReadonlyArray<number> =a;
ro.push(2);// error [ts] Property 'push' does not exist on type 'ReadonlyArray<number>'.
a.push(2);
a = ro; // error [ts] Type 'ReadonlyArray<number>' is not assignable to type 'number[]'
a = roasstring[];// error [ts] Type 'ReadonlyArray<number>' cannot be converted to type 'string
a = roasany[];// ok 类型断言重写 但是如下依旧会报错
a = roasnumber[];// ok 类型断言重写 但是如下依旧会报错
ro.push(2);// error [ts] Property 'push' does not exist on type 'ReadonlyArray<number
a.push(2);


*类型断言

通过上面的 as 和 下面的几个 as 类型断言, 大概可判断类型断言 x as y, x 类型约束比 y 要更细致, 便能成功(纯属个人理解, 官方具体定义很飘渺不是很懂)

// ok
interface SquareConfig {
color?:string;
width?:number;
}
function createSquare(config:SquareConfig) {
// ...
}
let mySquare =createSquare({width:100,opacity:0.5 }as SquareConfig);
// error [ts] Type '{ width: number; opacity: number; }' cannot be converted to type 'SquareConfig'.
interface SquareConfig {
color:string;
width:number;
}
function createSquare(config:SquareConfig) {
// ...
}
let mySquare =createSquare({width:100,opacity:0.5 }as SquareConfig);// ok
interface SquareConfig {
color:string;
width:number;
}
function createSquare(config:SquareConfig) {
// ...
}
let mySquare =createSquare({width:100,opacity:0.5,color:"my name is string" }as SquareConfig);
// 更好的方法 这样定义接口 SquareConfig 添加其他参数不会报错了
interface SquareConfig {
color?:string;
width?:number;
[propName:string]:any;
}


*类类型接口

当你操作类和接口的时候,你要知道类是具有两个类型的:静态部分的类型和实例的类型。

如下例子, 类一般实现的约束实例部分的接口, 当需要使用 new 关键字实例化的时候才会对其静态部分进行检查

interface ClockConstructor {
new (hour:number,minute:number):ClockInterface; //ok
// new (hour:number,minute:number)ok
}
interface ClockInterface {
tick();
}

function createClock(ctor:ClockConstructor,hour:number,minute:number):ClockInterface {
returnnewctor(hour,minute);
}

class DigitalClockimplementsClockInterface {
constructor(h:number,m:number) { }
tick() {
console.log("beep beep");
}
}

let digital =createClock(DigitalClock,12,17);

*构造函数工厂制造类

js版本构造函数工厂制造类

function counter(){
}
counter.interval =123;
counter.reset =function () { };
在 ts 中需要如下约束

interface Counter {
(start:number):string;
interval:number;
reset():void;
}
function getCounter():Counter {
let counter = <Counter>function (start:number) { };
counter.interval =123;
counter.reset =function () { };
returncounter;
}
let c =getCounter();
c(10);
c.reset();
c.interval =5.0

*private 与 protected

如下的例子可以看出, private修饰 不能在实例中和子类中访问; protected能在子类中访问。

class Person {
private name:string;
constructor(name:string) {this.name =name; }
}

class EmployeeextendsPerson {
private department:string; //private

constructor(name:string,department:string) {
super(name)
this.department =department;
}

public getElevatorPitch() {
return `Hello, my name is${this.name} and I work in${this.department}.`;
// [ts] Property 'name' is private and only accessible within class 'Person'.
}
}

let howard =newEmployee("Howard","Sales");
console.log(howard.getElevatorPitch());
console.log(howard.name);// error

class Person {
protected name:string; //protected
constructor(name:string) {this.name =name; }
}

class EmployeeextendsPerson {
private department:string;

constructor(name:string,department:string) {
super(name)
this.department =department;
}

public getElevatorPitch() {
return `Hello, my name is${this.name} and I work in${this.department}.`;
// ok
}
}

let howard =newEmployee("Howard","Sales");
console.log(howard.getElevatorPitch());
console.log(howard.name);// error

* 不一样的set get

需要提醒的别和 java 中的 getFullName, setFullName 语法弄混!在 ts 中 中间上有一个空格的存在。

class Employee {
private _fullName:string;
get fullName():string {
return this._fullName;
}
set fullName(newName:string) {
// 如果满足一定条件才能修改值成功
}
}
let employee =newEmployee();
employee.fullName ="Bob Smith";

*类的实例部分与静态部分

在类类型接口中其实我们就谈过这部分内容, 声明一个类其实也就声明类的静态和实例部分。

知识点:

typeof Greeter 取的是 Greeter类静态部分的类型, 

那么如下例子中 greeterMaker类 只能访问其静态成员。 

static standardGreeting ="Hello, there";
constructor() {
}
greeting: string;
greet() {
if (this.greeting) {
return "Hello, " +this.greeting;
}
else {
return Greeter.standardGreeting;
}
}
}
interface StaticClass {// 在 类类型声明中 就谈过的在 类实例化时 对类静态成员就行检查
new ();
standardGreeting: string;
}
function createGreeter (ctr:StaticClass):Greeter {
return newctr();
}
let greeter1:Greeter;
greeter1 = createGreeter(Greeter);
console.log(greeter1.greet());
let greeterMaker:typeofGreeter =Greeter;
// 从这可以知道 typeof 取的是类的静态部分的类型 typeof Greeter 相当于语句 type a = typeof Greeter; let greeterMaker: a = Greeter;
greeterMaker.standardGreeting ="Hey there!";
let greeter2:Greeter =newgreeterMaker();
console.log(greeter2.greet());
// 既然 typeof 取的是类的静态部分的类型 那么 interface StaticClass 也是对静态部分进行约束检查的, 果不其然下面的语句是成立相等的 !
let clasA:StaticClass;
let clasB:typeofGreeter;
clasB = clasA;
clasA = clasB;

* this

学习使用JavaScript里this就好比一场成年礼, 下面简要说一下typescript里面的this。

可以看出在嵌套return function 里面 this 指向不再是deck 对象, 而是windows, 故报错!

var deck = {
suits: ["hearts","spades","clubs","diamonds"],
cards: Array(52),
createCardPicker: function () {
return function () {
var pickedCard =Math.floor(Math.random() *52);
var pickedSuit =Math.floor(pickedCard /13);
return { suit:this.suits[pickedSuit],card:pickedCard %13 };
};
}
};
var cardPicker =deck.createCardPicker();
var pickedCard =cardPicker();
alert("card: " +pickedCard.card +" of " +pickedCard.suit);
// Uncaught TypeError: Cannot read property '0' of undefined
在上面的情况下, 我们便需要利用typescript的特性, 以定义接口的形式, 对变量, 参数...加以约束, this 也需要显示声明其类型, 才不会在类型问题上出错 。

interface Card {
suit: string;
card: number;
}
interface Deck {
suits: string[];
cards: number[];
createCardPicker(this:Deck): ()=>Card;
}
let deck:Deck = {
suits: ["hearts","spades","clubs","diamonds"],
cards: Array(52),
// NOTE: The function now explicitly specifies that its callee must be of type Deck
createCardPicker: function(this: Deck) {
return () => {
let pickedCard =Math.floor(Math.random() *52);
let pickedSuit =Math.floor(pickedCard /13);

return {suit:this.suits[pickedSuit],card:pickedCard %13};
}
}
}
let cardPicker =deck.createCardPicker();
let pickedCard =cardPicker();
alert("card: " +pickedCard.card +" of " +pickedCard.suit);

* 箭头函数和 this

如下例子会报错, 因为传入的 h.onClickBad 仅仅被当作一个函数, 此时里面的 this 已经丢失了

class Handler {
info: string;
onClickBad (e:string) {
this.info =e;
}
}
let h =newHandler();
function ss(cb) {
cb("sss")
}
ss(h.onClickBad);
console.log(h.info)
当然如果这样定义 ss 函数是没问题的, 此时 this 不会丢失, 但是这样更改 ss 函数是不是相当于改了需求?这是不合理的方式没有从源头解决问题。

function ss(){
h.onClickBad("sss")
}
此时来看看箭头优势, 改写一下 Handler onClickBad为箭头函数方式。

class Handler {
info: string;
onClickBad = (e:string) =>{
this.info =e;
}
}
为什么改成这样不会丢失 this , 我们可以看看箭头函数干了什么事, 当编译成 js 文件时

var Handler = (function () {
function Handler() {
var _this =this;
this.onClickBad =function (e) {
_this.info =e;
};
}
return Handler;
}());
var h =newHandler();
function ss(cb) {
cb("sss");
}
ss(h.onClickBad);
console.log(h.info);
是的, 正如我们平时书写 js 文件时, 为了防止 this 丢失我们通常也是用变量存储, 箭头函数其实就在帮我们干这事。

* 重载

typescript 中的重载无力吐槽, 没有使用的冲动。if else 嵌套去选择性调用, 极度不友好, java等强类型重载机制就清晰明了多了。

let suits = ["hearts","spades","clubs","diamonds"];
function pickCard(x: {suit:string;card:number; }[]):number;
function pickCard(x:number): {suit:string;card:number; };
function pickCard(x):any {
// Check to see if we're working with an object/array
// if so, they gave us the deck and we'll pick the card
if (typeofx =="object") {
let pickedCard =Math.floor(Math.random() *x.length);
return pickedCard;
}
// Otherwise just let them pick the card
else if (typeofx =="number") {
let pickedSuit =Math.floor(x /13);
return { suit:suits[pickedSuit],card:x % 13 };
}
}

* 泛型 与 类 类型

下面的例子也证明了之前说的几个知识点

1. 声明一个类, 实际上也声明了其 实例部分和静态部分, 所以类也能作为接口使用(因为有实例部分属性的约束检查和接口的属性检查原理是一致的)

2. 对一个等于函数的变量约束有两种方式

a. 

const a: (name:string)=> string = (name:string)=> {
return name
}

b.

const a: {(name:string):string} = (name:string)=> {
return name
}

3. c: new (name: string)=> A , 对参数c的约束在类 类型中也谈过, 实际上是对静态部分进行约束检查, new (name: string)=> A 也可以写出 { (name: string): A }  。

class Animal {
numLegs: number;
}
class LionextendsAnimal {
constructor(publicname:string){
super()
}
keeper: "hello word!";
}
function createInstance<AextendsAnimal>(c:new (name:string)=>A):A {
return newc(name);
}
createInstance<Lion>(Lion).keeper;

* 枚举

使用枚举我们可以定义一些有名字的数字常量

1. 如下, 为普通枚举

enum FileAccess {
// constant members
None,
Read = 1 <<1,
Write = 1 <<2,
ReadWrite = Read |Write,
// computed member
G = "123".length
}
将会编译成一个很有意思的对象, 具有反向映射的特点, 看看代码就知道这个对象的特点了

var FileAccess;
(function (FileAccess) {
// constant members
FileAccess[FileAccess["None"] =0] = "None";
FileAccess[FileAccess["Read"] =2] = "Read";
FileAccess[FileAccess["Write"] =4] = "Write";
FileAccess[FileAccess["ReadWrite"] =6] = "ReadWrite";
// computed member
FileAccess[FileAccess["G"] ="123".length] ="G";
})(FileAccess || (FileAccess = {}));
相当于 声明一个如下的对象

var FileAccess ={
"None":0,
0: "None"
}
2. 如下为常数枚举

如下例子用const 修饰, 不能有需要计算的成员, 如 G = "123".leng 等是需要计算出结果的

让我们看看编译的结果

const enumFileAccess2 {
None,
Read = 1 <<1,
Write = 1 <<2,
ReadWrite = Read |Write,
// G = "123".length [ts] In 'const' enum declarations member initializer must be constant expression.
}
const a = FileAccess2.ReadWrite

让我们看看编译的结果,是的 常数枚举在编译时会被删除

var a = 6 /* ReadWrite */;
3. 外部枚举
使用 declare 修饰, declare 一般用在声明文件中 如放在global.d.ts中进行全局声明
declare enumFileAccess3 {
None,
Read = 1 <<1,
Write = 1 <<2,
ReadWrite = Read |Write,
// G = "123".length [ts] In 'const' enum declarations member initializer must be constant expression.
}
const a2 = FileAccess3.ReadWrite
编译成
var a2 = FileAccess3.ReadWrite;