TypeScript 类和函数

先看两篇文章关于 ES5 原型链的,写的特别好,图画的就更好了,参考资料:
帮你彻底搞懂JS中的prototype、proto与constructor(图解)
详解JavaScript中的new操作符

忍不住盗图一张:


原型链:f1.__proto__.__proto__.__proto__ === null;

一、TypeScript 类

1.1 介绍

TypeScript 的 class 和 ES2015 的 class 并不是完全一样的概念,ES6 的 class 仅仅是一种语法糖,但是这种语法糖已经成为标准,并且(新)浏览器内核基本支持,而平时使用的时候,基本都是通过 webpack 或者 gulp 这种进行兼容或者打包。

而 TypeScript 的 class 可以使得开发者在不依赖 ES5 的环境下直接使用,不需要等到下个 JS 版本。类的形式在 JavaScript 中有两种写法,ES5 的类和 ES6 的类,决定 TypeScript 翻译成哪种在 tsconfig.json 里面配置:

"target": "ESNEXT", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
1.2 TypeScript 类与 ES6 类的不同。

ES6 的类有 extends 和 static 这点和 TypeScript 没啥不同,不同的是 ES6 的属性修饰符只有默认公开属性和一个私有属性(还是在提案中)。私有属性还和 TypeScript 不同,使用 # 来定义的,官方还给出了理由。

之所以要引入一个新的前缀#表示私有属性,而没有采用private关键字,是因为 JavaScript 是一门动态语言,没有类型声明,使用独立的符号似乎是唯一的比较方便可靠的方法,能够准确地区分一种属性是否为私有属性。另外,Ruby 语言使用@表示私有属性,ES6 没有用这个符号而使用#,是因为@已经被留给了 Decorator

ES 和 TS 比除了属性修饰符不同,没有抽象类之外,其他的都差不多。

1.3 属性修饰符

1、public 默认
一些语言的默认属性修饰符是 protected,而 TypeScript 中成员属性默认都是 public,除了默认之外,也可以强制手动指定。

特点是:可以被继承、外部实例和内部 this 可以访问
2、private 私有属性

特点是:只能内部访问
3、protected
构造函数如果使用 protected 声明,说明这个类不能被实例化,只能被继承。

特点是:外部实例不能访问
4、readonly 修饰符
readonly 修饰符用于将属性设置为只读,如果要设置值,则只能在声明或者是在构造函数中初始化内容。
5、参数属性
定义的属性可以赋值默认值,不在构造函数进行初始化。

1.4 抽象类

抽象类作为其他子类的基类,一般不进行直接实例化,特别的像接口,但和接口不同的是,抽象类可以去实现成员属性或者是方法,然后子类再去覆写成员属性或者方法。

abstract class Person {
    abstract talk(): void;
    walk(): void {
        console.log('...')
    }
}

和其他高级语言的面向对象抽象类一样,如果一个方法被定义为 abstract(抽象方法),则这个方法可以不在基类中实现,但是它的子类必须去实现这个方法。抽象方法必须在抽象类里面。

abstract class Person {
    abstract talk(): void;
    walk(): void {
        console.log('...')
    }
}
new Person();//不允许,直接报错
class Male extends Person{ 
    talk(){}//必须有这个
}
1.5 TypeScript 类的其他知识

去参考:http://es6.ruanyifeng.com/#docs/class

二、函数

TypeScript 的函数主要增加了强类型判断和一个函数重载知识点。

2.1 函数类型

下面两个函数声明了参数的类型以及函数返回值的类型,上面是具名函数,下面是匿名函数

function add(x: number , y: number ): number {
   return x + y;
}

let myAdd = function(x: number, y: number) :number {
   return x + y;
}

虽然上面已经定义了函数的类型,但是匿名函数并没有定义完整的函数类型,完整的函数类型:

let myAdd: (x: number, y: number) => number  = function (x: number, y: number) : number {
    return x + y;
}

完整的函数类型包括:参数类型返回值类型

首先声明了参数类型 :(x: number, y: number) ,然后声明了返回值类型 => number =

而参数的名字实际上可以不一致,上面示例中虽然写了相同的名字,但是可以写成两个不同的参数名称:

let myAdd: (x1: number, y1: number) => number  = function (x: number, y: number) : number {
    return x + y;
}

返回值类型通过 => 进行表示,如果一个函数返回值是 void ,也必须声明是 void,不能省略因为完整的函数类型包含参数类型和返回值类型两个部分,一个也不能省略。

let myAdd: (x1: number, y1: number) => void  = function (x: number, y: number) : number {
    return x + y;
}

函数的类型只是由参数类型和返回值组成的。

函数中使用的捕获变量不会体现在类型里。 实际上,这些变量是函数的隐藏状态并不是组成API的一部分。

