node学习实践

npm

npm是node的包管理工具,我们可以使用npm来安装依赖,也可以将自己编写好的程序上传到npm库中供他人使用。

参考链接 https://www.runoob.com/nodejs/nodejs-npm.html

安装模块

我们可以使用npm install来安装npm模块,此时安装路径为当前目录下的node_modules文件夹,如果带上了-g参数,将进行全局安装,安装到 /usr/local 或 node 的安装目录,此时在其它的项目中也可以使用该模块。

创建模块

其他命令

  • 使用npm uninstall来卸载模块
  • 使用npm update更新模块
  • 使用npm search搜索模块

typescript

TypeScript 是 JavaScript 的一个超集,支持 ECMAScript 6 标准。

TypeScript 由微软开发的自由和开源的编程语言。

TypeScript 设计目标是开发大型应用,它可以编译成纯 JavaScript,编译出来的 JavaScript 可以运行在任何浏览器上。

TypeScript 是一种给 JavaScript 添加特性的语言扩展。增加的功能包括:

  • 类型批注和编译时类型检查
  • 类型推断
  • 类型擦除
  • 接口
  • 枚举
  • Mixin
  • 泛型编程
  • 名字空间
  • 元组
  • Await

以下功能是从 ECMA 2015 反向移植而来:

  • 模块
  • lambda 函数的箭头语法
  • 可选参数以及默认参数

TypeScript 可以通过类型注解提供编译时的静态类型检查,另外tsc可以将多个ts文件编译成一个js文件,于是我们可以将代码分散到多个文件中,条理更清晰,之后的学习实践都将使用Typescript来进行。

Typescript环境配置

我们首先要安装Typescript的环境,使用npm install -g typescript即可,成功安装之后我们便可使用tsc来进行Typescript相关的操作。比如编写hello.ts

let say:string = "hello world";
console.log(say);`</pre>

使用`tsc hello.ts`将该文件编译为js文件,同一个目录下出现了名为hello.js的文件,使用`node hello.js`即可执行。

### Typescript学习笔记

关于Typescript的详细教程请查看[Typescript中文文档](https://www.tslang.cn/docs/home.html)以及[菜鸟教程Typescript](https://www.runoob.com/typescript/ts-basic-syntax.html)

#### 面向对象

ts是面向对象的语言

> *   **对象**:对象是类的一个实例,有状态和行为。
  • :类是一个模板,它描述一类对象的行为和状态。
  • 方法:方法是类的操作的实现步骤。
以下操作创建了一个类以及实例化了它的一个对象,并调用了对象的一个方法

<pre>`class Friend { 
   greet():void { 
      console.log("hello!") 
   } 
} 
var friend = new Friend(); 
friend.greet();`</pre>

编译成的js文件内容如下

<pre>`var Friend = /** @class */ (function () {
    function Friend() {
    }
    Friend.prototype.greet = function () {
        console.log("hello!");
    };
    return Friend;
}());
var friend = new Friend();
friend.greet();
`</pre>

#### 基础类型

Typescript声明一个变量的时候,可以规定该变量的类型,格式`let a: number = 1;`
  • any 此类型的变量可以赋予任何类型的值(编译时不会进行类型检查)
`let arrayList: any[] = [1, false, 'fine']; //可以定义一个可以储存任何类型的数组`
  • number: ts中没有整数类型以及浮点数类型,而都是使用number类型

  • string 字符串类型

  • boolean 布尔类型

  • 数组类型
    采用如下格式声明

`// 在元素类型后面加上[]
  let arr: number[] = [1, 2];
  // 或者使用数组泛型
  let arr: Array = [1, 2];`
  • 元组
`let x: [string, number];
  x = ['Runoob', 1];    // 运行正常
  x = [1, 'Runoob'];    // 报错
  console.log(x[0]);    // 输出 Runoob`
  • 枚举
`enum Color {Red, Green, Blue};
  let c: Color = Color.Blue;
  console.log(c);    // 输出 2`
  • void 用于标识方法返回值 function hello(): void{}

  • null 表示对象缺失,null依旧是一个object(不同于undefined)

  • undefined 用于将变量初始化为一个未定义的值

  • never 是其它类型的子类型,代表永远不会出现的值

    可以使用"|"来表示一个变量可以是多种类型,如let x: number | null | undefined;

    类型断言,可以使用或者是value as string的方式来告诉编译器一个变量的类型。

    Null 和 Undefined 是其他任何类型(包括 void)的子类型,可以赋值给其它类型,如数字类型,此时,赋值后的类型会变成 null 或 undefined。而在TypeScript中启用严格的空校验(--strictNullChecks)特性,就可以使得null 和 undefined 只能被赋值给 void 或本身对应的类型


    变量声明



    在较新的标准中加入了let和const两种变量声明方式

  • let
    在ES6之前,我们都是用var来声明变量,而JS只有函数作用域和全局作用域,没有块级作用域,所以{}限定不了var声明变量的访问范围,新加入的let可以将变量的作用域限定在代码块中,并且无法在变量声明前使用该变量(如果使用var,该变量的声明会被提升到使用前),相同代码块中,同名变量不能多次声明,相对于var,let提升了安全性。

  • const
    使用const修饰的变量拥有与let类似的作用域规则,除此之外它在被赋值后无法再次改变。

    解构
  • 数组解构
