许多API
返回一个对象或null
/ undefined
,并且可能仅在结果不为null
时才希望从结果中提取属性,Optional Chaining Operator
允许开发人员处理许多情况,而无需重复自己和/或在临时变量中分配中间结果,TS
中可以直接使用该运算符
。
const { log } = console;
let test = Math.random() > 0.5 ? ['ssss'] : null;
log(test?.length);
// 1
// undefined
// undefined
// 1
基础案例
?.
运算符左侧(LHS
)的值为undefined
或null
,则表达式的值为undefined
。否则,将正常执行后续属性
访问,方法
或函数
调用。
a?.b // undefined if `a` is null/undefined, `a.b` otherwise.
// a == null ? undefined : a.b
a?.[x] // undefined if `a` is null/undefined, `a[x]` otherwise.
// a == null ? undefined : a[x]
a?.b() // undefined if `a` is null/undefined
// a == null ? undefined : a.b()
// throws a TypeError if `a.b` is not a function
// otherwise, evaluates to `a.b()`
a?.() // undefined if `a` is null/undefined
// a == null ? undefined : a()
// throws a TypeError if `a` is neither null/undefined, nor a function
// invokes the function `a` otherwise
a is null /undefined | a is not null /undefined | |
---|---|---|
a?.b | undefined | a.b |
a?.[x] | undefined | a[x] |
a?.b() | undefined | a.b() |
a?.() | undefined | a() |
短路
如果,运算符左侧(LHS
),?.
评估为null
/undefined
,则不评估运算符右侧(RHS
),这个过程称为短路(Short-circuiting)
。
a?.[++x] // `x` is incremented if and only if `a` is not null/undefined
// a == null ? undefined : a[++x]
长时间短路
实际上,短路
在被触发时不仅会跳过当前的属性
访问,方法
或函数
调用,而且还会直接跳过可选链接
运算符之后的属性
访问,方法
或函数
调用的整个执行链
。
a?.b.c(++x).d // if `a` is null/undefined, evaluates to undefined. Variable `x` is not incremented.
// otherwise, evaluates to a.b.c(++x).d.
// a == null ? undefined : a.b.c(++x).d
因为先对a
和null
进行匹配检查。
a
为null
,则直接短路返回undefined
。a
不为null
,但a.b
为空,则在试图访问a.b
的c
属性时,就会抛出一个TypeError
错误。该功能由例如C#
和CoffeeScript
来实现;参见现有技术。
可选链堆
Optional Chain
是Optional Chaining
运算符,后跟一系列属性
访问,方法
或函数
调用。即可选链
之后可以跟随另一个可选链
。
a?.b[3].c?.(x).d
// a == null ? undefined : a.b[3].c == null ? undefined : a.b[3].c(x).d
// (as always, except that `a` and `a.b[3].c` are evaluated only once)
边缘限制
使用括号运算符
可以限制了短路
的判断范围。
(a?.b).c
// (a == null ? undefined : a.b).c
因为先对a
和null
进行匹配检查。
a
为null
,则直接短路返回undefined
。a
不为null
,但a.b
为空,则直接短路返回undefined
,后续即访问undefined
的c
,就会抛出一个TypeError
错误。可选链 && 删除
可选链
可以和delete
运算符,用于限制delete
运算符直接操作的内容范围。
delete a?.b
// a == null ? true : delete a.b
尽管可以出于完整性考虑将它们包括在内,但由于缺乏实际用例或其他令人信服的原因,因此不支持以下内容
new a?.()
a?.\
string``new a?.b()
,a?.b\
string``尽管以下内容有一些用例,但不支持以下内容:参见第18期的讨论:
a?.b = c
至少在实践中不支持以下内容,因为它没有多大意义,参见问题#4(评论):
super?.()
,super?.foo
。new?.target
和import?.('foo')
等不包含在内。语法或静态语义将禁止上述所有情况,以便稍后添加支持。
obj?.[expr]和func?.(arg)看起来很丑陋。为什么不使用obj?[expr]和func?(arg)一样<语言X>?
我们不使用obj?[expr]
和 func?(arg)
语法,因为解析器很难有效地将这些形式,与条件运算符(例如obj?[expr].filter(fun)
和func?(x - 2) + 3
区分开。
这两种情况的替代语法各有其缺陷。而决定哪一个看上去最糟则主要是个人品味的问题。这是我们做出选择的方式:
obj?.prop
情况的语法,这种语法最常出现?.
的字符序列的使用扩展到其他情况:obj?.[expr]
,func?.(arg)
。至于<language X>
,它具有与JavaScript
不同的语法约束,这是因为<X不支持某些构造或X中的工作方式不同>
。
为什么
(null)?.b
会默认返回undefined
,而不是null
?
a?.b
是a
变量下是否有b
属性,如果没有找到对应的b
属性,则返回undefined
,即因为a.b = undefined
,所以,a?.b = undefined
。
特别是,该值null
被认为没有属性。因此,(null)?.b
是返回undefined
。
为什么
foo?.()
,会在在foo为undefined
或者null
时,抛出错误?
想象一下一个库,它将onChange
在用户提供处理程序时调用它,例如。如果用户提供的是数字3
而不是函数,则该库很可能会抛出错误的用法并通知用户。这正是所建议的语义要onChange?.()
实现的。
此外,这确保了?.
在所有情况下都具有一致的含义,无需在我们进行检查的特殊情况下,需要先执行typeof foo === 'function'
,再执行后续值有效性校验,只需全面检查foo == null
即可。
最后,请记住,可选链接不是错误抑制机制。
可选操作只会检测,对应的值是undefined
或者 null
,不会捕获或抑制通过评估周围的代码引发的错误。
(function () {
"use strict"
undeclared_var?.b // ReferenceError: undeclared_var is not defined
arguments?.callee // TypeError: 'callee' may not be accessed in strict mode
arguments.callee?.() // TypeError: 'callee' may not be accessed in strict mode
true?.() // TypeError: true is not a function
})()
更多疑问请查看optional-chaining QA
执行属性访问时,通常希望提供默认值
,如果该属性访问的结果为null
或undefined
。
当前,在JavaScript
中表达此意图的典型方法是使用||
运算符。
const response = {
settings: {
nullValue: null,
height: 400,
animationDuration: 0,
headerText: '',
showSplashScreen: false
}
};
const undefinedValue = response.settings.undefinedValue || 'some other default'; // result: 'some other default'
const nullValue = response.settings.nullValue || 'some other default'; // result: 'some other default'
这在null
和undefined
值的常见情况下效果很好,但是有一些虚假的值可能会产生令人惊讶的结果:
const headerText = response.settings.headerText || 'Hello, world!'; // Potentially unintended. '' is falsy, result: 'Hello, world!'
const animationDuration = response.settings.animationDuration || 300; // Potentially unintended. 0 is falsy, result: 300
const showSplashScreen = response.settings.showSplashScreen || true; // Potentially unintended. false is falsy, result: true
无效合并运算符旨在更好地处理这些情况,并用作对无效值(null
或undefined
)的相等性检查。
主要使用??
运算符,如果左侧的表达式值为undefined
或null
的时候,则返回其右侧表达式值。
const response = {
settings: {
nullValue: null,
height: 400,
animationDuration: 0,
headerText: '',
showSplashScreen: false
}
};
const undefinedValue = response.settings.undefinedValue ?? 'some other default'; // 'some other default'
const nullValue = response.settings.nullValue ?? 'some other default'; // 'some other default'
const headerText = response.settings.headerText ?? 'Hello, world!'; // ''
const animationDuration = response.settings.animationDuration ?? 300; // 0
const showSplashScreen = response.settings.showSplashScreen ?? true; // false
设计模式中有种模式是装饰者模式
,decorator
的实现和这个设计模式很相近,主要做一些非侵入式
的能力扩展。
在 ES6
之前,装饰器
可能并没有那么重要,因为你只需要加一层 wrapper
就好了,但是现在,由于语法糖 class
的出现,它们不支持类
所需的一些常见行为,这个时候想要去在多个类
之间共享或者扩展一些方法
的时候,代码会变得错综复杂,难以维护,而这,也正式我们 decorator
的用武之地。
注意目前decorators,还处于第二阶段中,之后语法可能会变化,注意及时关注decorators-github查看最新进度。
如果需要开启 decorator
这项experimental
支持,需要手动开启experimentalDecorators
选项。
tsc --target ES5 --experimentalDecorators
tsconfig.json:
或者修改tsconfig.json
配置。
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}
decorator
其实是一个语法糖,背后通过拦截es5
的Object.defineProperty(target,name,descriptor)
进行装饰功能,详细了解Object.defineProperty
可以查看MDN文档。
class Melon(){
@readonly
name
}
在属性上的修饰符,会在Object.defineProperty
为Melon
原型上注册对应属性之前,执行以下代码。
let descriptor = {
value:specifiedFunction,
enumerable:false,
configurable:true,
writeable:true
};
descriptor = readonly(Melon.prototype,'name',descriptor) || descriptor;
Object.defineProperty(Melon.prototype,'name',descriptor);
从上面的伪代码我们可以看出,decorator
只是在Object.defineProperty
为Melon.prototype
注册属性
之前,执行了一个装饰函数
,属于一个类对Object.defineProperty
的拦截。
也就是说在利用decorator
进行能力扩展时,主要是根据装饰
的目标
不同,取到对应的target
,name
,和descriptor
实参,做相关修改和扩展。
target
要在其上定义属性的对象。
name
要定义或修改的属性的名称。
descriptor中主要有
configurable
,enumerable
,value
,writable
,get
,和set
属性可配置
属性名 | 属性描述 |
---|---|
configurable | 通用配置项,当且仅当该属性的 configurable 为 true 时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false 。 |
enumerable | 通用配置项,当且仅当该属性的enumerable 为true 时,该属性才能够出现在对象的枚举属性中。默认为 false 。 |
value | 数据描述专有配置项,该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined 。 |
writable | 数据描述专有配置项,当且仅当该属性的writable 为true 时,value 才能被赋值运算符改变。默认为 false 。 |
get | 数据存取专有配置项,一个给属性提供 getter 的方法,如果没有 getter 则为 undefined 。当访问该属性时,该方法会被执行,方法执行时没有参数传入,但是会传入this 对象(由于继承关系,这里的this 并不一定是定义该属性的对象)。默认为 undefined 。 |
set | 数据存取专有配置项,一个给属性提供 setter 的方法,如果没有 setter 则为 undefined 。当属性值修改时,触发执行该方法。该方法将接受唯一参数,即该属性新的参数值。默认为 undefined 。 |
descriptor
中主要分两种形式来做数据描述符
和存取描述符
。
Object.defineProperty
方法做具体属性值定义,称为数据描述符
行为,比如装饰器
中对于类属性
的复写,就是属于这种情况。Object.defineProperty
方法做属性的访问器相关定义,称为存取描述符
行为,比如装饰器
中对于类整体
和类方法
的复写,就是属于这种情况。vue 2.x之前的版本数据双向观测联动也是通过这个来实现的。configurable | enumerable | value | writable | get | set | |
---|---|---|---|---|---|---|
数据描述符 | Yes | Yes | Yes | Yes | No | No |
存取描述符 | Yes | Yes | No | No | Yes | Yes |
decorator
是一种特殊种类的声明,可被附连到一个类声明
,方法
,访问器
,属性
,或参数
。
装饰器使用形式是@expression
,在其中expression
必须是一个函数,该函数将在运行时使用有关修饰声明的信息来调用。
在一行上使用多个装饰器。
@f @g x
在多行上使用多个装饰器。
@f
@g
x
当多个修饰符应用于一个声明时,它们的求值类似于数学中的函数组成。
上述代码中,是将f
和g
两个方法进行能力组合, 组合结果是 f(g(x))
。
因此,在TypeScript
中的单个声明上评估多个装饰器时,将执行以下步骤:
const { log } = console;
function f() {
log("f(): evaluated");
return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
log(target, propertyKey, "f(): called");
};
}
function g() {
log("g(): evaluated");
return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
log(target, propertyKey, "g(): called");
};
}
class C {
@f()
@g()
method() { }
}
const testObj: C = new C();
testObj.method();
// f(): evaluated
// g(): evaluated
// {}, 'method', g(): called
// {}, 'method', f(): called
decorator
是一种特殊种类的声明,可被附连到一个类声明
,类方法
,类访问器
,类属性
,或类方法参数
。
装饰器使用形式是@expression
,在其中expression
必须是一个函数,该函数将在运行时使用有关修饰声明的信息来调用。
class declaration && decorator
在类声明之前声明一个类装饰器
。
类装饰器
应用于类的构造函数
,可用于观察
,修改
或替换
类定义。
不能在声明文件
,或任何其他环境上下文(例如,在declare
类中)中使用类装饰器
。
类装饰器
的表达式将在运行时作为函数调用,装饰类的构造函数
为其唯一参数。
下面是一个有关如何重写构造函数
的示例。
function classDecorator<T extends { new (...args: any[]): {} }>(
constructor: T
) {
return class extends constructor {
newProperty = "new property";
hello = "override";
};
}
@classDecorator
class Greeter {
property = "property";
hello: string;
constructor(m: string) {
this.hello = m;
}
}
console.log(new Greeter("world"));
如果,类装饰器
返回一个值,它将用提供的构造函数
替换类声明。
注意,如果是选择返回新的构造函数
,则必须注意维护原始原型
,运行时应用装饰器的逻辑不会为执行此操作。
property declaration && decorator
属性装饰器
是在属性声明之前声明。
属性装饰器
不能在声明文件
,重载
或任何其他环境上下文(例如在declare
类中)中使用。
属性装饰器的表达式将在运行时作为函数调用,并带有以下两个参数:
注意,由于在TypeScript
中未初始化属性装饰器
,因此未将属性描述符
作为参数
提供给属性装饰器
。
这是因为当前在定义原型成员时没有描述实例属性
的机制,也没有观察
或修改
该属性的初始化程序
的方法。
返回值
也将被忽略。因此,属性装饰器
只能用于观察
已为类声明了特定名称
的属性
。
可以使用此信息来记录有关该属性的元数据
,@format
装饰器和getFormat
函数应用于Greeter
类上方法的属性装饰器
的示例。
import "reflect-metadata";
const formatMetadataKey = Symbol("format");
function format(formatString: string) {
return Reflect.metadata(formatMetadataKey, formatString);
}
function getFormat(target: any, propertyKey: string) {
return Reflect.getMetadata(formatMetadataKey, target, propertyKey);
}
class Greeter {
@format("Hello, %s")
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
let formatString = getFormat(this, "greeting");
return formatString.replace("%s", this.greeting);
}
}
当@format("Hello, %s")
被调用时,通过Reflect.metadata
函数,增加了使用属性元数据条目。
当getFormat
被调用时,它读取的格式中的元数据值
。
注意此示例需要reflect-metadata
库。有关库的更多信息,请参见元数据reflect-metadata。
method declaration && decorator
在方法
声明之前声明方法装饰器
。装饰器
将应用于方法的属性描述符,并可用于观察,修改或替换方法定义。
方法装饰器
不能在声明文件
,重载
或任何其他环境上下文(例如在declare
类中)中使用。
方法装饰器
的表达式将在运行时作为函数调用,并带有以下三个参数
注:如果你的js
编译版本低于是小于ES5
,属性描述将会是undefined
。
如果方法装饰器
返回一个值,它将用作方法
的属性描述符
。
以下是@enumerable
应用于Greeter
类上方法的方法装饰器
的示例
const { log } = console;
function enumerable(value: boolean) {
return function(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
descriptor.enumerable = value;
};
}
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
@enumerable(false)
greet() {
return "Hello, " + this.greeting;
}
}
const testObj: Greeter = new Greeter('melon');
log(testObj.greet());// Hello, melon
log(testObj.greeting);// melon
我们可以@enumerable
使用以下函数声明来定义方法装饰器
,当@enumerable(false)
装饰器被调用时,它修改enumerable
的属性描述符的属性。
accessor declaration && decorator
在访问器
声明之前,就声明了一个访问器装饰器
。访问器修饰符
应用于访问器
的属性描述符,可用于观察
,修改
或替换访问器
的定义。
访问装饰器
不能在声明文件
,重载
或任何其他环境上下文(例如在declare
类中)中使用。
注意,TypeScript
不允许装饰单个成员的get
和set
访问器。而是,该成员的所有装饰器必须应用于按文档顺序指定的第一个访问器。
这是因为访问器装饰器
适用于Property Descriptor
,它组合了get
和set
访问器,而不是分别合并每个声明。
访问器装饰器
的表达式将在运行时作为函数调用,并带有以下三个参数
注:如果你的js
编译版本低于是小于ES5
,属性描述将会是undefined
。
如果访问器装饰器
返回一个值,它将用作成员
的属性描述符
。
以下是@configurable
应用于Point
类成员的访问器修饰符的示例
const { log } = console;
function configurable(value: boolean) {
return function(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
descriptor.configurable = value;
};
}
class Point {
private _x: number;
private _y: number;
constructor(x: number, y: number) {
this._x = x;
this._y = y;
}
@configurable(false)
get x() {
return this._x;
}
@configurable(false)
get y() {
return this._y;
}
}
const testObj: Point = new Point(3,4);
log(testObj.x);// 3
log(testObj.y);// 4
testObj.x = 4;// Cannot assign to 'x' because it is a read-only property.
testObj.y = 5;// Cannot assign to 'y' because it is a read-only property.
parameter declaration && decorator
在参数
声明之前声明参数装饰器
。参数装饰器
应用于类构造函数,或方法声明的函数。
参数装饰器
不能在声明文件
,重载
或任何其他环境上下文(例如在declare
类中)中使用。
参数装饰器
的表达式将在运行时作为函数调用,并带有以下三个参数
注意,参数装饰器
只能用于观察已在方法上声明了的参数
,参数装饰器
的返回值将被忽略。
以下是@required
应用于Greeter
类上方法的参数装饰器
的示例
import "reflect-metadata";
const requiredMetadataKey = Symbol("required");
function required(
target: Object,
propertyKey: string | symbol,
parameterIndex: number
) {
let existingRequiredParameters: number[] =
Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || [];
existingRequiredParameters.push(parameterIndex);
Reflect.defineMetadata(
requiredMetadataKey,
existingRequiredParameters,
target,
propertyKey
);
}
function validate(
target: any,
propertyName: string,
descriptor: TypedPropertyDescriptor<Function>
) {
let method = descriptor.value;
descriptor.value = function() {
let requiredParameters: number[] = Reflect.getOwnMetadata(
requiredMetadataKey,
target,
propertyName
);
if (requiredParameters) {
for (let parameterIndex of requiredParameters) {
if (
parameterIndex >= arguments.length ||
arguments[parameterIndex] === undefined
) {
throw new Error("Missing required argument.");
}
}
}
return method.apply(this, arguments);
};
}
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
@validate
greet(@required name: string) {
return "Hello " + name + ", " + this.greeting;
}
}
该@required
装饰器用于标记该参数的元数据条目
。然后,@validate
装饰器是将现有greet
方法包装在一个函数中,该函数在调用原始方法之前先验证参数
。
注意此示例需要reflect-metadata
库。有关库的更多信息,请参见元数据reflect-metadata。
因为存在函数提升,这边目前装饰器
更多是应用于修改一个类声明
,类方法
,类访问器
,类属性
,或类方法参数
中,不能直接装饰具体方法
。
let counter = 0;
const add = function () {
counter++;
};
@add
function foo() {
}
上面的代码,意图是执行后counter
等于 1
,但是实际上结果是counter
等于 0
。因为函数提升
,使得实际执行的代码是下面这样。
@add
function foo() {
}
let counter;
let add;
counter = 0;
add = function () {
counter++;
};
下面是另一个例子。
const readOnly = require("some-decorator");
@readOnly
function foo() {
}
上面代码也有问题,因为实际执行是下面这样。
let readOnly;
@readOnly
function foo() {
}
readOnly = require("some-decorator");
总之,由于存在函数提升,使得装饰器
不能用于函数。类是不会存在函数提升
的,所以就没有这方面的问题。
另一方面,如果一定要装饰函数,可以采用高阶函数的形式直接执行。
function doSomething(name) {
console.log('Hello, ' + name);
}
function loggingDecorator(wrapped) {
return function() {
console.log('Starting');
const result = wrapped.apply(this, arguments);
console.log('Finished');
return result;
}
}
const wrapped = loggingDecorator(doSomething);
自标准化以来的20多年来,JavaScript
已经走了很长一段路。尽管在2020年,JavaScript
可以在服务器
,数据科学
甚至IoT设备
上使用,但更多的场景还是在Web浏览器。
网站由HTML
或XML
文档组成。这些文档是静态的,它们不会更改。document对象模型(DOM
)是浏览器
,为了方便操作静态页面提供的编程接口。
很多相关UI库这边都是DOM API可用于更改文档结构,样式和内容,实现相关操作。
TypeScript
是JavaScript
的类型化超集,它附带DOM API
的类型定义。这些定义可以在任何默认的TypeScript
项目中轻松获得,在TypeScript
可以通过HTMLElement
类型进行DOM
相关操作。
在此处探索DOM
类型定义的源代码:HTMLElement 实现。
下面示例,如何在TypeScript
中,将<p>Hello, World</p>
元素添加到#app
元素中。
// 1. Select the div element using the id property
const app = document.getElementById("app");
// 2. Create a new <p></p> element programmatically
const p = document.createElement("p");
// 3. Add the text content
p.textContent = "Hello, World!";
// 4. Append the p element to the div element
app?.appendChild(p);
编译并运行index.html
页面后,生成的HTML
将为
<div id="app">
<p>Hello, World!</p>
</div>
TypeScript
代码的第一行使用全局变量document
,检查该变量是否显示它是由lib.dom.d.ts
文件中的Document
接口定义的。
Document.getElementById
向其传递元素ID
字符串,如果能够在页面上找到对应元素,将返回HTMLElement
,如果找不到,则会返回null
。
getElementById(elementId: string): HTMLElement | null;
它充当所有其他元素接口的基础接口,例如,p
代码示例中变量的类型
为HTMLParagraphElement
。
因为,该方法返回值
无法在运行前确定,所以,可以结合可选链
运算符来调用HTMLElement
接口。
Document.createElement
向其传递元素任何内容string
,将返回标准HTMLElement
。开发人员可以通过这个接口是创建唯一的HTML
元素标签。
例如
document.createElement('a')
,那么它将是type
的元素HTMLAnchorElement
。document.createElement('xyz')
返回一个<xyz></xyz>
元素,显然不是HTML
规范指定的元素。createElement<K extends keyof HTMLElementTagNameMap>(tagName: K, options?: ElementCreationOptions): HTMLElementTagNameMap[K];
createElement(tagName: string, options?: ElementCreationOptions): HTMLElement;
上面是一个重载的函数定义。第二个重载是最简单的,并且与该getElementById
方法非常相似。
对于createElement
的第一个定义,它使用了一些高级通用模式,最好将其分解为多块。
<K extends keyof HTMLElementTagNameMap>
。该表达式定义了一个通用参数K
,该参数被限制在接口的键上HTMLElementTagNameMap
。HTMLElementTagNameMap
映射接口包含每个指定的HTML
标记名称及其对应的类型接口,例如,这是前5个映射值:
interface HTMLElementTagNameMap {
"a": HTMLAnchorElement;
"abbr": HTMLElement;
"address": HTMLElement;
"applet": HTMLAppletElement;
"area": HTMLAreaElement;
...
}
有些元素不具有唯一的属性,因此它们只是返回HTMLElement
,而其他类型的确具有唯一的属性和方法,因此它们返回其特定的接口(将从扩展或实现HTMLElement
)。
createElement
定义的其余部分:(tagName: K, options?: ElementCreationOptions): HTMLElementTagNameMap[K]
。第一个参数tagName
定义为通用参数K
。TypeScript
解释器足够聪明,可以从此参数推断出通用参数。这意味着开发人员在使用该方法时实际上不必指定泛型参数。传递给tagName
参数的任何值都将被推断为K
,因此可以在定义的其余部分中使用。
返回值HTMLElementTagNameMap[K]
接受tagName
参数,并使用它返回相应的类型
。
Node.appendChild
具体HTMLElement
对象执行appendChild
方法,向其传递元素任何内容string
,将返回标准HTMLElement
。
appendChild<T extends Node>(newChild: T): T;
此方法的工作方式与从createElement
参数T
推断出通用参数的方法类似newChild
。T
被限制在另一个基本接口上Node
。
querySelector && querySelectorAll
这两种方法都是获取适合更多唯一约束的dom
元素列表的出色工具。它们在lib.dom.d.ts
中定义为
/**
* Returns the first element that is a descendant of node that matches selectors.
*/
querySelector<K extends keyof HTMLElementTagNameMap>(selectors: K): HTMLElementTagNameMap[K] | null;
querySelector<K extends keyof SVGElementTagNameMap>(selectors: K): SVGElementTagNameMap[K] | null;
querySelector<E extends Element = Element>(selectors: string): E | null;
/**
* Returns all element descendants of node that match selectors.
*/
querySelectorAll<K extends keyof HTMLElementTagNameMap>(selectors: K): NodeListOf<HTMLElementTagNameMap[K]>;
querySelectorAll<K extends keyof SVGElementTagNameMap>(selectors: K): NodeListOf<SVGElementTagNameMap[K]>;
querySelectorAll<E extends Element = Element>(selectors: string): NodeListOf<E>;
该querySelectorAll
定义类似于getElementByTagName
,但它返回一个新类型:NodeListOf
。
此返回类型本质上是标准JavaScript list
元素的自定义实现。可以说,替换NodeListOf<E>
为E[]
会带来非常相似的用户体验。
NodeListOf
只有实现了以下属性和方法:length
,item(index)
,forEach((value, key, parent) => void)
,和数字索引
。
注意,此方法返回元素列表
,而不是节点列表
,这是NodeList
是取的Node.childNodes
属性。
要查看实际使用的这些方法,请将现有代码修改为:
<ul>
<li>First :)</li>
<li>Second!</li>
<li>Third times a charm.</li>
</ul>;
const first = document.querySelector("li"); // returns the first li element
const all = document.querySelectorAll("li"); // returns the list of all li elements
有时候你会遇到这样的情况,你会比TypeScript
更了解某个值的详细信息。
通常这会发生在你清楚地知道一个实体
具有比它现有类型更确切的类型
。
通过类型断言
这种方式可以告诉编译器,"相信我,我知道自己在干什么"。
类型断言
好比其它语言里的类型转换
,或者类型装箱,但是不进行特殊的数据检查
和解构
。
它没有运行时的影响,只是在编译阶段
起作用。 TypeScript
会假设你已经进行了必须的检查。
类型断言
有两种形式。
其一是"尖括号"语法:
const someValue: any = "this is a string";
const strLength: number = (<string>someValue).length;
// 当然直接写也是可以的。
const strLength2: number = someValue.length;// 16
另一个为as语法:
const someValue: any = "this is a string";
const strLength: number = (someValue as string).length;
两种形式是等价的。 至于使用哪个大多数情况下是凭个人喜好;然而,当你在TypeScript
里使用JSX
时,只有as
语法断言是被允许的。