所谓的捕获变量,与函数本身是无关的,因为这个是由函数作用域外(父级作用域或更高级作用域)声明的变量,只是在函数中使用了而已。

const a:number = 1;
let add:(x: number) => number = function(x: number) :number {
    return a + x;
}
推断类型

完整的函数类型中,需要我们明确标示参数的类型是什么,但是如果两边的参数中只有一边的参数声明了类型,typescript 会尝试去识别类型:

let myAdd: (baseValue: number, increment: number) => number =
    function(x, y) { return x + y; };

上面代码中,因为 baseValueincrement 已经声明了类型是 number,所以 x 和 y 会推断出类型是 number。

2.2 可选参数和默认参数

TypeScript 里的每个函数参数都是必须的,这不是指不能传递 nullundefined 作为参数,而是说编译器检查用户是否为每个参数都传入了值。

编译器还会假设只有这些参数会被传递进函数。

简短地说,传递给一个函数的参数个数必须与函数期望的参数个数一致。

function add(x: number, y: number) :number {
    return x + y;
}

add(1,2);
add(1); // 错误
add(1,2,3); // 错误

上面 add 方法的使用中,后面两种都是错误的,因为传入的参数个数与期望的个数不相符合

如果需要声明某个参数是可选参数,则可以通过 ? 来标示:

function add(x:number, y:number, z?: number): number {
    z = z || 0;
    return x + y + z;
}

上面代码中通过 z? 声明 z 可选参数,因此传入 2 个或者3个参数都是可以的。

然而上面代码为了兼容 z 没有值得时候,写了个默认值处理 z = z || 0,而 ES6 有了函数参数默认值特性之后,这种写法基本都是被废弃的,在 TypeScript 中,可以使用同样的方式声明函数参数的默认值:

function add(x:number, y:number, z:number = 0): number {
    return x + y + z;
}

add(1, 3);
add(1,2,3);
add(1,2,3,4)

而一旦声明了默认值之后,就表示这个值可有可无,类似于已经加了 z? 声明。

需要注意的是,可选参数必须接在必需参数后面。换句话说,可选参数后面不允许再出现必需参数了

2.3 剩余参数

ES6 同样拥有 rest 参数,也就是剩余参数。它可以表示任意剩下的参数,本质是一个数组。

typescript 中同样通过省略号来表示剩下的多个参数:

function add(x: number, ...otherNum:number[]): number {
   otherNum && otherNum.forEach((item) => {
    x += item;
  });
   return x;
}
add(1, 3, 4, 54, 5, 6);
2.4 重载

重载:允许一个函数接受不同数量或类型的参数时,作出不同的处理。比如,我们需要实现一个函数 reverse,输入数字 123 的时候,输出反转的数字 321,输入字符串 'hello' 的时候,输出反转的字符串 'olleh'。
利用联合类型,我们可以这么实现:

function reverse(x: number | string): number | string {
    if (typeof x === 'number') {
        return Number(x.toString().split('').reverse().join(''));
    } else if (typeof x === 'string') {
        return x.split('').reverse().join('');
    }
}

然而这样有一个缺点,就是不能够精确的表达,输入为数字的时候,输出也应该为数字,输入为字符串的时候,输出也应该为字符串。
这时,我们可以使用重载定义多个 reverse 的函数类型:

function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string {
    if (typeof x === 'number') {
        return Number(x.toString().split('').reverse().join(''));
    } else if (typeof x === 'string') {
        return x.split('').reverse().join('');
    }
}

上例中,我们重复定义了多次函数 reverse,前几次都是函数定义,最后一次是函数实现。在编辑器的代码提示中,可以正确的看到前两个提示。

但是需要注意的是重载函数只是两个函数声明,并没有函数体。所以上面虽然有三个函数但是只有两个重载函数。

TypeScript 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 这是16年5月份编辑的一份比较杂乱适合自己观看的学习记录文档,今天18年5月份再次想写文章,发现简书还为我保存起的...
    Jenaral阅读 2,881评论 2 9
  • 函数和对象 1、函数 1.1 函数概述 函数对于任何一门语言来说都是核心的概念。通过函数可以封装任意多条语句,而且...
    道无虚阅读 4,673评论 0 5
  • "Unterminated string literal.": "未终止的字符串文本。", "Identifier...
    两个心阅读 8,480评论 0 4
  • 一、什么是 TypeScript? 百度解释:TypeScript是一种由微软开发的自由和开源的编程语言。它是Ja...
    wave浪儿阅读 2,252评论 1 20
  • 概述 TypeScript本质上是向JavaScript语言添加了可选的静态类型和基于类的面向对象编程,同时也支持...
    oWSQo阅读 8,563评论 1 45