TypeScript高级用法的知识点汇总

引言

作为一门强大的静态类型检查工具,如今在许多中大型应用程序以及流行的JS库中均能看到TypeScript的身影。JS作为一门弱类型语言,在我们写代码的过程中稍不留神便会修改掉变量的类型,从而导致一些出乎意料的运行时错误。然而TypeScript在编译过程中便能帮我们解决这个难题,不仅在JS中引入了强类型检查,并且编译后的JS代码能够运行在任何浏览器环境,Node环境和任何支持ECMAScript 3(或更高版本)的JS引擎中。最近公司刚好准备使用TypeScript来对现有系统进行重构,以前使用TypeScript的机会也不多,特别是一些有用的高级用法,所以借着这次机会,重新巩固夯实一下这方面的知识点,如果有错误的地方,还请指出。

1、类继承

在ES5中,我们一般通过函数或者基于原型的继承来封装一些组件公共的部分方便复用,然而在TypeScript中,我们可以像类似Java语言中以面向对象的方式使用类继承来创建可复用的组件。我们可以通过class关键字来创建类,并基于它使用new操作符来实例化一个对象。为了将多个类的公共部分进行抽象,我们可以创建一个父类并让子类通过extends关键字来继承父类,从而减少一些冗余代码的编写增加代码的可复用性和可维护性。示例如下:

class Parent {
 readonly x: number;
 constructor() {
 this.x = 1;
 }

 print() {
 console.log(this.x);
 }
}

class Child extends Parent {
 readonly y: number;
 constructor() {
 // 注意此处必须优先调用super()方法
 super();
 this.y = 2;
 }

 print() {
 // 通过super调用父类原型上的方法,但是方法中的this指向的是子类的实例
 super.print();
 console.log(this.y);
 }
}

const child = new Child();
console.log(child.print()) // -> 1 2

在上述示例中,Child子类中对父类的print方法进行重写,同时在内部使用super.print()来调用父类的公共逻辑,从而实现逻辑复用。class关键字作为构造函数的语法糖,在经过TypeScript编译后,最终会被转换为兼容性好的浏览器可识别的ES5代码。class在面向对象的编程范式中非常常见,因此为了弄清楚其背后的实现机制,我们不妨多花点时间来看下经过编译转换之后的代码是什么样子的(当然这部分已经比较熟悉的同学可以直接跳过)。

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 Parent = /** @class */ (function () {
 function Parent() {
 this.x = 1;
 }
 Parent.prototype.print = function () {
 console.log(this.x);
 };
 return Parent;
}());
var Child = /** @class */ (function (_super) {
 __extends(Child, _super);
 function Child() {
 var _this =
 // 注意此处必须优先调用super()方法
 _super.call(this) || this;
 _this.y = 2;
 return _this;
 }
 Child.prototype.print = function () {
 // 通过super调用父类原型上的方法,但是方法中的this指向的是子类的实例
 _super.prototype.print.call(this);
 console.log(this.y);
 };
 return Child;
}(Parent));
var child = new Child();
console.log(child.print()); // -> 1 2

以上就是转换后的完整代码,为了方便对比,这里将原来的注释信息保留,仔细研究这段代码我们会发现以下几个要点:

1) 子类Child的构造函数中super()方法被转换成了var _this = _super.call(this) || this,这里的_super指的就是父类Parent,因此这句代码的含义就是调用父类构造函数并将this绑定到子类的实例上,这样的话子类实例便可拥有父类的x属性。因此为了实现属性继承,我们必须在子类构造函数中调用super()方法,如果不调用会编译不通过。

2) 子类Child的print方法中super.print()方法被转换成了_super.prototype.print.call(this) ,这句代码的含义就是调用父类原型上的print方法并将方法中的this指向子类实例,由于在上一步操作中我们已经继承到父类的x属性,因此这里我们将直接打印出子类实例的x属性的值。

3) extends关键字最终被转换为__extends(Child, _super)方法,其中_super指的是父类Parent,为了方便查看,这里将_extends方法单独提出来进行研究。

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 __());
 };
})();

