之前介绍了一下TypeScript的基本配置和基本类型,接下来就开始一些跟之前原生JS区别很大的内容.
1.接口 interfaces
首先JS是没有接口的这个概念的,但是接口还是在JAVA,OC里都是比较常见的概念.JAVA接口和OC的.h文件声明接口,就是一些方法的集合,只有方法的声明,没有对应方法的实现,这些方法可能在不同的地方被实现.在JS里只能模拟这种接口
2.鸭子类型 duck typing
动态类型语言对变量类型的宽容给实际编码带来了很大的灵活性,无需检测,也不需要考虑变量是否拥有这个方法,随意去调用任何的方法,这些便利都得益于鸭子类型.
鸭子类型这个概念源自一个谚语"当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子",比如现在有两个变量,一个是数组arr,另外一个变量也能能使用length,也能使用下标来获取值,那就可以认为另外一个变量也是一个数组.
再举一个例子:一只小老鼠被猫盯上了,情急之下,它学了狗叫,猫撤了之后,小老鼠的妈妈不无感叹的对它说:看吧,我让你学的这门儿外语多么重要啊.这里也使用了鸭子类型,当猫抓老鼠的时候,老鼠学了狗叫,让猫以为那是条狗,所以逃跑.
鸭子类型总得来说,关注的不是对象的类型本身,而是它是如何使用的.
3.JS的接口
通过鸭子类型实现接口,下面的代码是在网上摘录下来得,大家可以借鉴一下
//一: 接口类 Class Interface ==>实例化N多个接口
/**
* 接口类需要2个参数
* 参数1: 接口的名字 (string)
* 参数2: 接受方法名称的集合(数组) (array)
*/
var Interface = function(name , methods){
//判断接口的参数个数
if(arguments.length != 2){
throw new Error('argument.length must be 2');
}
this.name = name;
this.methods = [];//定义一个内置的空数组对象 等待接受methods里的元素(方法名字)
for(var i = 0, len = methods.length ;i < len; i++ ){
if(typeof methods[i] != 'string'){
throw new Error('methods.type must be string');
}
this.methods.push(methods[i]);
}
}
// 二: 准备工作:
// 1 实例化接口对象
var CompositeInterface = new Interface('CompositeInterface' , ['add' , 'remove']);
var FormItemInterface = new Interface('FormItemInterface' , ['update','select']);
// CompositeImpl implements CompositeInterface , FormItemInterface
// 2 具体的实现类
var CompositeImpl = function(){
}
// 3 实现接口的方法implements methods
CompositeImpl.prototype.add = function(){
alert('add...');
}
CompositeImpl.prototype.update = function(){
alert('update...');
}
CompositeImpl.prototype.select = function(){
alert('select...');
}
CompositeImpl.prototype.remove = function(){
alert('remove...');
}
// 三:检验接口里的方法
// 如果检验通过 不做任何操作 不通过:浏览器抛出error
// 这个方法的目的 就是检测方法的
Interface.ensureImplements = function(object){
// 如果检测方法接受的参数小于2个 参数传递失败!
if(arguments.length < 2){
throw new Error('Interface.ensureImplements method constructor arguments must be >= 2!');
}
// 获得接口实例对象
for(var i = 1; i < arguments.length;i++){
var instanceInterface = arguments[i];
if(instanceInterface.constructor != Interface){
throw new Error('the arguments constructor not be Interface Class');
}
// 循环接口实例对象里面的每一个方法
for(var j=0;j < instanceInterface.methods.length;j++){
var methodName = instanceInterface.methods[j];
// object[key] 就是方法
if(!object[methodName] || typeof object[methodName] != 'function'){
throw new Error("the method name '" + methodName + "' is not found !");
}
}
}
}
// 最后使用
var c1 = new CompositeImpl();
Interface.ensureImplements(c1,CompositeInterface,FormItemInterface);
c1.add();
4.TypeScript接口
TypeScript里,接口的作用就是为类型命名和代码体统的一套契约,通过契约来判断属性是否存在,也可以认为接口就是一个自定义的类型
interface LabelledValue {
label: string;
}
function printLabel(labelledObj: LabelledValue){
console.log(labelledObj.label);
}
let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);
LabelledValue相当于接口的名,在函数printLabel里,需要传一个参数,指定参数类型就是接口类型,就是LabelledValue类型,然后在函数里调用传进去对象的属性,如果调用的是size,就会在文件里报错,因为在对应接口里,并没有对应属性.接口里的属性只要存在并且类型正确就可以.
5.可选属性
在接口里,有些属性不一定是必须的,有些属性可能在某些条件下存在,这时候,为了避免在书写,对这样的属性,在可选的属性名字定的的后面加一个?符号
interface SquareConfig {
color?: string;
width?: number;
}
6.只读属性
属性的设置前,需要在属性名前,用readonly来修饰属性,通过这个类型构建出来的对象,属性值不允许变化
interface Point {
readonly x: number;
readonly y: number;
}
let p1: Point = { x: 10, y: 20 };
p1.x = 5; // error!
TypeScrip还有一个类型ReadonlyArray<T>,这个类型跟之前说过的数组类型相似,只是所有对数组操作的方法都去掉了
readonly和const很相似,区别就在于做为变量使用的话用const
,若做为属性则使用readonly.
7.函数类型
除了带属性的普通对象之外,接口也可以描述函数类型.
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
let result = source.search(subString);
if (result == -1) {
return false;
} else {
return true;
}
}
接口里括号里是函数类型的参数,但是参数名可以与接口里定义的名不匹配,冒号后是返回值的类型,这个类型要相同,否则编译器会有警告
8.可索引的类型
这种接口的使用类似数组和对象,通过索引找到对应值,接口支持两种类型的索引,数字和字符串
// 数组元素是数字
interface StringArray {
[index: number]: number;
}
var myArray: StringArray = [1, 2];
console.log(myArray[0]);
// 数组是字符串
interface StringArray {
[index: number]: string;
}
var myArray: StringArray = ["Bob", "Fred"];
console.log(myArray[0]);
接口里方括号里index代表了索引,冒号代表索引类型,方括号外的number代表了取值的类型.如果接口里同时使用两种类型的索引,数字索引的返回值必须是字符串索引返回值类型的子类型,字符串对应的是父类,数字对应的是子类
class Animal {
name: string;
}
class Dog extends Animal {
breed: string;
}
interface NotOkay {
[x: number]: Dog;
[x: string]: Animal;
}
这是正确的写法,Dog这个类是Animal这个类的子类,所以number对应的是子类Dog,string对应的是父类Animal.当使用 number来索引时,JavaScript会将它转换成string然后再去索引对象.
index的类型可以用来面熟常用的数组和dictionary (字典),需要所有属性和返回值匹配
interface Dictionary {
[index: string]: number;
length: number; // 可以,length是number类型
name: string // 错误,`name`的类型不是索引类型的子类型
}
可以理解为number代表了string的子类型,所以不会报错.最后还能将index设置成只读,只需要加上readonly就可以了
9.类类型
类类型就是明确用来强制一个类去符合某种契约
interface ClockInterface {
currentTime: Date;
setTime(d: Date);
}
class Clock implements ClockInterface {
currentTime: Date;
setTime(d: Date) {
this.currentTime = d;
}
constructor(h: number, m: number) { }
}
声明了一个接口,一个日期类型的属性,还有一个setTime的方法,这个方法在类里实现.在类里方法和属性分为静态部分和实例部分,对应接口来讲,只能对实例部分进行类型检查
interface ClockConstructor {
new (hour: number, minute: number);
}
class Clock implements ClockConstructor {
currentTime: Date;
constructor(h: number, m: number) { }
}
这里就会报错,原因在于借口创建了一个类的实例,也就是new了一个对象,这个对象在创建的时候需要使用类的构造器,也就是constructor,它就是类的静态部分的内容,接口检测不到,所以会报错.因此,在操作类的静态部分,可以定义两个接口
interface ClockConstructor {
new (hour: number, minute: number): ClockInterface;
}
interface ClockInterface {
tick();
}
function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface {
return new ctor(hour, minute);
}
class DigitalClock implements ClockInterface {
constructor(h: number, m: number) { }
tick() {
console.log("beep beep");
}
}
let digital = createClock(DigitalClock, 12, 17);
通过两个接口,ClockConstructor接口调用ClockInterface,ClockInterface接口调用tick方法,返回值就是一个新的对象,从而完成静态部分内容的调用