Open mqyqingfeng opened 2 years ago
return s.toLowercse() === "ok"; 这里拼写错误“toLowerCase”
@lfq1514
return s.toLowercse() === "ok"; 这里拼写错误“toLowerCase”
hh,不是拼写错误,认真看看上下文!!
class Point { x: number; y: number; } //属性“x”没有初始化表达式,且未在构造函数中明确赋值。ts(2564)
强迫症,在 implements 部分的例子中,NameChecker 缺了个 }
。
在箭头函数那里的第三条,不能在派生中使用super.getName,因为原型链中并没有入口可以获取该基类方法,刚开始不能理解这句话。 后来写了下代码,如下
class MyClass1 {
name = "MyClass1";
getName = () => {
return this.name;
};
constructor() {
this.name = "MyClass1";
// MyClass1.prototype.getName = this.getName;
}
}
class Derived1 extends MyClass1 {
testGetName = () => {
// TypeError: (intermediate value).getName is not a function.
// 此时的getName其实是实例的函数,在原型链上并不存在
// 但是可以通过给原型添加该属性,来使得super.getName()可用
return super.getName();
};
}
const d = new Derived1();
console.log(d.testGetName());
发现确实不行,想了下才意识到,在class中的方法是会被添加到MyClass1.prototype上的,而属性则会作为实例的属性,在构造时进行添加,也就是添加到实例上去的,原型上并不存在这个属性,可不就是没有入口可以获取该基类方法么。
因此,如果想绕过这个限制,就可以在构造函数中给原型添加这个属性来实现super调用。也就是添加上面注释掉的 MyClass1.prototype.getName = this.getName, 这段代码
不知道理解有没有错误,欢迎大家指教。
类(Classes)
TypeScript 完全支持 ES2015 引入的
class
关键字。和其他 JavaScript 语言特性一样,TypeScript 提供了类型注解和其他语法,允许你表达类与其他类型之间的关系。
类成员(Class Members)
这是一个最基本的类,一个空类:
这个类并没有什么用,所以让我们添加一些成员。
字段(Fields)
一个字段声明会创建一个公共(public)可写入(writeable)的属性:
注意:类型注解是可选的,如果没有指定,会隐式的设置为
any
。字段可以设置初始值(initializers):
就像
const
、let
和var
,一个类属性的初始值会被用于推断它的类型:--strictPropertyInitialization
strictPropertyInitialization 选项控制了类字段是否需要在构造函数里初始化:
注意,字段需要在构造函数自身进行初始化。TypeScript 并不会分析构造函数里你调用的方法,进而判断初始化的值,因为一个派生类也许会覆盖这些方法并且初始化成员失败:
如果你执意要通过其他方式初始化一个字段,而不是在构造函数里(举个例子,引入外部库为你补充类的部分内容),你可以使用明确赋值断言操作符(definite assignment assertion operator)
!
:readonly
字段可以添加一个
readonly
前缀修饰符,这会阻止在构造函数之外的赋值。构造函数(Constructors)
类的构造函数跟函数非常类似,你可以使用带类型注解的参数、默认值、重载等。
但类构造函数签名与函数签名之间也有一些区别:
Super 调用(Super Calls)
就像在 JavaScript 中,如果你有一个基类,你需要在使用任何
this.
成员之前,先在构造函数里调用super()
。忘记调用
super
是 JavaScript 中一个简单的错误,但是 TypeScript 会在需要的时候提醒你。方法(Methods)
类中的函数属性被称为方法。方法跟函数、构造函数一样,使用相同的类型注解。
除了标准的类型注解,TypeScript 并没有给方法添加任何新的东西。
注意在一个方法体内,它依然可以通过
this.
访问字段和其他的方法。方法体内一个未限定的名称(unqualified name,没有明确限定作用域的名称)总是指向闭包作用域里的内容。Getters / Setter
类也可以有存取器(accessors):
TypeScript 对存取器有一些特殊的推断规则:
get
存在而set
不存在,属性会被自动设置为readonly
从 TypeScript 4.3 起,存取器在读取和设置的时候可以使用不同的类型。
索引签名(Index Signatures)
类可以声明索引签名,它和对象类型的索引签名是一样的:
因为索引签名类型也需要捕获方法的类型,这使得并不容易有效的使用这些类型。通常的来说,在其他地方存储索引数据而不是在类实例本身,会更好一些。
类继承(Class Heritage)
JavaScript 的类可以继承基类。
implements
语句(implements
Clauses)你可以使用
implements
语句检查一个类是否满足一个特定的interface
。如果一个类没有正确的实现(implement)它,TypeScript 会报错:类也可以实现多个接口,比如
class C implements A, B {
注意事项(Cautions)
implements
语句仅仅检查类是否按照接口类型实现,但它并不会改变类的类型或者方法的类型。一个常见的错误就是以为implements
语句会改变类的类型——然而实际上它并不会:在这个例子中,我们可能会以为
s
的类型会被check
的name: string
参数影响。实际上并没有,implements
语句并不会影响类的内部是如何检查或者类型推断的。类似的,实现一个有可选属性的接口,并不会创建这个属性:
extends
语句(extends
Clauses)类可以
extend
一个基类。一个派生类有基类所有的属性和方法,还可以定义额外的成员。覆写属性(Overriding Methods)
一个派生类可以覆写一个基类的字段或属性。你可以使用
super
语法访问基类的方法。TypeScript 强制要求派生类总是它的基类的子类型。
举个例子,这是一个合法的覆写方法的方式:
派生类需要遵循着它的基类的实现。
而且通过一个基类引用指向一个派生类实例,这是非常常见并合法的:
但是如果
Derived
不遵循Base
的约定实现呢?即便我们忽视错误编译代码,这个例子也会运行错误:
初始化顺序(Initialization Order)
有些情况下,JavaScript 类初始化的顺序会让你感到很奇怪,让我们看这个例子:
到底发生了什么呢?
类初始化的顺序,就像在 JavaScript 中定义的那样:
这意味着基类构造函数只能看到它自己的
name
的值,因为此时派生类字段初始化还没有运行。继承内置类型(Inheriting Built-in Types)
在 ES2015 中,当调用
super(...)
的时候,如果构造函数返回了一个对象,会隐式替换this
的值。所以捕获super()
可能的返回值并用this
替换它是非常有必要的。这就导致,像
Error
、Array
等子类,也许不会再如你期望的那样运行。这是因为Error
、Array
等类似内置对象的构造函数,会使用 ECMAScript 6 的new.target
调整原型链。然而,在 ECMAScript 5 中,当调用一个构造函数的时候,并没有方法可以确保new.target
的值。 其他的降级编译器默认也会有同样的限制。对于一个像下面这样的子类:
你也许可以发现:
undefined
,所以调用sayHello
会导致错误instanceof
失效,(new MsgError()) instanceof MsgError
会返回false
。我们推荐,手动的在
super(...)
调用后调整原型:不过,任何
MsgError
的子类也不得不手动设置原型。如果运行时不支持Object.setPrototypeOf
,你也许可以使用__proto__
。不幸的是,这些方案并不会能在 IE 10 或者之前的版本正常运行。解决的一个方法是手动拷贝原型中的方法到实例中(就比如
MsgError.prototype
到this
),但是它自己的原型链依然没有被修复。成员可见性(Member Visibility)
你可以使用 TypeScript 控制某个方法或者属性是否对类以外的代码可见。
public
类成员默认的可见性为
public
,一个public
的成员可以在任何地方被获取:因为
public
是默认的可见性修饰符,所以你不需要写它,除非处于格式或者可读性的原因。protected
protected
成员仅仅对子类可见:受保护成员的公开(Exposure of protected members)
派生类需要遵循基类的实现,但是依然可以选择公开拥有更多能力的基类子类型,这就包括让一个
protected
成员变成public
:这里需要注意的是,如果公开不是故意的,在这个派生类中,我们需要小心的拷贝
protected
修饰符。交叉等级受保护成员访问(Cross-hierarchy protected access)
不同的 OOP 语言在通过一个基类引用是否可以合法的获取一个
protected
成员是有争议的。在 Java 中,这是合法的,而 C# 和 C++ 认为这段代码是不合法的。
TypeScript 站在 C# 和 C++ 这边。因为
Derived2
的x
应该只有从Derived2
的子类访问才是合法的,而Derived1
并不是它们中的一个。此外,如果通过Derived1
访问x
是不合法的,通过一个基类引用访问也应该是不合法的。看这篇《Why Can’t I Access A Protected Member From A Derived Class?》,解释了更多 C# 这样做的原因。
private
private
有点像protected
,但是不允许访问成员,即便是子类。因为
private
成员对派生类并不可见,所以一个派生类也不能增加它的可见性:交叉实例私有成员访问(Cross-instance private access)
不同的 OOP 语言在关于一个类的不同实例是否可以获取彼此的
private
成员上,也是不一致的。像 Java、C#、C++、Swift 和 PHP 都是允许的,Ruby 是不允许。TypeScript 允许交叉实例私有成员的获取:
警告(Caveats)
private
和protected
仅仅在类型检查的时候才会强制生效。这意味着在 JavaScript 运行时,像
in
或者简单的属性查找,依然可以获取private
或者protected
成员。private
允许在类型检查的时候,通过方括号语法进行访问。这让比如单元测试的时候,会更容易访问private
字段,这也让这些字段是弱私有(soft private)而不是严格的强制私有。不像 TypeScript 的
private
,JavaScript 的私有字段(#
)即便是编译后依然保留私有性,并且不会提供像上面这种方括号获取的方法,这让它们变得强私有(hard private)。当被编译成 ES2021 或者之前的版本,TypeScript 会使用 WeakMaps 替代
#
:如果你需要防止恶意攻击,保护类中的值,你应该使用强私有的机制比如闭包,
WeakMaps
,或者私有字段。但是注意,这也会在运行时影响性能。TypeScript 系列
TypeScript 系列文章由官方文档翻译、重难点解析、实战技巧三个部分组成,涵盖入门、进阶、实战,旨在为你提供一个系统学习 TS 的教程,全系列预计 40 篇左右。点此浏览全系列文章,并建议顺便收藏站点。
微信:「mqyqingfeng」,加我进冴羽唯一的读者群。
如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。