在以上代码中,主要可以分为两个部分来进行理解,第一部分为extendStatics(d, b)方法,第二部分为该方法后面的两行代码。

第一部分:

在extendStatics方法内部虽然代码量相对较多,但是不难发现其实还是主要为了兼容ES5版本的执行环境。在ES6中新增了Object.setPrototypeOf方法用于手动设置对象的原型,但是在ES5的环境中我们一般通过一个非标准的__proto__属性来进行设置,Object.setPrototypeOf方法的原理其实也是通过该属性来设置对象的原型,其实现方式如下:

Object.setPrototypeOf = function(obj, proto) {
 obj.__proto__ = proto;
 return obj;
}

extendStatics(d, b)方法中,d指子类Child,b指父类Parent,因此该方法的作用可以解释为:

// 将子类Child的__proto__属性指向父类Parent
Child.__proto__ = Parent;

可以将这行代码理解为构造函数的继承,或者叫静态属性和静态方法的继承,即属性和方法不是挂载到构造函数的prototype原型上的,而是直接挂载到构造函数本身,因为在JS中函数本身也可以作为一个对象,并可以为其赋予任何其他的属性,示例如下:

function Foo() {
 this.x = 1;
 this.y = 2;
}

Foo.bar = function() {
 console.log(3);
}

Foo.baz = 4;
console.log(Foo.bar()) // -> 3
console.log(Foo.baz) // -> 4

因此当我们在子类Child中以Child.someProperty访问属性时,如果子类中不存在就会通过Child.__proto__寻找父类的同名属性,通过这种方式来实现静态属性和静态方法的路径查找。

第二部分:

在第二部分中仅包含以下两行代码:

function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());

其中d指子类Child,b指父类Parent,这里对于JS中实现继承的几种方式比较熟悉的同学可以一眼看出,这里使用了寄生组合式继承的方式,通过借用一个中间函数__()来避免当修改子类的prototype上的方法时对父类的prototype所造成的影响。我们知道,在JS中通过构造函数实例化一个对象之后,该对象会拥有一个__proto__属性并指向其构造函数的prototype属性,示例如下:

function Foo() {
 this.x = 1;
 this.y = 2;
}

const foo = new Foo();
foo.__proto__ === Foo.prototype; // -> true

对于本例中,如果通过子类Child来实例化一个对象之后,会产生如下关联:

const child = new Child();
child.__proto__ === (Child.prototype = new __());
child.__proto__.__proto__ === __.prototype === Parent.prototype;

// 上述代码等价于下面这种方式
Child.prototype.__proto__ === Parent.prototype;

因此当我们在子类Child的实例child对象中通过child.someMethod()调用某个方法时,如果在实例中不存在该方法,则会沿着__proto__继续往上查找,最终会经过父类Parent的prototype原型,即通过这种方式来实现方法的继承。

基于对以上两个部分的分析,我们可以总结出以下两点:

// 表示构造函数的继承,或者叫做静态属性和静态方法的继承,总是指向父类
1. Child.__proto__ === Parent;

// 表示方法的继承,总是指向父类的prototype属性
2. Child.prototype.__proto__ === Parent.prototype;

2、访问修饰符

TypeScript为我们提供了访问修饰符(Access Modifiers)来限制在class外部对内部属性的访问,访问修饰符主要包含以下三种:

  • public:公共修饰符,其修饰的属性和方法都是公有的,可以在任何地方被访问到,默认情况下所有属性和方法都是public的。
  • private:私有修饰符,其修饰的属性和方法在class外部不可见。
  • protected:受保护修饰符,和private比较相似,但是其修饰的属性和方法在子类内部是被允许访问的。

我们通过一些示例来对几种修饰符进行对比:

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

const man = new Human('tom', 20);
console.log(man.name, man.age); // -> tom 20
man.age = 21;
console.log(man.age); // -> 21

在上述示例中,由于我们将访问修饰符设置为public,因此我们通过实例man来访问name和age属性是被允许的,同时对age属性重新赋值也是允许的。但是在某些情况下,我们希望某些属性是对外不可见的,同时不允许被修改,那么我们就可以使用private修饰符:

