编程语言中的高级应用有哪些?

    作者:课课家教育更新于: 2019-08-05 08:45:59

    大神带你学编程,欢迎选课

    java具有简单性、面向对象、分布式、健壮性、安全性、平台独立与可移植性、多线程、动态性等特点。Java可以编写桌面应用程序、Web应用程序、分布式系统和嵌入式系统应用程序等。

    当我们讨论 TypeScript 时,我们在讨论什么?TypeScript 中的 Decorator 较为特殊,为 Angular 团队和 TypeScript 团队交易的结果,有兴趣可自行搜索相关资料。

    TypeScript 的定位

    • Javascript 的超集
    • 编译期行为
    • 不引入额外开销
    • 不改变运行时行为
    • 始终与 ESMAScript 语言标准一致 (stage 3 语法)

    编程语言中的高级应用有哪些_编程语言_Java_Javascript_课课家

    TypeScript 中的 Decorator 较为特殊,为 Angular 团队和 TypeScript 团队交易的结果,有兴趣可自行搜索相关资料。而且近期 EcmaScript 规范中的 decorator 提案内容发生了剧烈变动,建议等此语法标准完全稳定后再在生产项目中使用。

    本文只讨论图中蓝色部分。

    类型的本质是契约

    JSDoc 也能标注类型,为什么要用 TypeScript?

    • JSDoc 只是注释,其标注没有约束作用
    • TS 有—checkJs 选项,但不好用

    TS 会自动推断函数返回值类型,为什么要多此一举标注出来?

    • 契约高于实现
    • 检查返回值是否写错
    • 写 return 时获得提醒

    开始之前

    几组 VSCode 快捷键

    • 代码补全 control + 空格 ctrl + 空格
    • 快速修复 command + . ctrl + .
    • 重构(重命名) fn + f2 f2

    一个网站

    TypeScript Playground

    初始化项目

    自行配置

    1. "compilerOptions": { 
    2.     "esModuleInterop"true
    3.     "allowSyntheticDefaultImports"true
    4.     "noImplicitAny"true
    5.     "strictNullChecks"true
    6.     "noImplicitThis"true
    7.     "moduleResolution""node" 
    8.   

    react 项目运行 create-react-app ${项目名} —scripts-version=react-scripts-ts

    小试牛刀

    &和 | 操作符

    虽然在写法上,这两个操作符与位运算逻辑操作符相同。但在语义上,它们与位运算刚好相反。

    位运算的表现:

    1. 1001 | 1010 = 1011    // 合并1 
    2. 1001 & 1010 = 1000    // 只保留共有1 

    在 TypeScript 中的表现:

    1. interface IA { 
    2.     a: string 
    3.     b: number 
    4.   
    5. type TB = { 
    6.     b: number 
    7.     c: number[] 
    8.   
    9. type TC = IA | TB;    // TC类型的变量的键只需包含ab或bc即可,当然也可以abc都有 
    10. type TD = IA & TB;    // TD类型的变量的键必需包含abc 

    对于这种表现,可以这样理解: & 表示必须同时满足多个契约, | 表示满足任意一个契约即可。

    interface 和 type 关键字

    interface 和 type 两个关键字因为其功能比较接近,常常引起新手的疑问:应该在什么时候用 type,什么时候用 interface?

    interface 的特点如下:

    • 同名 interface 自动聚合,也可以和已有的同名 class 聚合,适合做 polyfill
    • 自身只能表示 object/class/function 的类型

    建议库的开发者所提供的公共 api 应该尽量用 interface/class,方便使用者自行扩展。举个例子,我之前在给腾讯云 Cloud Studio 在线编辑器开发插件时,因为查阅到的 monaco 文档是 0.15.5 版本(当时的最新版本)的,而 Cloud Studio 使用的 monaco 版本为 0.14.3,缺失了一些我需要的 API,所以需要手动 polyfill 一下。

    1. /** 
    2.  * Cloud Studio使用的monaco版本较老0.14.3,和官方文档相比缺失部分功能 
    3.  * 另外vscode有一些特有的功能,必须适配 
    4.  * 故在这里手动实现作为补充 
    5.  */ 
    6. declare module monaco { 
    7.   interface Position { 
    8.     delta(deltaLineNumber?: number, deltaColumn?: number): Position 
    9.   } 
    10.   
    11. // monaco 0.15.5 
    12. monaco.Position.prototype.delta = function (this: monaco.Position, deltaLineNumber = 0, deltaColumn = 0) { 
    13.   return new monaco.Position(this.lineNumber + deltaLineNumber, this.column + deltaColumn); 
    14.   

    与 interface 相比,type 的特点如下:

    • 表达功能更强大,不局限于 object/class/function
    • 要扩展已有 type 需要创建新 type,不可以重名
    • 支持更复杂的类型操作
    1. type Tuple = [number, string]; 
    2. const a: Tuple = [2'sir']; 
    3. type Size = 'small' | 'default' | 'big' | number; 
    4. const b: Size = 24

    基本上所有用 interface 表达的类型都有其等价的 type 表达。但我在实践的过程中,也发现了一种类型只能用 interface 表达,无法用 type 表达,那就是往函数上挂载属性。

    1. interface FuncWithAttachment { 
    2.     (param: string): boolean
    3.     someProperty: number; 
    4.   
    5. const testFunc: FuncWithAttachment = ...; 
    6. const result = testFunc('mike');    // 有类型提醒 
    7. testFunc.someProperty = 3;    // 有类型提醒 

    extends 关键字

    extends 本意为 “拓展”,也有人称其为 “继承”。在 TypeScript 中,extends 既可当作一个动词来扩展已有类型;也可当作一个形容词来对类型进行条件限定(例如用在泛型中)。在扩展已有类型时,不可以进行类型冲突的覆盖操作。例如,基类型中键 a 为 string,在扩展出的类型中无法将其改为 number。

    1. type A = { 
    2.     a: number 
    3.   
    4. interface AB extends A { 
    5.     b: string 
    6. // 与上一种等价 
    7. type TAB = A & { 
    8.     b: string 

    泛型

    在前文我们已经看到类型实际上可以进行一定的运算,要想写出的类型适用范围更广,不妨让它像函数一样可以接受参数。TS 的泛型便是起到这样的作用,你可以把它当作类型的参数。它和函数参数一样,可以有默认值。除此之外,还可以用 extends 对参数本身需要满足的条件进行限制。

    在定义一个函数、type、interface、class 时,在名称后面加上<> 表示即接受类型参数。而在实际调用时,不一定需要手动传入类型参数,TS 往往能自行推断出来。在 TS 推断不准时,再手动传入参数来纠正。

    1. // 定义 
    2. class React.Component { ... } 
    3. interface IShowConfigextends IShowProps> { ... } 
    4. // 调用 
    5. class Modal extends React.Component { ... } 

    条件类型

    除了与、或等基本逻辑,TS 的类型也支持条件运算,其语法与三目运算符相同,为 T extends U ? X : Y 。这里先举一个简单的例子。在后文中我们会看到很多复杂类型的实现都需要借助条件类型。

    1. type IsEqualType = A extends B ? (B extends A ? true : false) : false
    2. type NumberEqualsToString = IsEqualType;   // false 
    3. type NumberEqualsToNumber = IsEqualType;    // true 

    环境 Ambient Modules

    在实际应用开发时有一种场景,当前作用域下可以访问某个变量,但这个变量并不由开发者控制。例如通过 Script 标签直接引入的第三方库 CDN、一些宿主环境的 API 等。这个时候可以利用 TS 的环境声明功能,来告诉 TS 当前作用域可以访问这些变量,以获得类型提醒。

    具体有两种方式,declare 和三斜线指令。

    1. declare const IS_MOBILE = true;    // 编译后此行消失 
    2. const wording = IS_MOBILE ? '移动端' : 'PC端'

    用三斜线指令可以一次性引入整个类型声明文件。

    1. ///  
    2. const range = new monaco.Range(2367); 

    深入类型系统

    基本类型

    基本类型,也可以理解为原子类型。包括 number、boolean、string、null、undefined、function、array、字面量(true,false,1,2,‘a’)等。它们无法再细分。

    复合类型

    TypeScript 的复合类型可以分为两类: set 和 map 。set 是指一个无序的、无重复元素的集合。而 map 则和 JS 中的对象一样,是一些没有重复键的键值对。

    1. // set 
    2. type Size = 'small' | 'default' | 'big' | 'large'
    3. // map 
    4. interface IA { 
    5.     a: string 
    6.     b: number 

    复合类型间的转换

    1. // map => set 
    2. type IAKeys = keyof IA;    // 'a' | 'b' 
    3. type IAValues = IA[keyof IA];    // string | number 
    4.   
    5. // set => map 
    6. type SizeMap = { 
    7.     [k in Size]: number 
    8. // 等价于 
    9. type SizeMap2 = { 
    10.     small: number 
    11.     default: number 
    12.     big: number 
    13.     large: number 

    map 上的操作

    1. // 索引取值 
    2. type SubA = IA['a'];    // string     
    3.   
    4. // 属性修饰符 
    5. type Person = { 
    6.     age: number 
    7.     readonly name: string    // 只读属性,初始化时必须赋值 
    8.     nickname?: string    // 可选属性,相当于 | undefined 

    映射类型和同态变换

    在 TypeScript 中,有以下几种常见的映射类型。它们的共同点是只接受一个传入类型,生成的类型中 key 都来自于 keyof 传入的类型,value 都是传入类型的 value 的变种。

    1. type Partial = { [P in keyof T]?: T[P] }    // 将一个map所有属性变为可选的 
    2. type Required = { [P in keyof T]-?: T[P] }    // 将一个map所有属性变为必选的 
    3. type Readonly = { readonly [P in keyof T]: T[P] }    // 将一个map所有属性变为只读的 
    4. type Mutable = { -readonly [P in keyof T]: T[P] }    // ts标准库未包含,将一个map所有属性变为可写的 

    此类变换,在 TS 中被称为同态变换。在进行同态变换时,TS 会先复制一遍传入参数的属性修饰符,再应用定义的变换。

    1. interface Fruit { 
    2.     readonly name: string 
    3.     size: number 
    4. type PF = Partial;    // PF.name既只读又可选,PF.size只可选 

    其他常用工具类型

    由 set 生成 map

    1. type Recordextends keyof any, T> = { [P in K]: T }; 
    2.   
    3. type Size = 'small' | 'default' | 'big'
    4. /* 
    5. { 
    6.     small: number 
    7.     default: number 
    8.     big: number 
    9. } 
    10.  */ 
    11. type SizeMap = Record

    保留 map 的一部分

    1. type Pickextends keyof T> = { [P in K]: T[P] }; 
    2. /* 
    3. { 
    4.     default: number 
    5.     big: number 
    6. } 
    7.  */ 
    8. type BiggerSizeMap = Pick'default' | 'big'>; 
    9.   

    删除 map 的一部分

    1. type Omit = Pick>; 
    2. /* 
    3. { 
    4.     default: number 
    5. } 
    6.  */ 
    7. type DefaultSizeMap = Omit'big'>; 

    保留 set 的一部分

    1. type Extract = T extends U ? T : never; 
    2.   
    3. type Result = 1 | 2 | 3 | 'error' | 'success'
    4. type StringResult = Extract;    // 'error' | 'success 

    删除 set 的一部分

    1. type Exclude = T extends U ? never : T; 
    2. type NumericResult = Exclude;    // 1 | 2 | 3 

    获取函数返回值的类型。但要注意不要滥用这个工具类型,应该尽量多手动标注函数返回值类型。理由开篇时提过, 契约高于实现 。用 ReturnType 是由实现反推契约,而实现往往容易变且容易出错,契约则相对稳定。另一方面,ReturnType 过多也会降低代码可读性。

    1. type ReturnType = T extends (...args: any[]) => infer R ?  R : any; 
    2.   
    3. function f() { return { a: 3, b: 2}; } 
    4. /* 
    5. { 
    6.     a: number 
    7.     b: number 
    8. } 
    9.  */ 
    10. type FReturn = ReturnType

    以上这些工具类型都已经包含在了 TS 标准库中,在应用中直接输入名字进行使用即可。另外,在这些工具类型的实现中,出现了 infer、never、typeof 等关键字,在后文我会详细解释它们的作用。

    类型的递归

    TS 原生的 Readonly 只会限制一层写入操作,我们可以利用递归来实现深层次的 Readonly。但要注意,TS 对最大递归层数做了限制,最多递归 5 层。

    1. type DeepReadony = { 
    2.     readonly [P in keyof T]: DeepReadony 
    3.   
    4. interface SomeObject { 
    5.   a: { 
    6.     b: { 
    7.       c: number; 
    8.     }; 
    9.   }; 
    10.   
    11. const obj: Readonly = { a: { b: { c: 2 } } }; 
    12. obj.a.b.c = 3;    // TS不会报错 
    13.   
    14. const obj2: DeepReadony = { a: { b: { c: 2 } } }; 
    15. obj2.a.b.c = 3;    // Cannot assign to 'c' because it is a read-only property. 

    never infer typeof 关键字

    never 是 | 运算的幺元,即 x | never = x 。例如之前的 Exclude 运算过程如下:

    infer 的作用是让 TypeScript 自己推断,并将推断的结果存储到一个临时名字中,并且只能用于 extends 语句中。它与泛型的区别在于,泛型是声明一个 “参数”,而 infer 是声明一个 “中间变量”。infer 我用得比较少,这里借用一下官方的示例。

    1. type Unpacked = 
    2.     T extends (infer U)[] ? U : 
    3.     T extends (...args: any[]) => infer U ? U : 
    4.     T extends Promise ? U : 
    5.     T; 
    6.   
    7. type T0 = Unpacked;  // string 
    8. type T1 = Unpacked;  // string 
    9. type T2 = Unpacked<() => string>;  // string 
    10. type T3 = Unpacked>;  // string 
    11. type T4 = Unpacked[]>;  // Promise 
    12. type T5 = Unpacked[]>>;  // string 

    typeof 用于获取一个 “常量” 的类型,这里的 “常量” 是指任何可以在编译期确定的东西,例如 const、function、class 等。它是从 实际运行代码 通向 类型系统 的单行道。理论上,任何运行时的符号名想要为类型系统所用,都要加上 typeof。但是 class 比较特殊不需要加,因为 ts 的 class 出现得比 js 早,现有的为兼容性解决方案。

    在使用 class 时, class 名 表示实例类型, typeof class 表示 class 本身类型。没错,这个关键字和 js 的 typeof 关键字重名了 :)。

    1. const config = { width: 2, height: 2 }; 
    2. function getLength(str: string) { return str.length; } 
    3.   
    4. type TConfig = typeof config;    // { width: number, height: number } 
    5. type TGetLength = typeof getLength;    // (str: string) => number 

    实战演练

    我在项目中遇到这样一种场景,需要获取一个类型中所有 value 为指定类型的 key。例如,已知某个 React 组件的 props 类型,我需要 “知道”(编程意义上)哪些参数是 function 类型。

    1. interface SomeProps { 
    2.     a: string 
    3.     b: number 
    4.     c: (e: MouseEvent) => void 
    5.     d: (e: TouchEvent) => void 
    6. // 如何得到 'c' | 'd' ?  

    分析一下这里的思路,我们需要从一个 map 得到一个 set,而这个 set 是 map 的 key 的 子集,筛选子集的 条件 是 value 的类型。要构造 set 的子集,需要用到 never ;要实现条件判断,需要用到 extends ;而要实现 key 到 value 的访问,则需要索引取值。经过一些尝试后,解决方案如下。

    1. type GetKeyByValueType = { 
    2.     [K in keyof T]: T[K] extends Condition ? K : never 
    3. } [keyof T]; 
    4.   
    5. type FunctionPropNames =  GetKeyByValueType;    // 'c' | 'd' 

    这里的运算过程如下:

    1. // 开始 
    2.     a: string 
    3.     b: number 
    4.     c: (e: MouseEvent) => void 
    5.     d: (e: TouchEvent) => void 
    6. // 第一步,条件映射 
    7.     a: never 
    8.     b: never 
    9.     c: 'c' 
    10.     d: 'd' 
    11. // 第二步,索引取值 
    12. never | never | 'c' | 'd' 
    13. // never的性质 
    14. 'c' | 'd' 

    编译提示 Compiler Hints

    TypeScript 只发生在编译期,因此我们可以在代码中加入一些符号,来给予编译器一些提示,使其按我们要求的方式运行。

    类型转换

    类型转换的语法为 < 类型名> xxx 或 xxx as 类型名 。推荐始终用 as 语法,因为第一种语法无法在 tsx 文件使用,而且容易和泛型混淆。一般只有这几种场景需要使用类型转换:自动推断不准;TS 报错,想不出更好的类型编写方法,手动抄近路;临时 “放飞自我”。

    在使用类型转换时,应该遵守几个原则:

    • 若要放松限制,只可放松到能运行的最严格类型上
    • 如果不知道一个变量的精确类型,只标注到大概类型(例如 any[])也比 any 好
    • 任何一段 “放飞自我”(完全没有类型覆盖)区代码不应超过 2 行,应在出现第一个可以确定类型的变量时就补上标注

    在编写 TS 程序时,我们的目标是让类型覆盖率无限接近 100%。

    ! 断言

    ! 的作用是断言某个变量不会是 null / undefined,告诉编译器停止报错。这里由用户确保断言的正确。它和刚刚进入 EcmaScript 语法提案 stage 3 的 Optional Chaining 特性不同。Optional Chaining 特性可以保证访问的安全性,即使在 undefined 上访问某个键也不会抛出异常。而 ! 只是消除编译器报错,不会对运行时行为造成任何影响。

    1. // TypeScript 
    2. mightBeUndefined!.a = 2 
    3. // 编译为 
    4. mightBeUndefined.a = 2 

    // @ts-ignore

    用于忽略下一行的报错,尽量少用。

    其他

    我为什么不提 enum

    enum 在 TS 中出现的比较早,它引入了 JavaScript 没有的数据结构(编译成一个双向 map),入侵了运行时,与 TypeScript 宗旨不符。用 string literal union('small' | 'big' | 'large')可以做到相同的事,且在 debug 时可读性更好。如果很在意条件比较的性能,应该用二进制 flag 加位运算。

    1. // TypeScript 
    2. enum Size { 
    3.     small = 3
    4.     big, 
    5.     large 
    6. const a:Size = Size.large;    // 5 
    7.   
    8. // 编译为 
    9. var Size; 
    10. (function (Size) { 
    11.     Size[Size["small"] = 3] = "small"
    12.     Size[Size["big"] = 4] = "big"
    13.     Size[Size["large"] = 5] = "large"
    14. })(Size || (Size = {})); 
    15. const a = Size.large; // 5 

    写在最后

    应该以什么心态来编写 TypeScript

    我们应该编写有类型系统的 JavaScript,而不是能编译成 JavaScript 的 Java/C#。任何一个 TypeScript 程序,在手动删去类型部分,将后缀改成 .js 后,都应能够正常运行。

     Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承、指针等概念,因此Java语言具有功能强大和简单易用两个特征。Java语言作为静态面向对象编程语言的代表,极好地实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程。

课课家教育

未登录

1