TypeScript语言基础

变量

变量名

在js中,每个变量都有唯一的名字,也叫做标识符,定义规则如下:

  • 允许字母、数字、下划线、美元符号$
  • 允许Unicode转义序列
  • 仅允许字母、unicode转义序列、下划线、美元符号$作为第一个字符,不允许数字作为第一个字符
  • 变量名区分大小写
  • 不允许使用保留字作为标识符

变量声明

在js中,有三种声明变量的方式:

  • var
  • let
  • const

var声明是在ECMAScript2015前就支持的,而let和const则是ECMAScript2015中支持的,使用let和const能够声明具有块级作用域的变量,推荐使用let和const

var声明 若变量未初始化,则其默认值为undefined

let声明 若变量未初始化,则其默认值为undefined

const声明定义常量,必须设置初始值,并且不允许重新赋值

注释

ts支持三种类型注释

  • 单行注释
  • 多行注释
  • 区域注释

单行注释和多行注释

单行注释使用双斜线"//",不允许换行

多行注释以“/”符号作为开始并以“/”符号作为结束。允许换行

// 单行注释

/**
 * 多行注释
 */

区域注释

区域注释不是一种新的注释语法, 它借助单行注释的语法实现了定义代码折叠区域的功能。

// #region
 let x = 0;
//#endregion

数据类型

TypeScript是一种静态类型的编程语言,它为JavaScript添加了类型系统。在TypeScript中,可以使用多种数据类型来声明变量、函数参数、函数返回值等。

Undefined

Undefined类型只包含一个值,即undefined。在变量未被初始化时,它的值为unde-fined。

Null

Nul类型也只包含一个值,即nul。我们通常使用nul值来表示未初始化的对象。此外,nul值也常被用在JSON文件中,表示一个值不存在。

Boolean

Boolean类型包含两个逻辑值,分别是true和false。

String

String类型表示文本字符串,它由0个或多个字符构成。

JavaScript使用UTF-16编码来表示一个字符。UTF-16是一种Unicode字符编码方案,它使用16位(2字节)来表示大部分常用字符,而使用32位(4字节)来表示辅助平面字符(如表情符号、某些特殊字符等)。 image-20230710143147792

在JavaScript中,字符串是由16位的代码单元(code units)序列组成的。对于大部分常用字符,一个代码单元就可以表示一个字符,但对于一些辅助平面字符,需要使用两个代码单元来表示一个字符,这种表示方式称为代理对(surrogate pair)。

例如,字符"A"的UTF-16编码是U+0041,它可以用一个16位的代码单元表示,而字符"😊"的UTF-16编码是U+1F60A,它需要使用两个16位的代码单元(0xD83D和0xDE0A)来表示。

image-20230710143245664

Number

Number类型表示一个数字。JavaScript不详细区分整数类型、浮点数类型以及带符号的数字类型等。JavaScript使用双精度64位浮点数格式(IEEE 754)来表示数字,因此所有数字本质上都是浮点数。在该格式中,符号部分占用1位(bit),指数部分占用11位,小数部分占用52位,一共占用64位。

image-20230710143656658

Symbol

Symbol是ECMAScript 2015新引入的原始类型。Symbol值有一个重要特征,那就是每一个 Symbol值都是唯一的且不可改变的。Symbol值的主要应用场景是作为对象的属性名。

Symbol的设计初衷是用来实现对象的私有属性,但实际上Symbol并不能实现真正意义上的私有属性。JavaScript还是提供了一些方法允许程序去访问Symbol属性。虽然Symbol无法实现绝对的私有属性,但是它确实有助于缓解属性命名冲突问题。

Symbal()

Symbol是一种基本数据类型,用于创建独一无二的标识符。Symbol的值是唯一的,不可变的,并且在不同的上下文中是不相等的。

Symbol的创建方式是使用全局的Symbol函数,它可以接受一个可选的描述符作为参数。描述符主要用于调试和标识Symbol,对于Symbol的唯一性没有影响。

JavaScript提供了一个全局的“Symbol()”函数来创建Symbol类型的值。我们可以将“Symbol()” 函数想象成GUID(全局唯一标识符)的生成器,每次调用“Symbol()”函数都会生成一个完全不同的 Symbol值。示例如下:

Well-Known Symbols

Symbol还有一些内置的预定义Symbol,称为Well-Known Symbols(众所周知的符号),用于自定义对象的行为,比如Symbol.iteratorSymbol.toStringTag等。

