浅谈 TypeScript 特性
*readonly
用来约束一个对象的属性, 在对象刚创建时能赋值, 往后赋值均会报错, 在类中使用 readonly 只能在构造函数中赋初始值。
interface
Point {
readonly
x:
number;
readonly
y:
number;
}
const
p:
Point = {
x:
10,
y:
20 };
p.
x =
5;
// error [ts] Cannot assign to 'x' because it is a constant or a read-only property.!
*ReadonlyArray<T>
创建一个不可变的数组, 谈 ReadonlyArray<T> 的目的只是说明一下 typescript 会内置了一些实用的接口供开发者使用。
let
a:
number[] = [
1,
2,
3,
4];
let
ro:
ReadonlyArray<
number> =
a;
ro.
push(
2);
// error [ts] Property 'push' does not exist on type 'ReadonlyArray<number>'.
a.
push(
2);
a =
ro;
// error [ts] Type 'ReadonlyArray<number>' is not assignable to type 'number[]'
a =
ro
as
string[];
// error [ts] Type 'ReadonlyArray<number>' cannot be converted to type 'string
a =
ro
as
any[];
// ok 类型断言重写 但是如下依旧会报错
a =
ro
as
number[];
// ok 类型断言重写 但是如下依旧会报错
ro.
push(
2);
// error [ts] Property 'push' does not exist on type 'ReadonlyArray<number
a.
push(
2);
*类型断言
通过上面的 as 和 下面的几个 as 类型断言, 大概可判断类型断言 x as y, x 类型约束比 y 要更细致, 便能成功(纯属个人理解, 官方具体定义很飘渺不是很懂)
// ok
interface
SquareConfig {
color?:
string;
width?:
number;
}
function
createSquare(
config:
SquareConfig) {
// ...
}
let
mySquare =
createSquare({
width:
100,
opacity:
0.5 }
as
SquareConfig);
// error [ts] Type '{ width: number; opacity: number; }' cannot be converted to type 'SquareConfig'.
interface
SquareConfig {
color:
string;
width:
number;
}
function
createSquare(
config:
SquareConfig) {
// ...
}
let
mySquare =
createSquare({
width:
100,
opacity:
0.5 }
as
SquareConfig);
// ok
interface
SquareConfig {
color:
string;
width:
number;
}
function
createSquare(
config:
SquareConfig) {
// ...
}
let
mySquare =
createSquare({
width:
100,
opacity:
0.5,
color:
"my name is string" }
as
SquareConfig);
// 更好的方法 这样定义接口 SquareConfig 添加其他参数不会报错了
interface
SquareConfig {
color?:
string;
width?:
number;
[
propName:
string]:
any;
}
*类类型接口
当你操作类和接口的时候,你要知道类是具有两个类型的:静态部分的类型和实例的类型。
如下例子, 类一般实现的约束实例部分的接口, 当需要使用 new 关键字实例化的时候才会对其静态部分进行检查
interface
ClockConstructor {
new (
hour:
number,
minute:
number):
ClockInterface; //
ok
// new
(
hour
:
number
,
minute
:
number
)ok
}
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);
*构造函数工厂制造类
js版本构造函数工厂制造类
function
counter(){
}
counter.
interval =
123;
counter.
reset =
function () { };
在 ts 中需要如下约束
interface
Counter {
(
start:
number):
string;
interval:
number;
reset():
void;
}
function
getCounter():
Counter {
let
counter = <
Counter>
function (
start:
number) { };
counter.
interval =
123;
counter.
reset =
function () { };
return
counter;
}
let
c =
getCounter();
c(
10);
c.
reset();
c.
interval =
5.0
*private 与 protected
如下的例子可以看出, private修饰 不能在实例中和子类中访问; protected能在子类中访问。
class
Person {
private
name:
string;
constructor(
name:
string) {
this.
name =
name; }
}
class
Employee
extends
Person {
private
department:
string; //
private
constructor(
name:
string,
department:
string) {
super(
name)
this.
department =
department;
}
public
getElevatorPitch() {
return
`Hello, my name is
${this
.
name
}
and I work in
${this
.
department
}
.`;
// [ts] Property 'name' is private and only accessible within class 'Person'.
}
}
let
howard =
new
Employee(
"Howard",
"Sales");
console.
log(
howard.
getElevatorPitch());
console.
log(
howard.
name);
// error
class
Person {
protected
name:
string; //
protected
constructor(
name:
string) {
this.
name =
name; }
}
class
Employee
extends
Person {
private
department:
string;
constructor(
name:
string,
department:
string) {
super(
name)
this.
department =
department;
}
public
getElevatorPitch() {
return
`Hello, my name is
${this
.
name
}
and I work in
${this
.
department
}
.`;
// ok
}
}
let
howard =
new
Employee(
"Howard",
"Sales");
console.
log(
howard.
getElevatorPitch());
console.
log(
howard.
name);
// error
* 不一样的set get
需要提醒的别和 java 中的 getFullName, setFullName 语法弄混!在 ts 中 中间上有一个空格的存在。
class
Employee {
private
_fullName:
string;
get
fullName():
string {
return
this.
_fullName;
}
set
fullName(
newName:
string) {
// 如果满足一定条件才能修改值成功
}
}
let
employee =
new
Employee();
employee.
fullName =
"Bob Smith";
*类的实例部分与静态部分
在类类型接口中其实我们就谈过这部分内容, 声明一个类其实也就声明类的静态和实例部分。
知识点:
typeof Greeter 取的是 Greeter类静态部分的类型,
那么如下例子中 greeterMaker类 只能访问其静态成员。
static
standardGreeting =
"Hello, there";
constructor() {
}
greeting:
string;
greet() {
if (
this.
greeting) {
return
"Hello, " +
this.
greeting;
}
else {
return
Greeter.
standardGreeting;
}
}
}
interface
StaticClass {
// 在 类类型声明中 就谈过的在 类实例化时 对类静态成员就行检查
new ();
standardGreeting:
string;
}
function
createGreeter (
ctr:
StaticClass):
Greeter {
return
new
ctr();
}
let
greeter1:
Greeter;
greeter1 =
createGreeter(
Greeter);
console.
log(
greeter1.
greet());
let
greeterMaker:
typeof
Greeter =
Greeter;
// 从这可以知道 typeof 取的是类的静态部分的类型 typeof Greeter 相当于语句 type a = typeof Greeter; let greeterMaker: a = Greeter;
greeterMaker.
standardGreeting =
"Hey there!";
let
greeter2:
Greeter =
new
greeterMaker();
console.
log(
greeter2.
greet());
// 既然 typeof 取的是类的静态部分的类型 那么 interface StaticClass 也是对静态部分进行约束检查的, 果不其然下面的语句是成立相等的 !
let
clasA:
StaticClass;
let
clasB:
typeof
Greeter;
clasB =
clasA;
clasA =
clasB;
* this
学习使用JavaScript里this
就好比一场成年礼, 下面简要说一下typescript里面的this。
可以看出在嵌套return function 里面 this 指向不再是deck 对象, 而是windows, 故报错!
var
deck = {
suits: [
"hearts",
"spades",
"clubs",
"diamonds"],
cards:
Array(
52),
createCardPicker:
function () {
return
function () {
var
pickedCard =
Math.
floor(
Math.
random() *
52);
var
pickedSuit =
Math.
floor(
pickedCard /
13);
return {
suit:
this.
suits[
pickedSuit],
card:
pickedCard %
13 };
};
}
};
var
cardPicker =
deck.
createCardPicker();
var
pickedCard =
cardPicker();
alert(
"card: " +
pickedCard.
card +
" of " +
pickedCard.
suit);
// Uncaught TypeError: Cannot read property '0' of undefined
在上面的情况下, 我们便需要利用typescript的特性, 以定义接口的形式, 对变量, 参数...加以约束, this 也需要显示声明其类型, 才不会在类型问题上出错 。
interface
Card {
suit:
string;
card:
number;
}
interface
Deck {
suits:
string[];
cards:
number[];
createCardPicker(
this:
Deck): ()
=>
Card;
}
let
deck:
Deck = {
suits: [
"hearts",
"spades",
"clubs",
"diamonds"],
cards:
Array(
52),
// NOTE: The function now explicitly specifies that its callee must be of type Deck
createCardPicker:
function(
this:
Deck) {
return ()
=> {
let
pickedCard =
Math.
floor(
Math.
random() *
52);
let
pickedSuit =
Math.
floor(
pickedCard /
13);
return {
suit:
this.
suits[
pickedSuit],
card:
pickedCard %
13};
}
}
}
let
cardPicker =
deck.
createCardPicker();
let
pickedCard =
cardPicker();
alert(
"card: " +
pickedCard.
card +
" of " +
pickedCard.
suit);
* 箭头函数和 this
如下例子会报错, 因为传入的 h.onClickBad 仅仅被当作一个函数, 此时里面的 this 已经丢失了
class
Handler {
info:
string;
onClickBad (
e:
string) {
this.
info =
e;
}
}
let
h =
new
Handler();
function
ss(
cb) {
cb(
"sss")
}
ss(
h.
onClickBad);
console.
log(
h.
info)
当然如果这样定义 ss 函数是没问题的, 此时 this 不会丢失, 但是这样更改 ss 函数是不是相当于改了需求?这是不合理的方式没有从源头解决问题。
function
ss(){
h.
onClickBad(
"sss")
}
此时来看看箭头优势, 改写一下 Handler onClickBad为箭头函数方式。
class
Handler {
info:
string;
onClickBad = (
e:
string) =>{
this.
info =
e;
}
}
为什么改成这样不会丢失 this , 我们可以看看箭头函数干了什么事, 当编译成 js 文件时
var
Handler = (
function () {
function
Handler() {
var
_this =
this;
this.
onClickBad =
function (
e) {
_this.
info =
e;
};
}
return
Handler;
}());
var
h =
new
Handler();
function
ss(
cb) {
cb(
"sss");
}
ss(
h.
onClickBad);
console.
log(
h.
info);
是的, 正如我们平时书写 js 文件时, 为了防止 this 丢失我们通常也是用变量存储, 箭头函数其实就在帮我们干这事。
* 重载
typescript 中的重载无力吐槽, 没有使用的冲动。if else 嵌套去选择性调用, 极度不友好, java等强类型重载机制就清晰明了多了。
let
suits = [
"hearts",
"spades",
"clubs",
"diamonds"];
function
pickCard(
x: {
suit:
string;
card:
number; }[]):
number;
function
pickCard(
x:
number): {
suit:
string;
card:
number; };
function
pickCard(
x):
any {
// Check to see if we're working with an object/array
// if so, they gave us the deck and we'll pick the card
if (
typeof
x ==
"object") {
let
pickedCard =
Math.
floor(
Math.
random() *
x.
length);
return
pickedCard;
}
// Otherwise just let them pick the card
else
if (
typeof
x ==
"number") {
let
pickedSuit =
Math.
floor(
x /
13);
return {
suit:
suits[
pickedSuit],
card:
x %
13 };
}
}
* 泛型 与 类 类型
下面的例子也证明了之前说的几个知识点
1. 声明一个类, 实际上也声明了其 实例部分和静态部分, 所以类也能作为接口使用(因为有实例部分属性的约束检查和接口的属性检查原理是一致的)
2. 对一个等于函数的变量约束有两种方式
a.
const
a: (
name:
string)
=>
string = (
name:
string)
=> {
return
name
}
b.
const
a: {(
name:
string):
string} = (
name:
string)
=> {
return
name
}
3. c: new (name: string)=> A , 对参数c的约束在类 类型中也谈过, 实际上是对静态部分进行约束检查, new (name: string)=> A 也可以写出 { (name: string): A } 。
class
Animal {
numLegs:
number;
}
class
Lion
extends
Animal {
constructor(
public
name:
string){
super()
}
keeper:
"hello word!";
}
function
createInstance<
A
extends
Animal>(
c:
new (
name:
string)
=>
A):
A {
return
new
c(
name);
}
createInstance<
Lion>(
Lion).
keeper;
* 枚举
使用枚举我们可以定义一些有名字的数字常量。
1. 如下, 为普通枚举
enum
FileAccess {
// constant members
None,
Read =
1 <<
1,
Write =
1 <<
2,
ReadWrite =
Read |
Write,
// computed member
G =
"123".
length
}
将会编译成一个很有意思的对象, 具有反向映射的特点, 看看代码就知道这个对象的特点了
var
FileAccess;
(
function (
FileAccess) {
// constant members
FileAccess[
FileAccess[
"None"] =
0] =
"None";
FileAccess[
FileAccess[
"Read"] =
2] =
"Read";
FileAccess[
FileAccess[
"Write"] =
4] =
"Write";
FileAccess[
FileAccess[
"ReadWrite"] =
6] =
"ReadWrite";
// computed member
FileAccess[
FileAccess[
"G"] =
"123".
length] =
"G";
})(
FileAccess || (
FileAccess = {}));
相当于 声明一个如下的对象
var
FileAccess ={
"None"
:
0,
0
:
"None"
}
2. 如下为常数枚举
如下例子用const 修饰, 不能有需要计算的成员, 如 G = "123".leng 等是需要计算出结果的
让我们看看编译的结果
const
enum
FileAccess2 {
None,
Read =
1 <<
1,
Write =
1 <<
2,
ReadWrite =
Read |
Write,
// G = "123".length [ts] In 'const' enum declarations member initializer must be constant expression.
}
const
a =
FileAccess2.
ReadWrite
让我们看看编译的结果,是的 常数枚举在编译时会被删除
var
a =
6
/* ReadWrite */;
3. 外部枚举
使用 declare 修饰, declare 一般用在声明文件中 如放在global.d.ts中进行全局声明
declare
enum
FileAccess3 {
None,
Read =
1 <<
1,
Write =
1 <<
2,
ReadWrite =
Read |
Write,
// G = "123".length [ts] In 'const' enum declarations member initializer must be constant expression.
}
const
a2 =
FileAccess3.
ReadWrite
编译成
var
a2 =
FileAccess3.
ReadWrite;