class Human {
 public name: string;
 private age: number; // 此处修改为使用private修饰符
 public constructor(name: string, age: number) {
 this.name = name;
 this.age = age;
 }
}

const man = new Human('tom', 20);
console.log(man.name); // -> tom
console.log(man.age);
// -> Property 'age' is private and only accessible within class 'Human'.

我们将age属性的修饰符修改为private后,在外部通过man.age对其进行访问,TypeScript在编译阶段就会发现其是一个私有属性并最终将会报错。

注意:在TypeScript编译之后的代码中并没有限制对私有属性的存取操作。

编译后的代码如下:

var Human = /** @class */ (function () {
 function Human(name, age) {
 this.name = name;
 this.age = age;
 }
 return Human;
}());
var man = new Human('tom', 20);
console.log(man.name); // -> tom
console.log(man.age); // -> 20

使用private修饰符修饰的属性或者方法在子类中也是不允许访问的,示例如下:

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

class Woman extends Human {
 private gender: number = 0;
 public constructor(name: string, age: number) {
 super(name, age);
 console.log(this.age);
 }
}

const woman = new Woman('Alice', 18);
// -> Property 'age' is private and only accessible within class 'Human'.

在上述示例中由于在父类Human中age属性被设置为private,因此在子类Woman中无法访问到age属性,为了让在子类中允许访问age属性,我们可以使用protected修饰符来对其进行修饰:

class Human {
 public name: string;
 protected age: number; // 此处修改为使用protected修饰符
 public constructor(name: string, age: number) {
 this.name = name;
 this.age = age;
 }
}

class Woman extends Human {
 private gender: number = 0;
 public constructor(name: string, age: number) {
 super(name, age);
 console.log(this.age);
 }
}

const woman = new Woman('Alice', 18); // -> 18

当我们将private修饰符用于构造函数时,则表示该类不允许被继承或实例化,示例如下:

class Human {
 public name: string;
 public age: number;

 // 此处修改为使用private修饰符
 private constructor(name: string, age: number) {
 this.name = name;
 this.age = age;
 }
}

class Woman extends Human {
 private gender: number = 0;
 public constructor(name: string, age: number) {
 super(name, age);
 }
}

const man = new Human('Alice', 18);
// -> Cannot extend a class 'Human'. Class constructor is marked as private.
// -> Constructor of class 'Human' is private and only accessible within the class declaration.

当我们将protected修饰符用于构造函数时,则表示该类只允许被继承,示例如下:

class Human {
 public name: string;
 public age: number;

 // 此处修改为使用protected修饰符
 protected constructor(name: string, age: number) {
 this.name = name;
 this.age = age;
 }
}

class Woman extends Human {
 private gender: number = 0;
 public constructor(name: string, age: number) {
 super(name, age);
 }
}

const man = new Human('Alice', 18);
// -> Constructor of class 'Human' is protected and only accessible within the class declaration.

另外我们还可以直接将修饰符放到构造函数的参数中,示例如下:

class Human {
 // public name: string;
 // private age: number;

 public constructor(public name: string, private age: number) {
 this.name = name;
 this.age = age;
 }
}

const man = new Human('tom', 20);
console.log(man.name); // -> tom
console.log(man.age);
// -> Property 'age' is private and only accessible within class 'Human'.

3、接口与构造器签名

当我们的项目中拥有很多不同的类时并且这些类之间可能存在某方面的共同点,为了描述这种共同点,我们可以将其提取到一个接口(interface)中用于集中维护,并使用implements关键字来实现这个接口,示例如下:

interface IHuman {
 name: string;
 age: number;
 walk(): void;
}

class Human implements IHuman {

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

 walk(): void {
 console.log('I am walking...');
 }
}

上述代码在编译阶段能顺利通过,但是我们注意到在Human类中包含constructor构造函数,如果我们想在接口中为该构造函数定义一个签名并让Human类来实现这个接口,看会发生什么:

interface HumanConstructor {
 new (name: string, age: number);
}

class Human implements HumanConstructor {

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

 walk(): void {
 console.log('I am walking...');
 }
}
// -> Class 'Human' incorrectly implements interface 'HumanConstructor'.
// -> Type 'Human' provides no match for the signature 'new (name: string, age: number): any'.