Well-Known Symbols(众所周知的符号)是在ECMAScript 6中引入的一组预定义符号常量。这些符号常量在JavaScript中具有特殊的意义,并可用于自定义对象的行为。

以下是一些常见的Well-Known Symbols:

  • Symbol.iterator:表示一个对象的默认迭代器方法,用于定义对象的迭代行为。
  • Symbol.toStringTag:表示对象的默认字符串标签,用于自定义对象的toString()方法返回的字符串。
  • Symbol.hasInstance:用于自定义对象的instanceof运算符的行为。
  • Symbol.isConcatSpreadable:用于指示对象是否可展开为数组的成员。
  • Symbol.toPrimitive:用于自定义对象的类型转换行为。
  • Symbol.species:用于自定义构造函数创建派生对象的行为。
  • Symbol.matchSymbol.replaceSymbol.searchSymbol.split:用于自定义正则表达式相关操作的行为。

这些Well-Known Symbols在JavaScript内部使用,可以通过Symbol对象进行访问。例如:

const obj = {
  [Symbol.iterator]: function* () {
    yield 1;
    yield 2;
    yield 3;
  },
};

for (const item of obj) {
  console.log(item);
}

上述示例中,通过定义[Symbol.iterator]方法,我们自定义了obj对象的迭代行为,使其可以被for-of循环迭代。

Object

对象是属性的集合,每个对象属性都属于以下两种形式之一:

  • 数据属性。可以为Undefined、Null、Boolean、String、Number、Symbol和Object类型的值。

  • 存取器属性。由一个或两个存取器方法构成,用于获取和设置Undefined、Null、Boolean、 String、Number、Symbol和Object类型的值。

对象属性使用键值来标识,键值只能为字符串或Symbol值。所有字符串(也包括空字符串)和 Symbol值都是合法的键值。

字面量

在计算机科学中,字面量用于在源代码中表示某个固定值。在JavaScript程序中,字面量不是变量,它是直接给出的固定值。

null 字面量

Null字面量只有一个,记作null。

Boolean字面量

Boolean字面量有两个,分别记作true和false。

Number字面量

Number字面量包含四类:二进制整数字面量、八进制整数字面量、十进制数字面量以及十六进制整数字面量。

  • 二进制整数字面量以0b或0B开头(第一个字符为数字0),并只包含数字0或1。

  • 八进制整数字面量以0o或0O开头(第一个字符为数字0,第二个字符为字母o),并且只包含数字0至7。

  • 十进制数字面量由一串数字组成,支持整数、小数和科学记数法。

  • 十六进制整数字面量以0x或0X开头(第一个字符为数字0),可以包含数字0至9、小写字母a至f以及大写字母A至F。

字符串字面量

字符串字面量是使用一对单引号或双引号包围起来的Unicode字符。字符串字面量中可以包含 Unicode转义序列和十六进制转义序列。JavaScript中的主流编码风格推荐使用单引号表示字符串字面量。

模板字面量

模板字面量是ECMAScript 2015引入的新特性,模板字符串的基本语法是使用反引号"`"(键盘上数字键1左侧的按键)替换了字符串字面量中的单、 双引号。

模板字面量(Template Literals)是一种方便的字符串表示方式,允许在字符串中插入变量和表达式。它们使用反引号()来定义,内部可以包含占位符${expression}`,其中expression是需要插入的变量或表达式。

下面是使用模板字面量的示例:

let name = 'John';
let age = 30;

// 基本用法
let message = `My name is ${name} and I am ${age} years old.`;
console.log(message); // 输出: My name is John and I am 30 years old.

// 表达式插入
let sum = `The sum of 5 and 10 is ${5 + 10}.`;
console.log(sum); // 输出: The sum of 5 and 10 is 15.

// 多行字符串
let multiline = `
  This is a
  multiline
  string.
