第七章、类
一、类的定义
class Person {
name: string;//实例属性,前面省略了public关键词
constructor(n: string) {//构造函数,实例化类的时候触发的方法
this.name = n;//使用this关键字为当前类的name属性赋值
}
run(): void {//实例方法
console.log(this.name+ "在跑步");
}
}
var p = new Person("张三");
p.run();
二、类的继承
类的继承:在 TypeScript 中要想实现继承使用
extends
关键字,只要一旦实现了继承关系,那么子类中便拥有了父类的属性和方法,而在执行方法过程中,(继承链)首先从子类开始找,如果有,就使用,如果没有,就去父类中找。类的继承只能单向继承。
class Person extends Object{
name: string;//父类属性,前面省略了public关键词
constructor(n: string) {//构造函数,实例化父类的时候触发的方法
super();
this.name = n;//使用this关键字为当前类的name属性赋值
}
run(): void {//父类方法
console.log(this.name + "在跑步");
}
}
//中国人这个类继承了人这个类
class Chinese extends Person {
age: number;//子类属性
constructor(n: string, a: number) {//构造函数,实例化子类的时候触发的方法
super(n);//使用super关键字调用父类中的构造方法
this.age = a;//使用this关键字为当前类的age属性赋值
}
speak(): void {//子类方法
super.run();//使用super关键字调用父类中的方法
console.log(this.name + "说中文");
}
}
var c = new Chinese("张三", 28);
c.speak();
三、访问权限修饰符
注意:如果属性不加修饰符,默认就是公有(public)。
TypeScript 里面定义属性的时候给我们提供了 三种修饰符
1、public
public:公有类型,在当前类里面、子类、类外面都可以访问
2、protected
protected:保护类型,在当前类里面、子类里面可以访问,在类外部没法访问
3、private
private:私有类型,在当前类里面可以访问,子类、类外部都没法访问
4、访问权限修饰符(Access Modifiers)和变量声明关键字(var
、let
、const
)不能同时使用在同一个成员上
在 TypeScript 中,访问权限修饰符(Access Modifiers)和变量声明关键字(var
、let
、const
)不能同时使用在同一个成员上。
访问权限修饰符用于限制类成员的访问权限,包括 public
、private
和 protected
。这些修饰符用于控制类成员在类内部和类外部的可见性和访问性。
变量声明关键字用于声明变量,并指定其作用域和可变性。
根据 TypeScript 的语法规则,不能在类成员声明中同时使用访问权限修饰符和变量声明关键字。
以下是一些示例来说明这一点:
class MyClass {
public var myPublicVariable: number; // 错误,不允许同时使用 public 和 var
private let myPrivateVariable: string; // 错误,不允许同时使用 private 和 let
protected const myProtectedConstant: boolean; // 错误,不允许同时使用 protected 和 const
}
如上所示,以上示例中同时使用了访问权限修饰符和变量声明关键字,这是不符合 TypeScript 语法的,会导致编译错误。
如果你想在类中声明变量并指定其访问权限,应该将访问权限修饰符放在变量声明之前或之后,而不是同时使用它们。例如:
class MyClass {
public myPublicVariable: number;
private myPrivateVariable: string;
protected myProtectedVariable: boolean;
}
在上述示例中,我们分别为类成员声明了访问权限修饰符,并通过变量声明关键字指定了其类型。这样,就定义了具有相应访问权限的变量成员。
四、static关键字
1、静态属性(静态变量)
静态属性:被静态修饰符修饰的属性就是静态属性,静态属性可以通过类名直接调用。
class Person {
name: string;//属性,前面省略了public关键词
static sex: string = "男";//被静态修饰符static修饰的属性
constructor(n: string) {//构造函数,实例化类的时候触发的方法
this.name = n;
}
run(): void {//方法
console.log(this.name+ "在跑步");
}
}
console.log(Person.sex);
2、静态方法
静态方法:被静态修饰符修饰的方法就是静态方法,静态方法可以通过类名直接调用,但是在静态方法内部,不能直接调用当前类的非静态属性、非静态方法。
class Person {
name: string;//属性,前面省略了public关键词
static sex: string = "男";//被静态修饰符static修饰的属性
constructor(n: string) {//构造函数,实例化类的时候触发的方法
this.name = n;
}
run(): void {//方法
console.log(this.name + "在跑步");
}
static print(): void {//被静态修饰符static修饰的方法
// console.log('姓名:' + this.name);//错误
console.log('性别:' + Person.sex);//正确
// this.run();//错误
}
}
Person.print();
五、对象的默认初始化
对象的默认初始化:在创建对象时,如果没有显式地初始化成员变量,则会自动将成员变量初始化为默认值。例如,int 类型的成员变量默认值为 0,boolean 类型的成员变量默认值为 false,引用类型的成员变量默认值为 null。
以下是常见的默认值规则:
1、对象类型(包括类实例):默认值为 null
。
class MyClass {
myObject: SomeClass;
}
const instance = new MyClass();
console.log(instance.myObject); // 输出:null
2、数值类型:默认值为 0
。
class MyClass {
myNumber: number;
}
const instance = new MyClass();
console.log(instance.myNumber); // 输出:0
3、布尔类型:默认值为 false
。
class MyClass {
myBoolean: boolean;
}
const instance = new MyClass();
console.log(instance.myBoolean); // 输出:false
4、字符串类型:默认值为 ""
(空字符串)。
class MyClass {
myString: string;
}
const instance = new MyClass();
console.log(instance.myString); // 输出:""
需要注意的是,如果在定义类时为实例变量指定了初始值,那么该初始值将会覆盖默认值。
如果你在实例化类时不给实例变量赋值,并且没有在类的构造函数中初始化这些变量,那么它们将会具有默认值。这使得你可以在实例化类时不必为每个实例变量都提供初始值,而是使用默认值进行初始化。
六、TS类成员不可具有“const”关键字
在 TypeScript 中,类成员(包括属性和方法)不能使用 const
关键字修饰,这是因为类成员具有特定的行为和语义,与常量不同。
使用 const
关键字用于声明常量,这意味着该变量的值在声明后不能被修改。常量的值在编译时就被确定,并且在运行时保持不变。
而类成员具有不同的特性:
-
类成员可以在实例化后进行修改。类的实例可以对类成员进行赋值和修改,因此它们的值可以在运行时根据需要改变。
-
类成员具有与类实例相关的状态。类成员可以访问和操作类的其他成员、实例变量和方法,以及类的上下文和状态。这些特性使类成员成为与类实例关联的动态属性和行为。
因此,在 TypeScript 中,类成员不支持 const
关键字,因为它们具有可变性和与实例相关的特性,而不是常量的不可变性。
如果你希望在类中定义常量,可以使用类的静态成员或在类外部定义常量,并在类中引用它们。静态成员是与类本身相关联的属性和方法,而不是与类的实例相关联。可以使用 static
关键字来定义静态成员。
以下是一个示例,展示了在 TypeScript 中如何定义常量:
class MyClass {
static MY_CONSTANT: number = 10; // 定义一个静态常量
readonly myReadonlyProperty: string = "Hello"; // 只读属性,不能修改
myMethod(): void {
const localVariable: string = "World"; // 方法内部的局部常量
console.log(MyClass.MY_CONSTANT);
console.log(this.myReadonlyProperty);
console.log(localVariable);
}
}
console.log(MyClass.MY_CONSTANT); // 访问静态常量
const instance = new MyClass();
console.log(instance.myReadonlyProperty); // 访问只读属性
instance.myMethod(); // 调用方法并访问局部常量
在上述示例中,我们使用静态成员 MY_CONSTANT
定义了一个类级别的常量。通过类名加上成员名(例如 MyClass.MY_CONSTANT
),我们可以在类内外访问该常量。
另外,我们还使用了 readonly
关键字来定义只读属性,使其值在初始化后不能修改。
需要注意的是,即使 TypeScript 中的类成员不能具有 const
关键字,你仍然可以通过使用 readonly
属性或使用类的静态成员来实现类似的效果。
五、抽象类
TypeScript 中的抽象类:它是提供其他类继承的基类,不能直接被实例化。
用abstract关键字定义抽象类和抽象方法,抽象类中的抽象方法不包含具体实现并且必须在派生类(也就是其子类)中实现,abstract抽象方法只能放在抽象类里面。
我们常常使用抽象类和抽象方法用来定义标准。
//动物抽象类,所有动物都会跑(假设),但是吃的东西不一样,所以把吃的方法定义成抽象方法
abstract class Animal extends Object{
name: string;
constructor(name: string) {
super();
this.name = name;
}
abstract eat(): any;//抽象方法不包含具体实现并且必须在派生类中实现
run() {
console.log(this.name + "会跑")
}
}
class Dog extends Animal {
constructor(name: string) {
super(name);
}
eat(): any {//抽象类的子类必须实现抽象类里面的抽象方法
console.log(this.name + "吃骨头");
}
}
var d: Dog = new Dog("小狼狗");
d.eat();
class Cat extends Animal {
constructor(name: string) {
super(name);
}
eat(): any {//抽象类的子类必须实现抽象类里面的抽象方法
console.log(this.name + "吃老鼠");
}
}
var c: Cat = new Cat("小花猫");
c.eat();
六、多态
多态:父类定义一个方法不去实现,让继承它的子类去实现 ,每一个子类有不同的表现,多态属于继承。
//动物抽象类,所有动物都会跑(假设),但是吃的东西不一样,所以把吃的方法定义成抽象方法
abstract class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
abstract eat(): any;//抽象方法不包含具体实现并且必须在派生类中实现
run() {
console.log(this.name + "会跑")
}
}
class Dog extends Animal {
constructor(name: string) {
super(name);
}
eat(): any {//抽象类的子类必须实现抽象类里面的抽象方法
console.log(this.name + "吃骨头");
}
}
var d: Animal = new Dog("小狼狗");
d.eat();
class Cat extends Animal {
constructor(name: string) {
super(name);
}
eat(): any {//抽象类的子类必须实现抽象类里面的抽象方法
console.log(this.name + "吃老鼠");
}
}
var c: Animal = new Cat("小花猫");
c.eat();
第八章、接口
一、接口定义
在 TypeScript 中,接口(Interface)用于描述对象的形状,它可以定义对象的属性、方法等信息,但不包含具体的实现。接口可以被类实现(implement)或者对象类型(object type)声明。
在面向对象的编程中,接口是一种规范的定义,它定义了行为和动作的规范,在程序设计里面,接口起到一种限制和规范的作用。接口定义了某一批类所需要遵守的规范,接口不关心这些类的内部状态数据,也不关心这些类里方法的实现细节,它只规定这批类里必须提供某些方法,提供这些方法的类就可以满足实际需要。 typescrip中的接口类似于java,同时还增加了更灵活的接口类型,包括属性、函数、可索引和类等。
二、接口的特点
1、只针对引用数据类型
接口的用途就是对行为和动作进行规范和约束,跟抽象类有点像,但是,接口中不能有方法体,只允许有方法定义。
可被类实现或被其他引用数据类型声明
2、可重复声明
可以重复声明同一个接口,相当于接口内容合并
interface myInterface {
name: string,
age: number
}
interface myInterface{
gender: string
}
const obj: myInterface = {
name: "jzq",
age: 27,
gender: "男"
}
3、接口中只能有抽象方法和属性类型
接口中只能有属性名及其对应的属性值类型和抽象方法
三、函数形参属性类型接口
1、语法
interface 接口名{
属性名:属性值类型;
...
}
//对传入对象的属性约束,以下这个是一个属性接口
interface FullName {
firstName: string;
secondName: string;
}
function printName(name: FullName) {
console.log(name.firstName + "--" + name.secondName);
}
//传入的参数必须包含firstName、secondName
var obj = {
age: 20,
firstName: '张',
secondName: '三'
};
printName(obj);//正确
// printName("1213");//错误
四、函数类型接口
1、语法
interface 接口名{
(形参名:形参值类型,...): 返回值类型
}
//加密的函数类型接口
interface encrypt {
(key: string, value: string): string;
}
var md5: encrypt = function (key: string, value: string): string {
//模拟操作
return key + "----" + value;
}
console.log(md5("name", "zhangsan"));
var sha1: encrypt = function (key: string, value: string): string {
//模拟操作
return key + "====" + value;
}
console.log(sha1("name", "lisi"));
五、可索引型接口
可索引接口就是对数组、对象的约束,不常用。
1、语法
interface 接口名{
[index: 数据类型] :数据类型
}
//可索引接口,对数组的约束
interface UserArr {
[index: number]: string
}
var arr1: UserArr = ["aaa", "bbb"];
console.log(arr1[0]);
//可索引接口,对对象的约束
interface UserObj {
[index: string]: string
}
var arr2: UserObj = { name: '张三', age: '21' };
console.log(arr2);
六、类类型接口
类类型接口就是对类的约束,它和抽象类抽象有点相似。
1、语法
interface 接口名{
实例属性名:数据类型;
抽象方法名(形参名:数据类型):返回值类型;
}
interface Animal {
name: string;
eat(str: string): void;
}
class Dog implements Animal {
name: string;
constructor(name: string) {
this.name = name;
}
eat() {
console.log(this.name + "吃大骨头");
}
}
var d = new Dog("小狼狗");
d.eat();
class Cat implements Animal {
name: string;
constructor(name: string) {
this.name = name;
}
eat(food: string) {
console.log(this.name + "吃" + food);
}
}
var c = new Cat("小花猫");
c.eat("大老鼠");
七、接口的继承
接口可以继承接口,接口之间和抽象类之间的继承都是单向单继承,但是实现接口的子类可以实现多个接口。
简单来说,对于类、抽象类、接口继承只能单继承,但接口却可以多实现。
//人这个接口
interface Person {
eat(): void;
}
//程序员接口
interface Programmer extends Person {
code(): void;
}
//小程序接口
interface Web {
app(): void;
}
//前端工程师
class WebProgrammer implements Programmer, Web {
public name: string;
constructor(name: string) {
this.name = name;
}
eat() {
console.log(this.name + "下班吃饭饭")
}
code() {
console.log(this.name + "上班敲代码");
}
app() {
console.log(this.name + "开发小程序");
}
}
var w = new WebProgrammer("小贾");
w.eat();
w.code();
w.app();
第九章、泛型
一、泛型定义
在 TypeScript 中,泛型是一种类型,它允许在定义函数、类、接口等可重用的代码时,不指定具体的类型,而是将类型作为参数传递给代码。这使得代码具有更大的灵活性和可重用性。
二、语法
泛型的定义方式是在函数、类、接口等名称后面使用尖括号 <>
定义一个或多个泛型类型参数(该参数类型可随意),并在函数参数、返回值、类属性、方法参数等地方使用这些泛型类型参数。
三、定义一个泛型
1、函数泛型
以下是一个定义了一个泛型类型参数 T
的函数:
function identity<T>(arg: T): T {
return arg;
}
在上面的例子中,<T>
表示一个泛型类型参数,它可以代表任何类型。在函数的参数和返回值中,我们可以使用这个泛型类型参数 T
。
调用泛型函数时,可以明确指定泛型类型参数的类型,也可以让 TypeScript 推断出泛型类型参数的类型。例如,以下是调用 identity
函数的两种方式:
let output1 = identity<string>("hello world"); // 明确指定泛型类型参数为 string
let output2 = identity(42); // TypeScript 根据参数类型自动推断泛型类型参数为 number
在上面的例子中,identity
函数的返回值类型分别为 string
和 number
。
2、接口泛型
以下是一个定义了一个泛型类型参数 T
的接口:
interface Pair<T> {
first: T;
second: T;
}
在上面的例子中,Pair
接口定义了一个泛型类型参数 T
,并在接口的属性 first
和 second
中使用了这个泛型类型参数。
调用泛型接口时,同样可以明确指定泛型类型参数的类型,也可以让 TypeScript 推断出泛型类型参数的类型。例如,以下是使用 Pair
接口的两种方式:
let pair1: Pair<string> = { first: "hello", second: "world" }; // 明确指定泛型类型参数为 string
let pair2 = { first: 42, second: 3.14 }; // TypeScript 根据属性类型自动推断泛型类型参数为 number
在上面的例子中,pair1
的类型为 { first: string, second: string }
,pair2
的类型为 { first: number, second: number }
。
四、定义多个泛型
1、函数泛型
以下是一个定义了两个泛型类型参数 T
和 U
的函数:
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
在上面的例子中,<T, U>
表示定义了两个泛型类型参数 T
和 U
,并在函数的返回值类型 [T, U]
和参数类型 (first: T, second: U)
中使用了这两个泛型类型参数。
调用带有多个泛型类型参数的函数时,需要在调用时明确指定这些泛型类型参数的类型。例如,以下是调用 pair
函数的示例:
let result1 = pair<string, number>("hello", 42); // 明确指定泛型类型参数为 string 和 number
let result2 = pair(true, [1, 2, 3]); // 明确指定泛型类型参数为 boolean 和 number[]
在上面的例子中,result1
的类型为 [string, number]
,result2
的类型为 [boolean, number[]]
。注意,在调用泛型函数时,需要将泛型类型参数放在函数名的后面,使用尖括号 <>
包围。
2、接口泛型
以下是一个定义了两个泛型类型参数 T
和 U
的接口:
interface Pair<T, U> {
first: T;
second: U;
}
在上面的例子中,Pair
接口定义了两个泛型类型参数 T
和 U
,并在接口的属性 first
和 second
中使用了这两个泛型类型参数。
使用带有多个泛型类型参数的接口时,同样需要在使用时明确指定这些泛型类型参数的类型。例如,以下是使用 Pair
接口的示例:
let pair1: Pair<string, number> = { first: "hello", second: 42 }; // 明确指定泛型类型参数为 string 和 number
let pair2: Pair<boolean, string[]> = { first: true, second: ["a", "b", "c"] }; // 明确指定泛型类型参数为 boolean 和 string[]
在上面的例子中,pair1
的类型为 { first: string, second: number }
,pair2
的类型为 { first: boolean, second: string[] }
。
五、泛型继承
在 TypeScript 中,可以使用泛型继承来扩展一个泛型类型参数的约束范围,从而使得泛型类型参数必须满足更多的条件。
泛型继承的语法是使用 extends
关键字。例如,以下是一个定义了一个泛型类型参数 T
并对其进行了约束的函数:
function printLength<T extends { length: number }>(arg: T): void {
console.log(arg.length);
}
在上面的例子中,使用 extends
关键字对泛型类型参数 T
进行了约束,要求它必须具有一个名为 length
的数值属性。
在调用带有泛型继承的函数时,传入的参数必须满足这个约束条件。例如,以下是调用 printLength
函数的示例:
printLength("hello"); // 输出 5
printLength([1, 2, 3]); // 输出 3
printLength({ length: 4 }); // 输出 4
printLength({ name: "Alice" }); // 编译时报错,因为没有 length 属性
在上面的示例中,传入的第一个参数 "hello"
是一个字符串,它具有 length
属性,因此可以成功调用 printLength
函数并输出结果。传入的第二个参数 [1, 2, 3]
是一个数组,它也具有 length
属性,因此可以成功调用 printLength
函数并输出结果。传入的第三个参数 { length: 4 }
是一个对象,它同样具有 length
属性,因此也可以成功调用 printLength
函数并输出结果。传入的第四个参数 { name: "Alice" }
是一个对象,它没有 length
属性,因此在编译时就会报错。
除了对泛型类型参数进行约束外,还可以使用泛型继承来扩展一个泛型类型参数的类型范围。例如,以下是一个定义了两个泛型类型参数 T
和 U
并对它们进行了约束的函数:
function combine<T extends string, U extends string>(first: T, second: U): T | U {
if (typeof first === "string" && typeof second === "string") {
return first + second;
} else {
throw new Error("Both arguments must be strings");
}
}
在上面的例子中,使用 extends
关键字对泛型类型参数 T
和 U
进行了约束,要求它们必须是字符串类型。在函数的返回值类型中使用了联合类型 T | U
,表示函数的返回值可以是参数 first
的类型或者参数 second
的类型。
在调用带有泛型继承的函数时,传入的参数必须满足这个约束条件。例如,以下是调用 combine
函数的示例:
let result1 = combine("hello","world") // 返回值类型为 string
let result2 = combine("hello", 123); // 编译时报错,因为第二个参数不是字符串类型
在上面的示例中,传入的第一个参数 `"hello"` 是一个字符串,符合约束条件,传入的第二个参数 `"world"` 也是一个字符串,因此可以成功调用 `combine` 函数并返回字符串类型的结果。传入的第三个参数 `"hello"` 是一个字符串,符合约束条件,但传入的第四个参数 `123` 不是一个字符串,因此在编译时就会报错。 总之,泛型继承能够帮助我们定义更加灵活和可复用的泛型类型,从而提高代码的可读性和可维护性。
第四章、TypeScript模块化
TypeScript 默认采用 ECMAScript 2015 (ES6) 模块语法。TypeScript可以同时支持ES6和CommonJS模块系统的语法。这意味着当你在 TypeScript 代码中使用
import
和export
关键字时,它们会被翻译成对应的 ES6 模块语法。ES6 模块语法是一种用于组织和管理 JavaScript 代码的标准化方式,它允许你将代码分割成多个模块,并且可以方便地引入和导出这些模块。这使得代码更加模块化、可维护性更高,并且可以降低命名冲突和代码耦合度等问题。
当然,如果你需要在 TypeScript 代码中使用其他模块语法,比如 CommonJS 或 AMD 等,你可以在你的 TypeScript 代码中显式地指定对应的模块语法
但是需要注意的是,这些模块语法需要特定的模块加载器(例如 Node.js 或 RequireJS)才能正常工作。
一、全局模块
1、自定义全局模块
在 TypeScript 中,默认情况下,当你在一个新的 TypeScript 文件中编写代码时,它处于全局命名空间中。这意味着你可以在文件中直接访问全局变量和函数,而无需进行额外的导入操作。
然而,使用全局命名空间和全局变量是有一定风险的,因为它们容易导致命名冲突和代码维护问题。如果多个文件中定义了相同名称的全局变量,那么它们可能会相互覆盖,导致意外行为和错误。
例如:
foo.ts
const foo = 123;
bar.ts
const bar = foo; // allowed
2、默认全局模块
是指可以在整个应用程序中访问的模块或库。全局模块通常包含一组函数、类、接口或常量,它们被定义在一个独立的模块中,并且可以通过导入语句在应用程序的任何地方使用。
全局模块的特点是在引入之后无需额外的导入语句即可直接使用其中定义的内容。这是因为全局模块的定义已经注册到 TypeScript 的全局命名空间中,可以在任何地方访问和使用。
在使用全局模块之前,需要确保已经将其安装并配置好相应的类型声明文件(.d.ts
文件),以便 TypeScript 能够正确识别和类型检查全局模块中的内容。
常见的全局模块包括像 lodash
、moment
、axios
等第三方库,它们可以在应用程序中的任何地方直接使用,而无需每次都进行导入操作。
以下是一个示例,展示了如何在 TypeScript 中使用全局模块:
// 安装并配置了 lodash 库及其类型声明文件
// 使用全局模块
_.forEach([1, 2, 3], (num) => {
console.log(num);
});
在上述示例中,_.forEach
是 lodash
库中的一个全局函数,我们可以直接在代码中使用它,而无需额外导入 lodash
。这是因为 lodash
库已经被配置为全局模块,并且其类型声明文件已经正确安装和配置。
二、文件模块
-
文件模块也被称为外部模块。如果在你的 TypeScript 文件的根级别位置含有 import 或者 export,那么它会在这个文件中创建一个本地的作用域
-
模块是 TS 中外部模块的简称,侧重于代码和复用
-
模块在其自身的作用域里执行,而不是在全局作用域里
-
一个模块里的变量、函数、类等在外部是不可见的,除非你把它导出
-
如果想要使用一个模块里导出的变量,则需要导入
foo.ts
const foo = 123;
export {};
bar.ts
const bar = foo; // error
三、命名空间 namespace
1、命名空间定义
在 TypeScript 中,命名空间(namespace)是一种将代码组织到逻辑上相互关联的代码块中的方法,类似于模块。命名空间可以用来避免命名冲突,提高代码的可读性和可维护性。
在 TypeScript 中,命名空间可以使用 namespace
关键字来定义。一个命名空间可以包含变量、函数、类、接口等。下面是一个使用命名空间的示例:
// 定义命名空间
namespace MyNamespace {
export const pi = 3.14;
export function add(a: number, b: number) {
return a + b;
}
}
// 使用命名空间中的成员
console.log(MyNamespace.pi); // 3.14
console.log(MyNamespace.add(2, 3)); // 5
在上面的示例中,我们定义了一个名为 MyNamespace
的命名空间,其中包含了一个常量 pi
和一个函数 add
。通过使用 export
关键字,我们将这些成员导出,以便在其他地方可以访问到它们。
下面是一个命名空间的语法示例:
namespace MyNamespace {
// 命名空间中的变量
export const myVariable = 10;
// 命名空间中的函数
export function myFunction() {
console.log("Hello from MyNamespace!");
}
// 命名空间中的类
export class MyClass {
constructor(private name: string) {}
greet() {
console.log(`Hello, ${this.name}!`);
}
}
// 嵌套的命名空间
export namespace InnerNamespace {
// 嵌套命名空间中的变量
export const innerVariable = "Inner Variable";
// 嵌套命名空间中的函数
export function innerFunction() {
console.log("Hello from InnerNamespace!");
}
}
}
在上面的示例中,我们定义了一个名为 MyNamespace
的命名空间,并在其中包含了变量 myVariable
、函数 myFunction
、类 MyClass
和嵌套命名空间 InnerNamespace
。
要访问命名空间中的成员,可以使用点符号(.
)来进行访问,如下所示:
console.log(MyNamespace.myVariable); // 输出: 10
MyNamespace.myFunction(); // 输出: Hello from MyNamespace!
const obj = new MyNamespace.MyClass("John");
obj.greet(); // 输出: Hello, John!
console.log(MyNamespace.InnerNamespace.innerVariable); // 输出: Inner Variable
MyNamespace.InnerNamespace.innerFunction(); // 输出: Hello from InnerNamespace!
通过命名空间的名称和点符号,我们可以访问命名空间中定义的变量、函数和类。使用 export
关键字可以将命名空间中的成员导出,以便在外部模块中使用。
需要注意的是,为了在外部模块中使用命名空间的成员,需要使用 import
或 /// <reference>
来引入相应的命名空间。在较新的 TypeScript 版本中,使用 ES 模块语法的 import
更为常见和推荐。
另外,可以使用 export namespace
来定义导出的命名空间,这样外部模块可以直接访问命名空间中的成员,而无需通过命名空间的名称来访问。例如:
export namespace MyNamespace {
// ...
}
这样,在外部模块中就可以直接访问命名空间中的成员:
import { MyNamespace } from "./myNamespace";
console.log(MyNamespace.myVariable); // 输出: 10
这是一些基本的命名空间语法和用法示例。通过命名空间,可以将相关的代码逻辑组织在一起,并提供一种层次结构的方式来管理代码。
2、命名空间中的成员默认是私有的,只有在命名空间内部才能访问
如果不导出命名空间中的成员,外部模块将无法直接访问或使用命名空间中的内容。默认情况下,命名空间中的成员是私有的,只有在命名空间内部才能访问。
要在外部模块中使用命名空间中的成员,需要使用 export
关键字将它们导出。导出后,外部模块就可以通过引入命名空间,或者使用命名空间的全限定名称来访问命名空间中的成员。
示例代码:
// 命名空间不导出成员
namespace MyNamespace {
const privateVariable = 10; // 私有变量
export const publicVariable = 20; // 导出的公共变量
export function publicFunction() {
console.log("Hello from publicFunction!");
}
}
// 在外部模块中使用命名空间中的成员
console.log(MyNamespace.publicVariable); // 输出: 20
MyNamespace.publicFunction(); // 输出: Hello from publicFunction!
在上述示例中,我们将 publicVariable
和 publicFunction
导出,使其在外部模块中可见和可访问。
如果不导出命名空间中的成员,外部模块将无法直接访问这些成员,而只能通过内部的私有成员来间接操作。导出命名空间的成员可以提供封装和可复用的代码逻辑,并允许其他模块直接使用这些功能。
3、命名空间和文件模块的区别和联系
在 TypeScript 中,命名空间(Namespace)是一种组织和管理代码的方式,它提供了一种逻辑上的隔离和组织代码的机制。命名空间可以将相关的代码块封装在一个命名空间中,并通过命名空间来访问其中的成员。
命名空间在某种程度上类似于文件模块,它们都可以用于组织和封装代码。然而,它们之间存在一些区别。
命名空间主要用于逻辑上组织代码,将相关的功能和类型放在一个命名空间中,以避免全局命名冲突。通过使用命名空间,你可以将代码分割为不同的逻辑单元,并使用点符号来访问命名空间中的成员。命名空间提供了一种层次结构的组织方式,可以嵌套定义命名空间,并在命名空间中定义变量、函数、类等。
文件模块(File Module)则更加注重于文件级别的模块化。一个文件模块通常对应一个独立的文件,文件模块可以通过导入和导出语句与其他模块进行交互。文件模块提供了更加明确的模块化边界和依赖关系,可以方便地引入外部依赖并将内部功能暴露给其他模块使用。
虽然命名空间和文件模块都可以用于组织和管理代码,但随着 TypeScript 的发展,推荐使用文件模块作为模块化的方式,特别是在较大的项目中。文件模块提供了更强的封装性、可维护性和可重用性,并能够更好地处理模块之间的依赖关系。而命名空间则更适合用于较小的代码库或特定的场景,例如在将现有的全局命名空间迁移到模块化的代码中。
需要注意的是,在较新的 TypeScript 版本中,推荐使用 ES 模块语法(import/export)来进行模块化开发,而不是使用命名空间。ES 模块语法已成为 JavaScript 的标准模块化方案,可以与现代的 JavaScript 生态系统更好地集成。命名空间仍然存在,并且与 ES 模块可以互相使用,但在大多数情况下,使用 ES 模块更为常见和推荐。
命名空间只在 TypeScript 中有意义,在 JavaScript 中不存在这种概念。当 TypeScript 代码编译成 JavaScript 代码后,命名空间会被编译成一个对象。因此,要在 JavaScript 中使用 TypeScript 命名空间定义的成员,需要使用该对象来访问它们。
另外,随着 TypeScript 2.0 的发布,命名空间逐渐被模块(module)所取代。模块提供了更好的封装和依赖管理,推荐使用模块来组织代码。
第十章、类型声明文件与declare关键字
一、declare关键字
在 TypeScript 中,
declare
关键字用于声明全局变量、函数、类或命名空间,它告诉编译器某个标识符已经存在,但并不能在当前文件中进行具体的实现或定义。它通常用于与外部 JavaScript 库或环境进行交互,让 TypeScript 编译器知道这些变量或函数的存在,以便进行类型检查和编译。declare关键字使用在类型声明文件中
1、定义
declare
关键字有多种用法,具体取决于声明的内容和上下文。下面是一些常见的用法和示例说明:
1、声明全局变量或常量
declare const myVar: number;
declare let myGlobal: string;
declare var myLibrary: MyLibrary;
上述示例中,declare
用于声明全局变量 myVar
、myGlobal
和 myLibrary
,并指定它们的类型。
2、声明全局函数:
declare function myFunction(arg1: string, arg2: number): boolean;
上述示例中,declare
用于声明全局函数 myFunction
,指定了它的参数类型和返回类型。
3、声明全局类:
declare class MyClass {
constructor(name: string);
greet(): void;
}
上述示例中,declare
用于声明全局类 MyClass
,指定了它的构造函数和方法。
4、声明全局命名空间:
declare namespace MyNamespace {
function myFunction(): void;
let myVariable: number;
}
上述示例中,declare
用于声明全局命名空间 MyNamespace
,指定了其中的函数和变量。
通过使用 declare
关键字,可以告诉 TypeScript 编译器某些标识符的类型和存在,而无需提供具体的实现或定义。这样可以方便地与现有的 JavaScript 库或外部环境进行交互,同时享受 TypeScript 的类型检查和编译优势。
2、调用
在进行全局声明后,你可以直接在代码中使用声明的标识符进行调用或使用。由于全局声明只是告诉 TypeScript 编译器某个标识符的存在和类型,具体的实现或定义可能是在其他地方。
以下是一些示例说明如何调用全局声明的标识符:
1、调用全局变量或常量:
console.log(myVar); // 使用全局变量
myGlobal = 'Hello'; // 修改全局变量的值
console.log(myGlobal); // 打印全局变量的值
myLibrary.doSomething(); // 调用全局变量作为对象的方法
2、调用全局函数:
const result = myFunction('arg1', 123); // 调用全局函数
console.log(result); // 打印函数的返回值
3、创建全局类的实例并调用方法:
const myObj = new MyClass('John'); // 创建全局类的实例
myObj.greet(); // 调用类的方法
4、调用全局命名空间中的函数或变量:
MyNamespace.myFunction(); // 调用命名空间中的函数
console.log(MyNamespace.myVariable); // 打印命名空间中的变量的值
通过以上示例,你可以根据全局声明的类型和使用方式来调用相应的标识符。请确保在调用之前,已经进行了适当的全局声明,并确保所需的代码实现或定义可用。
3、同一个文件中不能使用declare关键字
对于在同一个文件中声明并使用命名空间,不需要使用 declare
关键字。declare
关键字通常用于在声明文件(.d.ts)中描述已存在的外部模块或库的类型定义。
在同一个文件中,你可以直接声明命名空间,并在其中定义和使用变量、函数、类等内容,而无需使用 declare
关键字。使用 declare
关键字会导致错误,因为它通常用于描述外部模块的类型声明。
如果你在同一个文件中使用 declare
关键字来声明命名空间,TypeScript 将会将其解释为对外部模块或库的类型声明,从而导致报错。因此,在同一个文件中声明并使用命名空间时,应避免使用 declare
关键字。
二、声明文件 .d.ts文件简介
1、声明文件定义
在 TypeScript 中,.d.ts 文件是声明文件的一种。它们通常用于描述一个 JavaScript 库或模块的类型,提供给 TypeScript 编译器用于类型检查和自动补全。这些文件只包含类型信息,没有具体实现代码。在编译时,TypeScript 编译器会读取这些声明文件并将它们与源代码结合起来,生成相应的 JavaScript 代码。
2、声明文件中可写内容
类型声明文件通常包含一些接口(interfaces)、类型别名(type aliases)、枚举(enumerations)和函数签名(function signatures),用来描述 JavaScript 库、框架或模块的类型信息。这些信息可以被 TypeScript 编译器或其他工具所使用,以提供更好的类型检查和代码提示。在 TypeScript 中,通常使用 .d.ts 文件来存储类型声明。在这些文件中,不需要实现任何 JavaScript 代码,只需要定义类型信息即可。
3、第三方声明文件
- 可以安装使用第三方的声明文件
- TypeScript 支持使用第三方声明文件来为 JavaScript 库提供类型定义,以便在 TypeScript 代码中进行类型检查和补全。
- @types 是一个约定的前缀,所有的第三方声明的类型库都会带有这样的前缀
- 约定是在安装第三方库的对应声明文件时,可以使用
@types
前缀,例如@types/lodash
。这样的约定使得使用第三方声明文件更加简单和一致。
- 约定是在安装第三方库的对应声明文件时,可以使用
- JavaScript 中有很多内置对象,它们可以在 TypeScript 中被当做声明好了的类型
- JavaScript 中有许多内置对象,如
Array
、Date
、Math
等。这些对象可以在 TypeScript 中作为预定义的类型使用,无需额外导入声明文件。
- JavaScript 中有许多内置对象,如
- 内置对象是指根据标准在全局作用域(Global)上存在的对象。这里的标准是指 ECMAScript 和其他环境(比如 DOM)的标准
- JavaScript 中有许多内置对象,如
Array
、Date
、Math
等。这些对象可以在 TypeScript 中作为预定义的类型使用,无需额外导入声明文件。
- JavaScript 中有许多内置对象,如
- 这些内置对象的类型声明文件,就包含在 TypeScript 核心库的类型声明文件中,具体可以查看ts 核心声明文件
总之,通过使用第三方声明文件和 TypeScript 核心库的类型声明文件,我们可以获得丰富的类型定义,以提升代码的可读性、维护性和可靠性。
三、查找声明文件原理
在 TypeScript 中,查找声明文件的原理是基于模块解析和配置文件的设置。当编译器遇到一个导入语句或类型引用时,它会根据一定的规则去查找相应的声明文件。
以下是 TypeScript 查找声明文件的原理:
1、配置文件的设置:
tsconfig.json 文件中的选项会影响声明文件的查找。typeRoots
和 types
字段用于指定声明文件的根路径或特定包的声明文件。baseUrl
字段用于指定模块解析的基准路径。
2、内置声明文件:
TypeScript 内置了一些常见库的声明文件,如 DOM 和 JavaScript 标准库的声明文件。这些声明文件可以直接被编译器找到和使用。
3、项目中的声明文件
TypeScript 会在当前项目的目录结构中查找声明文件。默认情况下,它会查找以 .d.ts
扩展名结尾的文件。
如果你在其他目录中编写了类型声明文件,而没有在 tsconfig.json 文件中配置相关选项,TypeScript 编译器会按照默认的查找规则进行查找。
默认情况下,TypeScript 编译器会在项目的根目录和子目录中查找类型声明文件。它会遍历整个项目目录结构,查找以 .d.ts
扩展名结尾的文件,并尝试与你的代码进行匹配。
如果你在其他目录中编写了类型声明文件,而没有在 tsconfig.json 文件中配置类型声明文件的根路径,编译器会在默认的搜索路径下进行查找。这意味着它会遍历项目的根目录和子目录,但不会主动查找其他目录。
所以,如果你的类型声明文件在其他目录中,建议将其放置在项目根目录或在 tsconfig.json 文件中配置类型声明文件的根路径,以确保编译器能够正确找到和使用这些声明文件。
4、声明文件的模块解析:
当遇到导入语句或类型引用时,TypeScript 会根据模块解析算法去查找相应的声明文件。模块解析可以是相对路径解析或模块路径解析,具体取决于导入语句中的路径形式和编译器配置。
综合以上原理,TypeScript 编译器会按照一定的规则和配置,在项目中查找合适的声明文件,并将其应用于类型检查、语法提示和补全等功能。
需要注意的是,为了能够正确查找到声明文件,确保你已经正确安装了相应的声明文件或类型声明库,并且在项目配置文件(如 tsconfig.json)中设置了正确的选项。
四、声明文件类型
在TypeScript中,类型声明文件(Type Declaration Files)用于描述 JavaScript 库、模块或其他代码的类型信息。类型声明文件使用.d.ts
作为文件扩展名,并包含了变量、函数、类等的类型声明。
类型声明文件可以定义多种类型,用于描述全局类型、模块类型、命名空间类型以及其他类型。以下是一些常见的类型声明文件中定义的类型:
1、使用declare
关键字:
在类型声明文件中,使用declare
关键字来定义类型,以告诉编译器某个标识符的类型信息。这可以是变量、函数、类等。
2、全局类型声明
全局类型声明用于描述全局变量、全局函数和全局类的类型。它们位于全局命名空间下,供整个项目使用。
3、模块类型声明
模块类型声明用于描述模块的类型信息,包括导出的变量、函数和类的类型。它们用于定义模块的接口,供其他模块导入和使用。
4、命名空间类型声明:
命名空间类型声明用于组织和封装相关的类型。它们提供一种将相关的类型和功能组织在一起的方式,并避免全局命名冲突。
-
枚举类型声明: 枚举类型声明用于定义枚举类型,即一组具名常量的集合。它们描述了可选的枚举值,并提供了与之相关的类型信息。
-
接口类型声明: 接口类型声明用于定义对象的结构和属性。它们描述了对象的属性名称、类型和方法签名等。
-
函数类型声明: 函数类型声明用于描述函数的参数和返回值的类型信息。它们包括函数的参数类型和返回值类型的定义。
-
类型别名声明: 类型别名声明用于为类型提供一个别名。它们允许你为复杂类型或类型组合创建简洁的别名,提高代码的可读性和可维护性。
此外,还有其他特定类型的声明,如泛型类型声明、联合类型声明、交叉类型声明等,用于描述更复杂的类型结构和组合。
这些类型声明可以根据需要进行组合和使用,以提供准确和详细的类型信息,以及增强开发过程中的类型检查和智能提示。
三、TypeScript
现在TypeScript
很流行。可以享受到面向对象、类型检查和编码提示等好处。
最终文件结构
root
|----node_modules/
|----package.json
|----protractor.conf.js
|----src/
|----page.ts
|----spec.ts
|----tsconfig.json
第五章、webpack的使用
一、webpack介绍
Webpack是一个现代化的静态模块打包工具,它是一个用于现代JavaScript应用程序的强大工具。Webpack能够把各种资源,如JS、CSS、HTML、图片、字体等等,视为模块,然后打包成适合生产环境部署的静态资源。
TypeScript是一种静态类型的编程语言,可以编译成JavaScript,并且可以在编译期间进行类型检查和错误检测。webpack是一个模块打包器,可以将多个模块打包成一个或多个文件,以便在浏览器中运行。它也支持TypeScript的编译和打包。在TypeScript中使用webpack可以让我们更加高效地管理和组织代码,以及更好地维护代码的可读性和可维护性。
Webpack的主要功能有:
-
支持模块化开发:Webpack能够将代码模块化,使用模块化开发可以有效地组织代码结构,提高代码的可维护性和可扩展性。
-
资源打包:Webpack可以将各种资源,如JS、CSS、HTML、图片、字体等等,视为模块,然后打包成适合生产环境部署的静态资源。
-
资源优化:Webpack能够对资源进行优化,如压缩JS、CSS、HTML等文件,将图片进行压缩和转换,实现对资源的最佳优化。
-
代码分割:Webpack支持将应用程序分割成多个块(chunks),从而实现代码分割,减少打包后文件的大小,提高应用程序的性能。
-
插件系统:Webpack支持使用各种插件进行功能扩展,用户可以自定义插件实现自己的需求。
总之,Webpack是一个强大的工具,能够帮助开发者高效地开发现代化的JavaScript应用程序。
二、webpack环境搭建
1、安装webpack
cnpm install -D webpack webpack-cli typescript ts-loader //-D 相当于--save-dev
查看版本:
npx webpack version
2、创建webpack.config.js配置文件
//引入Node.js内置模块
const path = require('path');
//webpack中所有的配置信息都应该写在module.exports对象中
module.exports = {
//指定入口文件
entry: "./src/index.ts",
//指定打包文件所在目录
output: {
//指定文件打包的目录
path: path.resolve(__dirname,'dist'),
//打包后的文件名
filename: "bundle.js"
},
//指定webpack打包时要使用的模块
module: {
//指定要加载的规则
rules: [
{
//test指定的是规则生效的文件
test: /\.ts$/,
//要使用的loader
use: 'ts-loader',
//要排除的文件
exclude: /node_modules/
}
]
}
}
3、创建tsconfig.json配置文件
用于配置TypeScript编译选项
4、在package.json文件中添加编译脚本
"scripts": {
"build": "webpack"
}
5、在命令行中运行编译命令
npm run build
以上是使用webpack打包TypeScript代码的基本步骤。需要注意的是,webpack会自动识别模块的依赖关系,并将其打包到一个或多个文件中。在webpack配置文件中,我们需要设置entry选项来指定程序的入口点,output选项来指定程序的输出文件。还需要设置resolve选项来告诉webpack在解析模块时应该搜索哪些文件扩展名,并且需要使用module选项来指定如何处理不同类型的模块。
三、插件安装
1、下载插件html-webpack-plugin
cnpm install -D html-webpack-plugin
2、下载插件clean-webpack-plugin
cnpm install -D clean-webpack-plugin
//引入html插件
const HTMLWebpackPlugin = require('html-webpack-plugin');
//引入clean插件
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
//配置webpack插件
plugins: [
new HTMLWebpackPlugin({
// title: "这是一个自定义个title"
//提供一个模版
template: "./src/index.html"
}),
new CleanWebpackPlugin(),
],
3、下载插件webpack-dev-server
cnpm install -D webpack-dev-server
修改 package.json 文件,添加一个启动命令:
"scripts": {
"dev": "webpack-dev-server --mode development"
}
在这个命令中,我们使用 webpack-dev-server
命令来启动服务器,并设置运行模式为 development。
- 在终端中运行
npm run dev
命令,启动开发服务器。
npm run dev
四、解决兼容性问题
1、插件安装
cnpm install -D @babel/core @babel/preset-env babel-loader core-js
五、webpack.config.js配置文件
//引入Node.js内置模块
const path = require('path');
//引入html插件
const HTMLWebpackPlugin = require('html-webpack-plugin');
//引入clean插件
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
//webpack中所有的配置信息都应该写在module.exports对象中
module.exports = {
//指定入口文件
entry: "./src/index.ts",
//指定打包文件所在目录
output: {
//指定文件打包的目录
path: path.resolve(__dirname, 'dist'),
//打包后的文件名
filename: "bundle.js",
//告诉webpack不使用箭头函数
environment:{
arrowFunction: false
}
},
//指定webpack打包时要使用的模块
module: {
//指定要加载的规则
rules: [
{
//test指定的是规则生效的文件
test: /\.ts$/,
//要使用的loader
use: [
//指定加载器
{
//指定加载器
loader: "babel-loader",
options: {
//设置预定义的环境
presets: [
[
//指定环境的插件
"@babel/preset-env",
//配置信息
{
//要兼容的目标浏览器
targets: {
"chrome":"58",
"ie":"11"
},
//指定corejs的版本
"corejs": "3",
//使用corejs的方式 ,“usage” 表示按需加载
"useBuiltIns": "usage"
},
],
],
}
},
'ts-loader'
],
//要排除的文件
exclude: /node_modules/
}
]
},
//配置webpack插件
plugins: [
new HTMLWebpackPlugin({
// title: "这是一个自定义个title"
//提供一个模版
template: "./src/index.html"
}),
new CleanWebpackPlugin(),
],
//设置引用模块
resolve: {
extensions: ['.ts','.js']
}
}
1、package.json
devDependencies
添加了jasmine types
的依赖,protractor
自带ts
申明文件。
这里的ts-node
读者可能没见过,它可以让ts
文件在node
上运行,详情见官网