`let input = [1, 2];
  let [first, second] = input;
  console.log(first); // outputs 1
  console.log(second); // outputs 2`
`// swap variables
  [first, second] = [second, first];`

使用...来获得剩余的变量

`let [first, ...rest] = [1, 2, 3, 4];
  console.log(first); // outputs 1
  console.log(rest); // outputs [ 2, 3, 4 ]`


  • 对象解构


`let o = {
    a: "foo",
    b: 12,
    c: "bar"
  };
  let { a, b } = o;`


若使用...获得剩余变量,解构出来的也是一个对象。

`//属性重命名 此处:不是用来指定类型
  let { a: newName1, b: newName2 } = o;`



函数解构
知道有这么个东西就好...

接口



注意,ts可以以下面这种方式直接创建一个对象(不需要使用类) let myObj = { size: 10, label: "Size 10 Object" };,接口中可以描述属性以及方法。

示例:


`interface LabelledValue {
    label: string;
  }


  function printLabel(labelledObj: LabelledValue) {
    console.log(labelledObj.label);
  }

  let myObj = {size: 10, label: "Size 10 Object"};
  printLabel(myObj);`
  • 可选属性
    接口里的属性可以不是必须的,在变量名后使用 ?: 来指定接口中的可选属性

  • 只读属性
    在变量名之前加上readonly来指定只读属性,只读属性只能在对象创建的时候修改其值,另外可以使用ReadonlyArray创建只读数组,此类数组创建后不可修改值,无法赋值给普通数组(可以使用类型断言 ra as number[] 来转换)

  • 额外属性检查
    如果直接传递对象,对象可以带有接口不存在的字段,但是如果传递的是对象字面量如 let mySquare = createSquare({ colour: "red", width: 100 });会报错,可以使用类型断言绕开检测,或者添加字符串索引签名,在接口中加入[propName: string]: any;,表示该接口还可以有任意数量的属性。

  • 函数接口
    指定参数类型以及返回值类型

`interface SearchFunc {
  (source: string, subString: string): boolean;
  }`
  • 索引类型
    索引可以是数字或者字符串,当使用数字索引的时候实际上ts是将数字转换为字符串之后再索引对象,因此数字索引时返回的对象必须是字符串索引返回对象的子类。
`interface StringArray {
  }`
`interface ReadonlyStringArray {
    readonly [index: number]: string;
  }
  let myArray: ReadonlyStringArray = ["Alice", "Bob"];
  myArray[2] = "Mallory"; // error!`
  • 接口继承
    可使用extends进行继承,一个接口可以继承多个接口
`interface Shape {
    color: string;
  }
  interface PenStroke {
    penWidth: number;
  }
  interface Square extends Shape, PenStroke {
    sideLength: number;
  }
  let square = {};
  square.color = "blue";
  square.sideLength = 10;
  square.penWidth = 5.0;`

#### 类

示例

`class Greeter {
      greeting: string;
      constructor(message: string) {
          this.greeting = message;
      }
      greet() {
          return "Hello, " + this.greeting;
      }
  }


  let greeter = new Greeter("world");`


类可以使用继承,另外注意派生类如果有构造函数constructor,需要调用super(),继承之后方法可以重写,另外也可以使用多态,父类引用子类对象,最终调用方法时使用的还是子类的方法。

##### 访问修饰符

Typescript中的对象成员也有 public(默认,可不写)/private/protect 这几个访问修饰符,和java类似,private修饰的成员不能在类之外访问(子类也不行)而protect修饰的成员可以在子类中访问。

若一个类的构造函数被标记为protect,该类不能被实例化,但是可以被继承,子类可以实例化。

另外,可以使用readonly将成员属性设置为只读的,只读属性只能在声明时或者构造函数中赋值。

还可以使用参数属性,定义与赋值在同一个地方进行

TypeScript使用的是结构性类型系统。 当我们比较两种不同的类型时,并不在乎它们从何处而来,如果所有成员的类型都是兼容的,我们就认为它们的类型是兼容的。

然而,当我们比较带有 privateprotected成员的类型的时候,情况就不同了。 如果其中一个类型里包含一个 private成员,那么只有当另外一个类型中也存在这样一个 private成员, 并且它们都是来自同一处声明时,我们才认为这两个类型是兼容的。 对于 protected成员也使用这个规则。



`class Animal {
>      private name: string;
>      constructor(theName: string) { this.name = theName; }
>  }
>
>
>  class Rhino extends Animal {
>      constructor() { super("Rhino"); }
>  }
>
>  class Employee {
>      private name: string;
>      constructor(theName: string) { this.name = theName; }
>  }
>
>  let animal = new Animal("Goat");
>  let rhino = new Rhino();
>  let employee = new Employee("Bob");
>
>  animal = rhino; //兼容,name来源相同
>  animal = employee; // 错误: Animal 与 Employee 不兼容. 如果是public修饰的即认为兼容`