然而TypeScript会编译出错,告诉我们错误地实现了HumanConstructor接口,这是因为当一个类实现一个接口时,只会对实例部分进行编译检查,类的静态部分是不会被编译器检查的。因此这里我们尝试换种方式,直接操作类的静态部分,示例如下:

interface HumanConstructor {
 new (name: string, age: number);
}

interface IHuman {
 name: string;
 age: number;
 walk(): void;
}

class Human implements IHuman {

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

 walk(): void {
 console.log('I am walking...');
 }
}

// 定义一个工厂方法
function createHuman(constructor: HumanConstructor, name: string, age: number): IHuman {
 return new constructor(name, age);
}

const man = createHuman(Human, 'tom', 18);
console.log(man.name, man.age); // -> tom 18

在上述示例中通过额外创建一个工厂方法createHuman并将构造函数作为第一个参数传入,此时当我们调用createHuman(Human, 'tom', 18)时编译器便会检查第一个参数是否符合HumanConstructor接口的构造器签名。

4、声明合并

在声明合并中最常见的合并类型就是接口了,因此这里先从接口开始介绍几种比较常见的合并方式。

4.1 接口合并

示例代码如下:

interface A {
 name: string;
}

interface A {
 age: number;
}

// 等价于
interface A {
 name: string;
 age: number;
}

const a: A = {name: 'tom', age: 18};

接口合并的方式比较容易理解,即声明多个同名的接口,每个接口中包含不同的属性声明,最终这些来自多个接口的属性声明会被合并到同一个接口中。

注意:所有同名接口中的非函数成员必须唯一,如果不唯一则必须保证类型相同,否则编译器会报错。对于函数成员,后声明的同名接口会覆盖掉之前声明的同名接口,即后声明的同名接口中的函数相当于一次重载,具有更高的优先级。

4.2 函数合并

函数的合并可以简单理解为函数的重载,即通过同时定义多个不同类型参数或不同类型返回值的同名函数来实现,示例代码如下:

// 函数定义
function foo(x: number): number;
function foo(x: string): string;

// 函数具体实现
function foo(x: number | string): number | string {
 if (typeof x === 'number') {
 return (x).toFixed(2);
 }

 return x.substring(0, x.length - 1);
}

在上述示例中,我们对foo函数进行多次定义,每次定义的函数参数类型不同,返回值类型不同,最后一次为函数的具体实现,在实现中只有在兼容到前面的所有定义时,编译器才不会报错。

注意:TypeScript编译器会优先从最开始的函数定义进行匹配,因此如果多个函数定义存在包含关系,则需要将最精确的函数定义放到最前面,否则将始终不会被匹配到。

4.3 类型别名联合

类型别名联合与接口合并有所区别,类型别名不会新建一个类型,只是创建一个新的别名来对多个类型进行引用,同时不能像接口一样被实现(implements)和继承(extends),示例如下:

type HumanProperty = {
 name: string;
 age: number;
 gender: number;
};

type HumanBehavior = {
 eat(): void;
 walk(): void;
}

type Human = HumanProperty & HumanBehavior;

let woman: Human = {
 name: 'tom',
 age: 18,
 gender: 0,
 eat() {
 console.log('I can eat.');
 },
 walk() {
 console.log('I can walk.');
 }
}

class HumanComponent extends Human {
 constructor(public name: string, public age: number, public gender: number) {
 this.name = name;
 this.age = age;
 this.gender = gender;
 }

 eat() {
 console.log('I can eat.');
 }

 walk() {
 console.log('I can walk.');
 }
}
// -> 'Human' only refers to a type, but is being used as a value here.

5、keyof 索引查询

在TypeScript中的keyof有点类似于JS中的Object.keys()方法,但是区别在于前者遍历的是类型中的字符串索引,后者遍历的是对象中的键名,示例如下:

interface Rectangle {
 x: number;
 y: number;
 width: number;
 height: number;
}

type keys = keyof Rectangle;
// 等价于
type keys = "x" | "y" | "width" | "height";

