浅谈 TypeScript 特性

浅谈 TypeScript 特性


*readonly

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

interface Point {
readonly x: number;
readonly y: 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 = ro as string[]; // error [ts] Type 'ReadonlyArray<number>' cannot be converted to type 'string
a = ro as any[]; // ok 类型断言重写 但是如下依旧会报错
a = ro as number[]; // 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 {
return new ctor( hour, minute);
}

class DigitalClock implements ClockInterface {
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 () { };
return counter;
}
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 Employee extends Person {
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 = new Employee( "Howard", "Sales");
console. log( howard. getElevatorPitch());
console. log( howard. name); // error

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

class Employee extends Person {
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 = new Employee( "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 = new Employee();
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 new ctr();
}
let greeter1: Greeter;
greeter1 = createGreeter( Greeter);
console. log( greeter1. greet());
let greeterMaker: typeof Greeter = Greeter;
// 从这可以知道 typeof 取的是类的静态部分的类型 typeof Greeter 相当于语句 type a = typeof Greeter; let greeterMaker: a = Greeter;
greeterMaker. standardGreeting = "Hey there!";
let greeter2: Greeter = new greeterMaker();
console. log( greeter2. greet());
// 既然 typeof 取的是类的静态部分的类型 那么 interface StaticClass 也是对静态部分进行约束检查的, 果不其然下面的语句是成立相等的 !
let clasA: StaticClass;
let clasB: typeof Greeter;
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 = new Handler();
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 = new Handler();
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 ( typeof x == "object") {
let pickedCard = Math. floor( Math. random() * x. length);
return pickedCard;
}
// Otherwise just let them pick the card
else if ( typeof x == "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 Lion extends Animal {
constructor( public name: string){
super()
}
keeper: "hello word!";
}
function createInstance< A extends Animal>( c: new ( name: string) => A): A {
return new c( 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 enum FileAccess2 {
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 enum FileAccess3 {
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;

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值