##### 存取器

注意存取器需要编译目标版本大等于于ES5

ts的getter/setter和java不太一样,比如

<pre>`    private _fullName: string;

    get fullName(): string {
        return this._fullName;
    }
//之后调用get方法是用
object.fullname的方式来执行的。`</pre>

##### 静态属性

Typescript中也可以使用static修饰静态属性

##### 抽象类

可以使用abstract修饰抽象类,是用abstract修饰抽象类中的抽象方法,抽象方法不包含方法体,由派生类实现。抽象类不可实例化,抽象类引用的子类对象不可调用子类特有的方法。

#### 函数

##### 有名字的函数以及匿名函数

<pre>`function add(x: number, y: number): number {
    return x + y;
}

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

##### 可选参数以及默认参数

默认情况下,参数的数量是必须满足函数的要求如果需要设置可选参数我们需要使用 **?:** 来指定一个可选参数,没有传递这个参数的时候,形参值为**undefined**,**可选参数必须跟在必须参数的后面**

`function buildName(firstName: string, lastName?: string)`

除此之外,我们也可以设置参数的默认值,设置了默认值的参数如果用户没有传递有效值时,会使用默认值

`function buildName(firstName: string, lastName = "Smith")`

##### 剩余参数

若我们无法预料会有多少个参数传入的时候可以使用剩余参数

剩余参数会被当做个数不限的可选参数。 可以一个都没有,同样也可以有任意个。 编译器创建参数数组,名字是你在省略号( `...`)后面给定的名字,你可以在函数体内使用这个数组。

<pre>`function buildName(firstName: string, ...restOfName: string[]) {
  return firstName + " " + restOfName.join(" ");
}`</pre>

##### 重载

参数类型不同,数量不同,类型的顺序不同

#### 泛型

_有时间时需要继续深入了解_

<pre>`function identity<T>(arg: T): T {
    return arg;
}`</pre>

类型变量T记录了传入的变量的类型,返回的也是已知类型的变量而非any

使用时有下面两种方式 手动指定或者是用类型推论

<pre>`let output = identity<string>("myString");  // type of output will be 'string'
//某些复杂情况可能无法使用下面这种形式,需要手动指定类型
let output = identity("myString");  // type of output will be 'string'`</pre>

#### 命名空间

如果要在外部调用命名空间中的类/接口,需要加上export关键字

<pre>`namespace SomeNameSpaceName { 
   export interface ISomeInterfaceName {      }  
   export class SomeClassName {      }  
}`</pre>

在外部调用

<pre>`SomeNameSpaceName.SomeClassName;`</pre>

如果命名空间在另一ts文件中,需要使用三斜杠语法

`/// <reference path = "SomeFileName.ts" />`

#### 模块

<pre>`// 模块导出部分
// 文件名 : SomeInterface.ts 
export interface SomeInterface { 
   // 代码部分
}`</pre>
<pre>`// 需要使用模块时进行导入
import someInterfaceRef = require("./SomeInterface");`</pre>
> Typescript中还有许多的高级用法我还没有学习,在将来的学习中还需要返回来深入了解。

### 使用Typescript创建一个nodejs项目

实际使用Typescript时,我们就不像上面那样直接使用tsc命令并带上一系列参数来编译ts文件,这是我们需要一个名为**tsconfig.json**的Typescript编译配置文件。

项目目录结构如下

├─build
├─node_modules
│  ├─.bin
│  ├─@types
│  │  └─node
│  │      └─ts3.2
│  ├─http
│  └─typescript
└─src

在项目根目录下执行 `tsc --init` 将会生成tsconfig.json文件,文件里面的配置项有很多,我们只修改其中的几项。

<pre>`{
  "compilerOptions": {
    "target": "es6",                          /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
    "module": "commonjs",                     /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
     "outDir": "./build",                        /* Redirect output structure to the directory. */
     "rootDir": "./src",                       /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */

    "strict": true,                           /* Enable all strict type-checking options. */
    "noImplicitAny": false,                 /* Raise error on expressions and */
    "esModuleInterop": true                   /* Enables emit interoperability between */
  }
}`</pre>

保存配置文件之后,我们只需要在项目根目录输入tsc,就可以将src文件夹中的ts文件编译,产生的js文件会储存在build文件夹中。

#### 使用.d.ts文件

我们在项目中常常需要用到内建模块或者第三方模块,但是在.ts文件中是没办法直接导入模块的。

我们可以在项目目录下执行`npm install --save-dev @types/node`获得node的类型说明文件,但是这个方法只能解决内建模块的说明文件问题,对于第三方模块



http://microsoft.github.io/TypeSearch/

我们可以修改package.json


<

pre>` "scripts": {
"build": "tsc"
}

之后我们只需要使用npm run build即可执行tsc命令(当然在将来的应用中我们往往不是只执行这一条简单的命令),比如可以加入"dev": "supervisor -w build ./build/server.js"之后执行npm run dev来持续监控脚本,在js文件变化后重启服务器。

node

文章目录