// 这里使用了泛型,强制要求第二个参数的参数名必须包含在第一个参数的所有字符串索引中
function getRectProperty<T extends object, K extends keyof T>(rect: T, property: K): T[K] {
 return rect[property];
}

let rect: Rectangle = {
 x: 50,
 y: 50,
 width: 100,
 height: 200
};

console.log(getRectProperty(rect, 'width')); // -> 100
console.log(getRectProperty(rect, 'notExist'));
// -> Argument of type '"notExist"' is not assignable to parameter of type '"width" | "x" | "y" | "height"'.

在上述示例中我们通过使用keyof来限制函数的参数名property必须被包含在类型Rectangle的所有字符串索引中,如果没有被包含则编译器会报错,可以用来在编译时检测对象的属性名是否书写有误。

6、Partial 可选属性

在某些情况下,我们希望类型中的所有属性都不是必需的,只有在某些条件下才存在,我们就可以使用Partial来将已声明的类型中的所有属性标识为可选的,示例如下:

// 该类型已内置在TypeScript中
type Partial<T> = {
 [P in keyof T]?: T[P]
};

interface Rectangle {
 x: number;
 y: number;
 width: number;
 height: number;
}

type PartialRectangle = Partial<Rectangle>;
// 等价于
type PartialRectangle = {
 x?: number;
 y?: number;
 width?: number;
 height?: number;
}

let rect: PartialRectangle = {
 width: 100,
 height: 200
};

在上述示例中由于我们使用Partial将所有属性标识为可选的,因此最终rect对象中虽然只包含width和height属性,但是编译器依旧没有报错,当我们不能明确地确定对象中包含哪些属性时,我们就可以通过Partial来声明。

7、Pick 部分选择

在某些应用场景下,我们可能需要从一个已声明的类型中抽取出一个子类型,在子类型中包含父类型中的部分或全部属性,这时我们可以使用Pick来实现,示例代码如下:

// 该类型已内置在TypeScript中
type Pick<T, K extends keyof T> = {
 [P in K]: T[P]
};

interface User {
 id: number;
 name: string;
 age: number;
 gender: number;
 email: string;
}

type PickUser = Pick<User, "id" | "name" | "gender">;
// 等价于
type PickUser = {
 id: number;
 name: string;
 gender: number;
};

let user: PickUser = {
 id: 1,
 name: 'tom',
 gender: 1
};

在上述示例中,由于我们只关心user对象中的id,name和gender是否存在,其他属性不做明确规定,因此我们就可以使用Pick从User接口中拣选出我们关心的属性而忽略其他属性的编译检查。

8、never 永不存在

never表示的是那些永不存在的值的类型,比如在函数中抛出异常或者无限循环,never类型可以是任何类型的子类型,也可以赋值给任何类型,但是相反却没有一个类型可以作为never类型的子类型,示例如下:

// 函数抛出异常
function throwError(message: string): never {
 throw new Error(message);
}

// 函数自动推断出返回值为never类型
function reportError(message: string) {
 return throwError(message);
}

// 无限循环
function loop(): never {
 while(true) {
 console.log(1);
 }
}

// never类型可以是任何类型的子类型
let n: never;
let a: string = n;
let b: number = n;
let c: boolean = n;
let d: null = n;
let e: undefined = n;
let f: any = n;

// 任何类型都不能赋值给never类型
let a: string = '123';
let b: number = 0;
let c: boolean = true;
let d: null = null;
let e: undefined = undefined;
let f: any = [];

let n: never = a;
// -> Type 'string' is not assignable to type 'never'.

let n: never = b;
// -> Type 'number' is not assignable to type 'never'.

let n: never = c;
// -> Type 'true' is not assignable to type 'never'.

let n: never = d;
// -> Type 'null' is not assignable to type 'never'.

let n: never = e;
// -> Type 'undefined' is not assignable to type 'never'.

let n: never = f;
// -> Type 'any' is not assignable to type 'never'.

9、Exclude 属性排除

与Pick相反,Pick用于拣选出我们需要关心的属性,而Exclude用于排除掉我们不需要关心的属性,示例如下:

// 该类型已内置在TypeScript中
// 这里使用了条件类型(Conditional Type),和JS中的三目运算符效果一致
type Exclude<T, U> = T extends U ? never : T;

