简介
typescript:javasscript的超集 ,添加了类型系统的 JavaScript,适用于任何规模的项目。
我们都知道JavaScript是一种弱类型的语言。而TypeScript增强了它的类型。
TypeScript 的[核心设计理念]:在完整保留 JavaScript 运行时行为的基础上,通过引入静态类型系统来提高代码的可维护性,减少可能出现的 bug。
TypeScript | JavaScript |
---|---|
JavaScript 的超集用于解决大型项目的代码复杂性 | 一种脚本语言,用于创建动态网页 |
可以在编译期间发现并纠正错误 是静态类型 | 作为一种解释型语言,只能在运行时发现错误,是动态类型 |
强类型,支持静态和动态类型 | 弱类型,没有静态类型选项 |
最终被编译成 JavaScript 代码,使浏览器可以理解 | 可以直接在浏览器中使用 |
支持模块、泛型和接口 | 不支持模块,泛型或接口 |
社区的支持仍在增长,而且还不是很大 | 大量的社区支持以及大量文档和解决问题的支持 |
动态类型是指在运行时才会进行类型检查
静态类型是指编译阶段就能确定每个变量的类型
1. 安装typescript
npm install -g typescript
编译ts文件(生成对应的js文件,)
tsc xxx.ts
2. 数据类型
原始数据类型:Boolean , String , Number , null , undefined ,Symbol
其他数据类型: Arrsy , Enum , Any , Never , Object , Void , Unknow , Tuple
- Symbol
es6中新增的一个类型,表示独一无二的值.Symbol 值通过Symbol()函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。
let a = Symbol()
let b = Symbol('b')
let d = Symbol()
a == b // false
- unknow ,any
在ts中any被成为全局超级类型,可以逃避类型检测
unknow也是另一种超级类型,但是unknow类型只能被赋值 unknow类型 和any类型
let value:unknown ;
let value2: any =value
let value3: boolean = value // Type 'unknown' is not assignable to type 'boolean
- tuple
一般数组是由同种类型的值组成,但是元组可以满足数组中含有不同类型的要求,
let data:[boolean , number , string] = [true ,123,'111']
3. 类型推论
如果没有明确的指定类型,那么 TypeScript 会依照类型推论(Type Inference)的规则推断出一个类型。
let c = 12345;
console.log(typeof c) // number
c = 'hahahha' // Error: Type 'string' is not assignable to type 'number'
4. 联合类型
取值可以为多种类型中的一种
let a :number | string | boolean;
a = 4
a = 'www'
a = true
当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法:
5. class
虽然 JavaScript 中有类的概念,但是可能大多数 JavaScript 程序员并不是非常熟悉类,这里对类相关的概念做一个简单的介绍。
- 类(Class):定义了一件事物的抽象特点,包含它的属性和方法
- 对象(Object):类的实例,通过
new
生成 - 面向对象(OOP)的三大特性:封装、继承、多态
- 封装(Encapsulation):将对数据的操作细节隐藏起来,只暴露对外的接口。外界调用端不需要(也不可能)知道细节,就能通过对外提供的接口来访问该对象,同时也保证了外界无法任意更改对象内部的数据
- 继承(Inheritance):子类继承父类,子类除了拥有父类的所有特性外,还有一些更具体的特性
- 多态(Polymorphism):由继承而产生了相关的不同的类,对同一个方法可以有不同的响应。比如
Cat
和Dog
都继承自Animal
,但是分别实现了自己的eat
方法。此时针对某一个实例,我们无需了解它是Cat
还是Dog
,就可以直接调用eat
方法,程序会自动判断出来应该如何执行eat
- 存取器(getter & setter):用以改变属性的读取和赋值行为
- 修饰符(Modifiers):修饰符是一些关键字,用于限定成员或类型的性质。比如
public
表示公有属性或方法 - 抽象类(Abstract Class):抽象类是供其他类继承的基类,抽象类不允许被实例化。抽象类中的抽象方法必须在子类中被实现
- 接口(Interfaces):不同类之间公有的属性或方法,可以抽象成一个接口。接口可以被类实现(implements)。一个类只能继承自另一个类,但是可以实现多个接口
class Greeter {
// 静态属性
static cname: string = "Greeter";
// 成员属性
greeting: string;
// 构造函数 - 执行初始化操作
constructor(message: string) {
this.greeting = message;
}
// 静态方法
static getClassName() {
return "Class name is Greeter";
}
// 成员方法
greet() {
return "Hello, " + this.greeting;
}
}
let greeter = new Greeter("world");
那么成员属性与静态属性,成员方法与静态方法有什么区别呢?可以直接看一下编译生成的 ES5 代码:
var Greeter = (function () {
// 构造函数 - 执行初始化操作
function Greeter(message) {
this.greeting = message;
}
// 静态方法
Greeter.getClassName = function () {
return "Class name is Greeter";
};
// 成员方法
Greeter.prototype.greet = function () {
return "Hello, " + this.greeting;
};
// 静态属性
Greeter.cname = "Greeter";
return Greeter;
}());
var greeter = new Greeter("world");
greeter.getClassName() // Property 'getClassName' does not exist on type 'Greeter'. Did you mean to access the static member 'Greeter.getClassName' instead?
Greeter.getClassName() // Class name is Greeter
私有字段
class Person {
#name: string;
constructor(name: string) {
this.#name = name;
}
greet() {
console.log(`Hello, my name is ${this.#name}!`);
}
}
let semlinker = new Person("Semlinker");
semlinker.#name;
// ~~~~~
// Property '#name' is not accessible outside class 'Person'
// because it has a private identifier.
与常规属性(甚至使用 private 修饰符声明的属性)不同,私有字段要牢记以下规则:
私有字段以 # 字符开头,有时我们称之为私有名称;
每个私有字段名称都唯一地限定于其包含的类;
不能在私有字段上使用 TypeScript 可访问性修饰符(如 public 或 private);
私有字段不能在包含的类之外访问,甚至不能被检测到。
抽象类
使用 abstract 关键字声明的类,我们称之为抽象类。抽象类不能被实例化,因为它里面包含一个或多个抽象方法。所谓的抽象方法,是指不包含具体实现的方法:
abstract class Person {
constructor(public name: string){}
abstract say(words: string) :void;
}
// Cannot create an instance of an abstract class.(2511)
const lolo = new Person(); // Error
抽象类不能被直接实例化,我们只能实例化实现了所有抽象方法的子类。
abstract class Person {
constructor(public name: string){}
// 抽象方法
abstract say(words: string) :void;
}
class Developer extends Person {
constructor(name: string) {
super(name);
}
say(words: string): void {
console.log(`${this.name} says ${words}`);
}
}
const lolo = new Developer("lolo");
lolo.say("I love ts!"); // lolo says I love ts!
6. implements / extends
- implements
实现, 一个新的类,从父类或者接口实现所有的属性和方法,同时可以重写属性和方法,包含一些新的功能 - extends
继承,一个新的接口或者类,从父类或者接口继承所有的属性和方法,不可以重写属性,但可以重写方法
interface Test {
x:number;
y:number
}
class TestDemo implements Test{
x = 1;
y = 2;
sayHello(){
console.log(`${this.x}--${this.y}say hello`)
}
}
class Test2Demo extends TestDemo{
y = 4
sayHello(){
console.log(`${this.x}--${this.y}say hello 2`)
}
}
const a = new Test2Demo()
const b = new TestDemo()
a.sayHello();
b.sayHello()
//1--4say hello 2
//1--2say hello
- 只有类才能实现和继承类
- 可以多实现和多继承
7. 泛型
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
function createArray(length: number, value: any): Array<any> {
let result = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
createArray(3, 'x'); // ['x', 'x', 'x']
无法精准定位返回的数据类型,any可以是任意类型
function createArray<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
createArray<string>(3, 'x'); // ['x', 'x', 'x']
这样可以精准推算出返回的数据类型
其中 T 代表 Type,在定义泛型时通常用作第一个类型变量名称。但实际上 T 可以用任何有效名称代替。除了 T 之外,以下是常见泛型变量代表的意思:
K(Key):表示对象中的键类型;
V(Value):表示对象中的值类型;
E(Element):表示元素类型。
- 泛型接口
当然也可以使用含有泛型的接口来定义函数的形状:
interface CreateArrayFunc {
<T>(length: number, value: T): Array<T>;
}
let createArray: CreateArrayFunc;
createArray = function<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
createArray(3, 'x'); // ['x', 'x', 'x']
- 泛型类
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) {
return x + y;
};
- 泛型约束
在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法:所有可以对泛型进行约束,必须含有特定的属性
function loggingIdentity<T extends TypeConstraint>(arg: T): T {
console.log(arg.length);
console.log(arg.personName);
return arg
}
loggingIdentity({personName:'hahah' ,length:4}) // 4 , 'hahah'
- 在泛型约束中使用类型参数
可以声明受另一个类型参数约束的类型参数。
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
let x = { a: 1, b: 2, c: 3, d: 4 };
getProperty(x, "a"); 1
getProperty(x, "m"); //Error Argument of type '"m"' is not assignable to parameter of type '"a" | "b" | "c" | "d"'.
8. 装饰器
8.1 装饰器是什么
- 它是一个表达式
- 该表达式被执行后,返回一个函数
- 函数的入参分别为 target、name 和 descriptor
- 执行该函数后,可能返回 descriptor 对象,用于配置 target 对象
8.2 装饰器的分类
- 类装饰器(Class decorators)
- 属性装饰器(Property decorators)
- 方法装饰器(Method decorators)
- 参数装饰器(Parameter decorators)
需要注意的是,若要启用实验性的装饰器特性,你必须在命令行或 tsconfig.json
里启用 experimentalDecorators
编译器选项:
Class decorators
// Class decorators
function classDecorator<T extends {new(...args:any[]):{}}>(constructor: T){
return class extends constructor{
newProperty ='new property';
hello = 'override';
}
}
function classFactoryDecorator(num:number){
return function(constructor:Function){
constructor.prototype.luckyNumber = Math.floor(Math.random() * num)
}
}
@classDecorator
@classFactoryDecorator(20)
class TestClass {
property = "property";
hello: string;
constructor(m: string) {
this.hello = m;
}
}
let greeting = new TestClass('hah');
console.log(greeting.hello , greeting.property , greeting.luckyNumber)
// "override", "property", 9
Property decorators
// Property decorators
function Min(num:number){
return function(target:Object , properrtyKey:string){
let value:string;
const setter = function(newVal:string){
if(newVal.length < num){
throw new Error(`Your password should be bigger than ${num}`)
}
value = newVal
}
const getter = function(){
return value
}
Object.defineProperty(target , properrtyKey , {
get:getter,
set:setter
})
}
}
class Password{
@Min(4)
password:string;
constructor(password:string){
this.password = password
}
}
let password = new Password('121212')
console.log(password.password)
let password2 = new Password('12') // Error :Your password should be bigger than 4
Method decorators
// declare type MethodDecorator = <T>(target:Object, propertyKey: string | symbol,
// descriptor: TypePropertyDescript<T>) => TypedPropertyDescriptor<T> | void;
function logger(target:Object , propertyKey:string , descriptor :PropertyDescriptor){
console.log(`${propertyKey}is working`)
}
function doubleResult(){
return function(target:Object , propertyKey:string , descriptor :PropertyDescriptor){
return {
value: function(...args:any[]){
var result = descriptor.value.apply(this, args) * 2;
return result;
}
}
}
}
class MethodDecoratorClass {
newName:string ;
constructor(newName:string){
this.newName = newName;
}
@logger
changeName(name:string){
this.newName = name
}
@doubleResult()
sum(x:number , y:number){
return x + y
}
}
let demo = new MethodDecoratorClass('hello');
demo.changeName('hahahh')
console.log(demo.newName)
console.log(demo.sum(1 ,2))
// [LOG]: "changeNameis working"
// [LOG]: "hahahh"
// [LOG]: 6
Parameter decorators
function notNull(target: any, propertyKey: string, parameterIndex: number) {
console.log("param decorator notNull function invoked ");
Validator.registerNotNull(target, propertyKey, parameterIndex);
}
function validate(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("method decorator validate function invoked ");
let originalMethod = descriptor.value;
//wrapping the original method
descriptor.value = function (...args: any[]) {//wrapper function
if (!Validator.performValidation(target, propertyKey, args)) {
console.log("validation failed, method call aborted: " + propertyKey);
return;
}
let result = originalMethod.apply(this, args);
return result;
}
}
class Validator {
private static notNullValidatorMap: Map<any, Map<string, number[]>> = new Map();
//todo add more validator maps
static registerNotNull(target: any, methodName: string, paramIndex: number): void {
let paramMap: Map<string, number[]> = this.notNullValidatorMap.get(target);
if (!paramMap) {
paramMap = new Map();
this.notNullValidatorMap.set(target, paramMap);
}
let paramIndexes: number[] = paramMap.get(methodName);
if (!paramIndexes) {
paramIndexes = [];
paramMap.set(methodName, paramIndexes);
}
paramIndexes.push(paramIndex);
}
static performValidation(target: any, methodName: string, paramValues: any[]): boolean {
let notNullMethodMap: Map<string, number[]> = this.notNullValidatorMap.get(target);
if (!notNullMethodMap) {
return true;
}
let paramIndexes: number[] = notNullMethodMap.get(methodName);
if (!paramIndexes) {
return true;
}
let hasErrors: boolean = false;
for (const [index, paramValue] of paramValues.entries()) {
if (paramIndexes.indexOf(index) != -1) {
if (!paramValue) {
console.error("method param at index " + index + " cannot be null");
hasErrors = true;
}
}
}
return !hasErrors;
}
}
class Task {
@validate
run(@notNull name: string): void {
console.log("running task, name: " + name);
}
}
console.log("-- creating instance --");
let task: Task = new Task();
console.log("-- calling Task#run(null) --");
//task.run(null);
console.log("----------------");
console.log("-- calling Task#run('test') --");
task.run("test");
// [LOG]: "param decorator notNull function invoked "
// [LOG]: "method decorator validate function invoked "
// [LOG]: "-- creating instance --"
// [LOG]: "-- calling Task#run(null) --"
// [LOG]: "----------------"
// [LOG]: "-- calling Task#run('test') --"
// [LOG]: "running task, name: test"
9.namespace
随着方法属性的增多,以便于在记录它们类型的同时还不用担心与其它对象产生命名冲突。 因此,我们把属性和方法包裹到一个命名空间内,而不是把它们放在全局命名空间下。
namespace Test {
export interface TestInterface{
location?:string
}
export const personName:string = 'hahahah';
export const getPersonName = () => {
return personName
}
}
let test:Test.TestInterface;
let nameNew = Test.getPersonName();
console.log(nameNew) // 'hahahah'
9.JSX
JSX是一种嵌入式的类似XML的语法。 它可以被转换成合法的JavaScript,尽管转换的语义是依据不同的实现而定的。 JSX因React框架而流行,但也存在其它的实现。 TypeScript支持内嵌,类型检查以及将JSX直接编译为JavaScript。
想要使用JSX必须做两件事:
- 给文件一个
.tsx
扩展名 - 启用
jsx
选项
TypeScript具有三种JSX模式:preserve
,react
和react-native
。 这些模式只在代码生成阶段起作用 - 类型检查并不受影响。 在preserve
模式下生成代码中会保留JSX以供后续的转换操作使用(比如:Babel)。 另外,输出文件会带有.jsx
扩展名。 react
模式会生成React.createElement
,在使用前不需要再进行转换操作了,输出文件的扩展名为.js
。 react-native
相当于preserve
,它也保留了所有的JSX,但是输出文件的扩展名是.js
。
网友评论