`;
console.log(multiline);
// 输出:
//   This is a
//   multiline
//   string.

使用模板字面量可以更方便地构建包含变量和表达式的字符串,避免了使用字符串连接符(+)的繁琐和容易出错的问题。同时,它也支持多行字符串的创建,使代码更易读和维护。

对象

在JavaScript中,对象属于非原始类型。同时,对象也是一种复合数据类型,它由若干个对象属性构成。对象属性可以是任意数据类型,如数字、函数或者对象等。当对象属性为函数时,我们通常称之为方法。当然,这只是惯用叫法不同,在本质上并无差别。

对象字面量

对象字面量也叫作对象初始化器,是最常用的创建对象的方法。

数据属性

对象字面量的数据属性由属性名和属性值组成, 语法如下所示:

{
  PropertyName: PropertyValue,
}

存取器属性

存取器属性(Accessor Property)是一种特殊的属性声明,它使用getset关键字来定义属性的读取和设置方法。存取器属性允许对属性的访问进行自定义逻辑的处理。

使用存取器属性可以使属性的读取和设置更加灵活,并可以在访问属性时执行额外的代码。

下面是使用存取器属性的示例:

class Circle {
  private _radius: number;

  get radius(): number {
    return this._radius;
  }

  set radius(value: number) {
    if (value <= 0) {
      throw new Error('Radius must be a positive number.');
    }
    this._radius = value;
  }

  get area(): number {
    return Math.PI * this._radius * this._radius;
  }
}

let circle = new Circle();
circle.radius = 5; // 调用 set radius() 方法
console.log(circle.radius); // 调用 get radius() 方法,输出: 5
console.log(circle.area); // 调用 get area() 方法,输出: 78.53981633974483

circle.radius = -1; // 调用 set radius() 方法,抛出错误

在上述示例中,Circle类定义了一个私有属性_radius和三个存取器属性:radiusarea。存取器属性的get方法用于获取属性值,set方法用于设置属性值。

通过存取器属性,我们可以对属性的读取和设置进行自定义逻辑的处理。在示例中,我们在设置半径时检查了半径值是否为正数,并在不满足条件时抛出了一个错误。另外,我们还定义了一个只读属性area,用于计算圆的面积。

存取器属性提供了一种更加灵活和可控的方式来定义属性,并允许在访问属性时执行额外的逻辑。

可计算属性名

可计算属性名(Computed Property Names)是一种允许在对象字面量中使用表达式来定义属性名的语法特性。通过可计算属性名,我们可以动态地创建对象的属性。

可计算属性名的语法是使用方括号[]将属性名表达式括起来,放在对象字面量中作为属性的名称。在计算属性名中,可以使用任何表达式,包括变量、函数调用、字符串拼接等。

下面是使用可计算属性名的示例:

let propertyName = 'name';
let person = {
  [propertyName]: 'Alice',
  age: 25,
  ['address']: '123 Main St',
  [getDynamicProperty()]: 'Dynamic Value',
};

console.log(person.name); // 输出: 'Alice'
console.log(person.age); // 输出: 25
console.log(person.address); // 输出: '123 Main St'
console.log(person[propertyName]); // 输出: 'Alice'
console.log(person[getDynamicProperty()]); // 输出: 'Dynamic Value'

function getDynamicProperty() {
  return 'dynamicProperty';
}

在上述示例中,我们使用可计算属性名来定义了一个包含动态属性的person对象。[propertyName]将动态地创建一个名为name的属性,['address']直接指定了一个名为address的属性。我们还可以通过person[propertyName]person[getDynamicProperty()]来访问可计算属性名所表示的属性。

可计算属性名为我们提供了一种在对象字面量中动态定义属性的方式,使我们能够更加灵活地创建对象。它可以与其他语法特性(如模板字面量、函数调用等)结合使用,使对象的属性更具动态性和可扩展性。

原型对象

对象与原型之间的引用关系

每个对象都有一个原型。对象的原型既可以是一个对象,即原型对象,也可以是nulI值。原型对象又有其自身的原型,因此对象的原型会形成一条原型链,原型链将终止于nul值。原型对象本质上是一个普通对象,用于在不同对象之间共享属性和方法。对象与其原型之间具有隐含的引用关系,

image-20230710150150536

animal、dog和cat均表示对象。dog和cat对象的原型对象均为animal对象;而 animal对象的原型为null值。dog和cat与其原型animal之间具有隐含的引用关系,该关系是通过对象的一个内部属性来维持的,即图中的“[[Prototype]]”属性。所谓隐含的引用关系是指,对象的原型不是对象的公共属性,因此无法通过对象属性访问来直接获取对象的原型。该图也展示了对象的原型会形成一条原型链,例如从dog到animal并终止于null值的虚线就表示了一条原型链。

原型的作用

原型能够用来在不同对象之间共享属性和方法,JavaScript中的继承机制也是通过原型来实现的。原型的作用主要体现在查询对象某个属性或方法时会沿着原型链依次向后搜索,如图所示。

image-20230710150402707

在访问dog对象上的color属性时,先尝试在dog对象上查找该属性。若找到color属性,则返回属性值;若没有找到color属性,则沿着原型链在原型对象animal中继续查找color属性。此例中, dog对象没有color属性而animal对象包含了color属性,因此最终会返回animal对象上的color属性值。如果直到原型链的尽头(null值)也没有找到相应属性,那么会返回undefined值而不是产生错误。

属性的遮蔽

如果对象本身和其原型对象上同时存在要访问的属性,那么就会产生遮蔽效果。在这种情况下,当前对象自身定义的属性拥有最高的优先级

image-20230710150659506

此例中,在dog对象和animal对象上都定义了color属性。在dog对象上读取color属性时, dog对象自身定义的color会被优先选用,而原型对象animal上的color属性会被忽略。

需要注意的是,原型对象在属性查询和属性设置时起到的作用是不对等的。在查询对象属性时会考虑对象的原型,但是在设置对象属性时不会考虑对象的原型,而是直接修改对象本身的属性值。

数组

数组表示一组有序元素的集合,它使用数字作为元素索引值。JavaScript中的数组不是独立的数据类型,它属于对象数据类型。

数组字面量

数组字面量是常用的创建数组的方法。数组字面量使用一对中括号“[]”将数组元素包含在内,数组元素之间使用逗号分隔。示例如下:

const colors = ['red','green','blue'];

数组中的元素

数组中的元素可以为任意类型的值,如数字、函数或对象等。数组元素的类型也不必全部相同。数组元素可通过数字索引进行访问,索引值从零开始。当数组访问越界或使用了未知的索引时不会产生错误,而是会返回undefined值。

数组的长度表示数组中容纳的元素数量。在经典的数组数据结构里,数组是内存中一段长度固定的连续存储空间。在一些编程语言中声明数组时需要指定数组长度,并且之后无法动态改变。

JavaScript中的数组没有这个限制,因为它本质上是对象,数组元素相当于对象的一个属性。通过数组的length属性就能够获取数组的长度。JavaScript数组的长度并不固定,当我们在数组中插入或删除元素时,数组长度会随之改变。

函数

函数是程序必不可少的组成部分之一,它允许我们创建可重用的代码单元。我们通常会将具有独立功能的代码提取成函数以提高代码的可重用性与可测试性。

JavaScript中的函数是“头等函数”,它具有以下特性:

  • 函数可以赋值给变量或对象属性。

  • 函数可以作为参数传递给另一个函数。

  • 函数可以作为函数返回值。

因为支持头等函数,所以JavaScript也在一定程度上支持函数式编程范式。接下来将概括介绍函数的使用方法。

函数声明

函数声明是最简单直接的函数定义方式。它的语法如下所示:

function name(param0,param1,...){
  // body
}

函数声明由以下几部分组成:

  • 必须以function关键字开始。

  • 必须指定一个函数名,函数名应该是合法的标识符。

  • 由一对小括号包围的可选形式参数列表,参数可以有零或多个。

  • 由一对大括号包围的函数体。

函数表达式

立即执行的函数表达式

立即执行的函数表达式(Immediately Invoked Function Expression,IIFE)是一种将函数定义和立即执行结合在一起的技术。它允许我们在定义之后立即执行函数,并且函数内部的变量和函数在外部是不可访问的。

下面是一个使用立即执行的函数表达式的示例:

(function() {
  // 函数内部的逻辑
  const message = 'Hello, TypeScript!';
  console.log(message);
})();

在上述示例中,我们使用(function() { ... })()的形式来创建一个匿名函数,并立即执行它。该函数内部包含了一些逻辑,我们可以在其中定义变量、执行语句和调用其他函数等。执行结果将立即打印出来。

使用立即执行的函数表达式可以创建一个封闭的作用域,以避免变量污染全局命名空间。这对于处理一些临时变量或执行一些初始化操作非常有用。

箭头函数

箭头函数是ECMAScript 2015中新增的特性,用来定义匿名的函数表达式。箭头函数一定是匿名函数。箭头函数最显著的特点是使用“胖箭头”符号连接函数的形式参数列表和函数体。箭头函数的基本语法如下所示:

(param0,param1)=>{
  // body
}