interface User {
 id: number;
 name: string;
 age: number;
 gender: number;
 email: string;
}

type keys = keyof User; // -> "id" | "name" | "age" | "gender" | "email"

type ExcludeUser = Exclude<keys, "age" | "email">;
// 等价于
type ExcludeUser = "id" | "name" | "gender";

在上述示例中我们通过在ExcludeUser中传入我们不需要关心的age和email属性,Exclude会帮助我们将不需要的属性进行剔除,留下的属性id,name和gender即为我们需要关心的属性。一般来说,Exclude很少单独使用,可以与其他类型配合实现更复杂更有用的功能。

10、Omit 属性忽略

在上一个用法中,我们使用Exclude来排除掉其他不需要的属性,但是在上述示例中的写法耦合度较高,当有其他类型也需要这样处理时,就必须再实现一遍相同的逻辑,不妨我们再进一步封装,隐藏这些底层的处理细节,只对外暴露简单的公共接口,示例如下:

// 使用Pick和Exclude组合实现
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

interface User {
 id: number;
 name: string;
 age: number;
 gender: number;
 email: string;
}

// 表示忽略掉User接口中的age和email属性
type OmitUser = Omit<User, "age" | "email">;
// 等价于
type OmitUser = {
 id: number;
 name: string;
 gender: number;
};

let user: OmitUser = {
 id: 1,
 name: 'tom',
 gender: 1
};

在上述示例中,我们需要忽略掉User接口中的age和email属性,则只需要将接口名和属性传入Omit即可,对于其他类型也是如此,大大提高了类型的可扩展能力,方便复用。

总结

在本文中总结了几种TypeScript的使用技巧,如果在我们的TypeScript项目中发现有很多类型声明的地方具有共性,那么不妨可以使用文中的几种技巧来对其进行优化改善,增加代码的可维护性和可复用性。笔者之前使用TypeScript的机会也不多,所以最近也是一边学习一边总结,如果文中有错误的地方,还希望能够在评论区指正。

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对我们的支持。

(0)

相关推荐

  • 如何获取TypeScript的声明文件.d.ts

    一.TypeScript的声明文件就像C/C++用.h文件.当使用TypeScript调用其他已经编写好的类库时,可以提供IntelliSense智能提示. 二.使用npm指令来获取.d.ts文件 install -save @types/jquery -g 运行后在type/jquery目录下生成4个文件:index.d.ts,lincense,package.json,readme.md,其中的index.d.ts即为jquery的声明文件. 三.npm需要Nodejs环境,安装步骤如下 1

  • TypeScript Type Innference(类型判断)

    TypeScript 是微软开发的 JavaScript 的超集,TypeScript兼容JavaScript,可以载入JavaScript代码然后运行.TypeScript与JavaScript相比进步的地方 包括:加入注释,让编译器理解所支持的对象和函数,编译器会移除注释,不会增加开销:增加一个完整的类结构,使之更新是传统的面向对象语言. 为什么会有 TypeScript? JavaScript 只是一个脚本语言,并非设计用于开发大型 Web 应用,JavaScript 没有提供类和模块的概

  • TypeScript 中接口详解

    在 TypeScript 中,接口是用作约束作用的,在编译成 JavaScript 的时候,所有的接口都会被擦除掉,因为 JavaScript 中并没有接口这一概念. 先看看一个简单的例子: function printLabel(labelledObj: { label: string }) { console.log(labelledObj.label); } var myObj = { size: 10, label: "Size 10 Object" }; printLabel

  • 深入理解JavaScript和TypeScript中的class

    前言 对于一个前端开发者来说,很少用到 class ,因为在 JavaScript 中更多的是 函数式 编程,抬手就是一个 function,几乎不见 class 或 new 的踪影.所以 设计模式 也是大多数前端开发者的一个短板. 最近在学习 Angular的过程中发现其大量的运用了 class,不得不佩服,Angular 确实是一个优秀的.值得深入研究的 框架. 本文将简单的介绍一下 JavaScript 和 TypeScript 中的 class. 基本概念 在介绍 class 之前,要先

  • 关于TypeScript模块导入的那些事

    前言 模块在其自身的作用域里执行,而不是在全局作用域里:这意味着定义在一个模块里的变量,函数,类等等在模块外部是不可见的,除非你明确地使用export之一导出它们. 相反,如果想使用其它模块导出的变量,函数,类,接口等的时候,你必须要导入它们,可以使用import之一. 模块是自声明的.在TypeScript里,两个模块之间的关系是通过在文件级别上使用import和export建立的. 下面话不多说了,来一起看看详细的介绍吧 ES6 模块导入的限制 我们先来看一个具体的例子: 在 Node 项目

  • Typescript 中的 interface 和 type 到底有什么区别详解

    interface VS type 大家使用 typescript 总会使用到 interface 和 type,官方规范 稍微说了下两者的区别 An interface can be named in an extends or implements clause, but a type alias for an object type literal cannot. An interface can have multiple merged declarations, but a type

  • 关于TypeScript中import JSON的正确姿势详解

    前言 Typescript是微软内部出品的,用actionscript的语法在写js的一门新语言,最近 TypeScript 中毒,想想我一个弱类型出身的人,怎么就喜欢上了类型约束--当然这不是重点,重点可能还是 JS 没有接口,我没法靠 class 语法糖写的非常 OO--下面这篇文章想说的其实是在 ts 中如何正确的 import json 格式. 首先我使用了基本姿势 import * as variable from './fooooooo.json' 结果发现他提示我并没有这个 mod

  • TypeScript入门-基本数据类型

    大致介绍 TypeScript是由C#语言之父Anders Hejlsberg主导开发的一门编程语言,TypeScript本质上是向JavaScript语言添加了可选的静态类型和基于类的面向对象编程,它相当于是JavaScript的超集 ES5.ES6和TypeScript的关系: 安装 首先需要安装npm,然后在输入 npm install -g typescript 安装完成后,因为TypeScript是以.ts结尾的,要想运行就得把他编译js文件,编译的方法特别简单就是使用tsc命令 ts

  • TypeScript学习之强制类型的转换

    前言 使用强类型变量常常需要从一种类型向另一种类型转换,通常使用ToString或ParseInt可以来实现一些简单的转换,但是有时候需要像.NET语言中那样将一种类型显示的转换为另一种类型,在TypeScript规范中,被称为"类型断言",它仍然是类型转换,只是语法是有些不同.下面来详细看看TypeScript的强制类型转换. TypeScript强制类型转换 在 TypeScript 中将一个 number 转换成 string ,这样做会报错: var a:number = 12

  • TypeScript高级用法的知识点汇总

    引言 作为一门强大的静态类型检查工具,如今在许多中大型应用程序以及流行的JS库中均能看到TypeScript的身影.JS作为一门弱类型语言,在我们写代码的过程中稍不留神便会修改掉变量的类型,从而导致一些出乎意料的运行时错误.然而TypeScript在编译过程中便能帮我们解决这个难题,不仅在JS中引入了强类型检查,并且编译后的JS代码能够运行在任何浏览器环境,Node环境和任何支持ECMAScript 3(或更高版本)的JS引擎中.最近公司刚好准备使用TypeScript来对现有系统进行重构,以前

  • Typescript高级类型Record,Partial,Readonly详解

    目录 联合类型 keyof Record Partial (部分的; 不完全的) Required(必须的) Pick(选择) Readonly (意思是只读的) Exclude(排除) Omit (省略的) ReadonlyArray(只读数组) TypeScript 是一种类型化的语言,允许你指定变量.函数参数.返回的值和对象属性的类型. 下面介绍Typescript高级类型Record,Partial,Readonly等知识. 联合类型 /* 首先是联合类型的介绍 */ let a: st

  • PHP的switch判断语句的“高级”用法详解

    只所以称为"高级"用法,是因为我连switch的最基础的用法都还没有掌握,so,接下来讲的其实还是它的基础用法! switch 语句和具有同样表达式的一系列的 IF 语句相似.很多场合下需要把同一个变量(或表达式)与很多不同的值比较,并根据它等于哪个值来执行不同的代码.这正是 switch 语句的用途. 注意: 注意和其它语言不同,continue 语句作用到 switch 上的作用类似于 break.如果在循环中有一个 switch 并希望 continue 到外层循环中的下一个轮回

  • Python tkinter事件高级用法实例

    本文实例讲述了Python tkinter事件高级用法.分享给大家供大家参考,具体如下: 先来看看运行效果: 完整实例代码: # -*- coding:utf-8-*- #! python3 from tkinter import * import threading, time trace = 0 class CanvasEventsDemo: def __init__(self, parent=None): canvas = Canvas(width=300, height=300, bg=

  • 详解Python进阶之切片的误区与高级用法

    众所周知,我们可以通过索引值(或称下标)来查找序列类型(如字符串.列表.元组...)中的单个元素,那么,如果要获取一个索引区间的元素该怎么办呢? 切片(slice)就是一种截取索引片段的技术,借助切片技术,我们可以十分灵活地处理序列类型的对象.通常来说,切片的作用就是截取序列对象,然而,它还有一些使用误区与高级用法,都值得我们注意.所以,本文将主要跟大家一起来探讨这些内容,希望你能学有所获. 事先声明,切片并非列表的专属操作,但因为列表最具有代表性,所以,本文仅以列表为例作探讨. 1.切片的基础

  • Mysql数据库高级用法之视图、事务、索引、自连接、用户管理实例分析

    本文实例讲述了Mysql数据库高级用法之视图.事务.索引.自连接.用户管理.分享给大家供大家参考,具体如下: 视图 视图是对若干张基本表的引用,一张虚表,只查询语句执行结果的字段类型和约束,不存储具体的数据(基本表数据发生了改变,视图也会跟着改变),方便操作,特别是查询操作,减少复杂的SQL语句,增强可读性. 1.----创建视图: create view 视图名称(一般以v_开头) as 查询语句; 2.----查看视图: select * from 视图名称; 3.----删除视图: dro

  • Python装饰器用法与知识点小结

    本文实例讲述了Python装饰器用法与知识点.分享给大家供大家参考,具体如下: (1)装饰器含参数,被装饰函数不含(含)参数 实例代码如下: import time # 装饰器函数 def wrapper(func): def done(*args,**kwargs): start_time = time.time() func(*args,**kwargs) stop_time = time.time() print('the func run time is %s' % (stop_time

  • 深入了解Python装饰器的高级用法

    原文地址 https://www.codementor.io/python/tutorial/advanced-use-python-decorators-class-function 介绍 我写这篇文章的主要目的是介绍装饰器的高级用法.如果你对装饰器知之甚少,或者对本文讲到的知识点易混淆.我建议你复习下装饰器基础教程. 本教程的目标是介绍装饰器的一些有趣的用法.特别是怎样在类中使用装饰器,怎样给装饰器传递额外的参数. 装饰器 vs 装饰器模式 Decorator模式是一个面向对象的设计模式,它

  • 你不知道的 TypeScript 高级类型(小结)

    前言 对于有 JavaScript 基础的同学来说,入门 TypeScript 其实很容易,只需要简单掌握其基础的类型系统就可以逐步将 JS 应用过渡到 TS 应用. // js const double = (num) => 2 * num // ts const double = (num: number): number => 2 * num 然而,当应用越来越复杂,我们很容易把一些变量设置为 any 类型,TypeScript 写着写着也就成了 AnyScript.为了让大家能更加深入

  • JS数组reduce你不得不知道的25个高级用法

    前言 reduce作为ES5新增的常规数组方法之一,对比forEach.filter和map,在实际使用上好像有些被忽略,发现身边的人极少使用它,导致这个如此强大的方法被逐渐埋没. 如果经常使用reduce,怎么可能放过如此好用的它呢!我还是得把他从尘土中取出来擦干净,奉上它的高级用法给大家.一个如此好用的方法不应该被大众埋没. 下面对reduce的语法进行简单说明,详情可查看MDN的reduce()的相关说明. 定义:对数组中的每个元素执行一个自定义的累计器,将其结果汇总为单个返回值 形式:a

随机推荐