面经-Ts语法相关

本文最后更新于:2025年1月14日 晚上

TS基础写法

TypeScript 教程 | 菜鸟教程 (runoob.com)

基础类型 · TypeScript中文网 · TypeScript——JavaScript的超集 (tslang.cn)

变量

1
private PlayStateCheckHandle: TimerHandle | undefined = undefined;

|表示联合类型,表示既可以是TimerHandle也可以是undefined

对象

1
Proto_Nodes: { [k: string]: Aki.Protocol.IProto_NodeInfo };

表示一个索引签名(index signature),用于定义一个对象类型,其中键(key)是字符串类型,而值(value)是 Aki.Protocol.IProto_NodeInfo 类型

函数

可变长参数

…args

1
2
3
4
5
6
7
8
function myFunction(first: number, ...rest: string[]) {
console.log(`First argument is ${first}`);
for (let arg of rest) {
console.log(arg);
}
}

myFunction(1, 'hello', 'world', '!'); // 输出: First argument is 1, hello, world, !

在 TypeScript 中,函数内部创建的局部变量确实可以返回并在函数外部被直接修改,但这通常适用于可变类型(mutable types),如对象(包括 MapSet、数组等)和函数。对于不可变类型(immutable types),如原始数据类型(数字、字符串、布尔值等),则不能直接修改。

可变类型:

当函数返回一个可变类型的值时,实际上返回的是这个值的引用(reference)。因此,通过这个引用所做的任何修改都会反映到原始对象上。

1
2
3
4
5
6
7
8
复制function createObject() {
const obj = { prop: 1 };
return obj; // 返回对象的引用
}

const myObj = createObject();
myObj.prop = 2; // 直接修改了 createObject 函数内部创建的对象
console.log(myObj.prop); // 输出: 2

这个是因为看的时候看到一段对我来说比较吊诡的代码,在一个私有函数创建了一个map(nodes),直接set到了另一个map(NodesGroupByStatus)里面,但是直接返回nodes,在另一个函数里面可以直接修改,后面查了才知道,ts如果返回maps这种可修改的变量相当于返回的是引用,所以就是改的原始的nodes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private AddNodeGroup(group: ENodeGroup): Map<number, TBehaviorNode> {
const nodes = new Map<number, TBehaviorNode>();
this.NodesGroupByStatus!.set(group, nodes);
return nodes;
}


public AddNodeToStatusGroup(node: TBehaviorNode, newStatus: Aki.Protocol.Proto_NodeStatus): void {
const groupId = this.GetGroupIdByStatus(newStatus);
let nodes = this.GetNodesByGroupId(groupId);
if (!nodes) {
nodes = this.AddNodeGroup(groupId);
}

nodes.set(node.NodeId, node);
}

继承extends

super关键字

  1. 调用父类构造函数:当你定义一个子类时,如果需要调用其父类的构造函数,可以使用 super()。这是必须的步骤,因为子类的实例在JavaScript/TypeScript中总是通过父类的构造函数创建的。
  2. 访问父类成员super 也可以用来访问父类中定义的属性和方法,这在你想要扩展或重写父类成员时非常有用。
  3. 访问父类的静态成员:使用 super 关键字,也可以访问父类的静态属性或方法
  4. 在构造函数中:在构造函数中使用 super 必须作为第一条语句,因为子类实例的创建是基于父类构造函数的。
  5. 在派生类的静态方法中:在静态方法中使用 super 来访问父类的静态成员。
  6. 在派生类的方法中:在非静态方法中使用 super 来访问父类的非静态成员。

泛型

1.基础泛型函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 不使用泛型
function returnString(value: string): string {
return value;
}
function returnNumber(value: number): number {
return value;
}

// 使用泛型
function returnItem<T>(value: T): T {
return value;
}

// 使用示例
const str = returnItem<string>("Hello"); // 类型为 string
const num = returnItem<number>(42); // 类型为 number
const bool = returnItem(true); // 类型推断为 boolean

2.泛型接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 定义一个泛型接口
interface Box<T> {
value: T;
getValue(): T;
}

// 实现泛型接口
class StringBox implements Box<string> {
constructor(public value: string) {}
getValue(): string {
return this.value;
}
}

class NumberBox implements Box<number> {
constructor(public value: number) {}
getValue(): number {
return this.value;
}
}

// 使用示例
const stringBox = new StringBox("Hello");
const numberBox = new NumberBox(42);

3.泛型约束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 使用extends关键字约束泛型类型
interface HasLength {
length: number;
}

function logLength<T extends HasLength>(value: T): number {
console.log(value.length);
return value.length;
}

// 可以使用的类型
logLength("Hello"); // 字符串有length属性
logLength([1, 2, 3]); // 数组有length属性
logLength({ length: 10 }); // 对象有length属性

// 会报错,因为number没有length属性
// logLength(42); // Error

4.多个类型参数

1
2
3
4
5
6
7
// 键值对映射
function getPair<K, V>(key: K, value: V): { key: K; value: V } {
return { key, value };
}

const pair1 = getPair<string, number>("age", 25);
const pair2 = getPair("name", "John"); // 类型推断

5.泛型类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class DataStorage<T> {
private data: T[] = [];

addItem(item: T) {
this.data.push(item);
}

removeItem(item: T) {
const index = this.data.indexOf(item);
if (index > -1) {
this.data.splice(index, 1);
}
}

getItems(): T[] {
return [...this.data];
}
}

// 使用示例
const textStorage = new DataStorage<string>();
textStorage.addItem("Hello");
textStorage.addItem("World");
console.log(textStorage.getItems()); // ["Hello", "World"]

const numberStorage = new DataStorage<number>();
numberStorage.addItem(10);
numberStorage.addItem(20);
console.log(numberStorage.getItems()); // [10, 20]

委托

一开始看这个OnUpdate的时候非常懵逼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
OnUpdate(newUpdate: $Undefinable<$Delegate<(InProgress: number) => void>>) : $Undefinable<LTweener>;    

protected override OnTick(deltaTime: number): void {
const tween = this.TextPlayTweenComp!.GetPlayTween() as UE.LGUIPlayTween_Int;
const tweener = tween?.GetTweener();
if (tweener) {
tweener.OnUpdate(
toManualReleaseDelegate((progress: number): void => {
const pro = Math.round(progress * (tween.to - tween.from)) + tween.from;
if (this.Progress < pro) {
this.Progress = pro;
this.GetText(EDigitalScreenView.TextList)?.SetText(this.Text.substring(0, this.Progress));
}
}),
);
}
}

运算符

as

基础类型 · TypeScript中文网 · TypeScript——JavaScript的超集 (tslang.cn)

类型断言,有点类似其他语言的类型转换,通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型。

1
2
3
let someValue: any = "this is a string";

let strLength: number = (someValue as string).length;

三元运算符

const id = a ? b : c;

条件 ? 表达式1 : 表达式2

  • 如果 条件(在这里是变量 a)为真(truthy),那么整个表达式的结果是 表达式1(在这里是变量 b)。
  • 如果 条件 为假(falsy),那么结果是 表达式2(在这里是变量 c)。

==和===和!==

  1. 严格等于 (===):
    • === 是严格等于运算符,它比较两个值是否完全相等,包括它们的类型。
    • 如果两个操作数的类型不同,=== 返回 false
    • 只有当两个操作数的类型和值都相同时,=== 才返回 true
  2. 等于 (==):
    • == 是等于运算符,它比较两个值是否等价。
    • 如果操作数的类型不同,JavaScript 会进行类型转换,然后再比较它们的值。
    • == 会根据需要将操作数转换为数字或字符串,然后进行比较。
  3. !==

​ 不相等,但是不会进行类型转换

?.和!.

! 被称为非空断言操作符,它用来告诉 TypeScript 编译器,某个位置的值不应该为 nullundefined

?.是一种语法糖, 如果多层访问中间有null或者undefined就会返回undefined,不用自己再另写类型检查

?:

表示调用这个对象或者参数的时候这个参数可选

??

逻辑运算符,用于返回两个操作数中第一个非空值(non-nullish value),或者在两个操作数都为空值(null 或 undefined)时返回右侧的操作数。

UE4容器使用

TArray

TArray:虚幻引擎中的数组 | 虚幻引擎4.26文档 (unrealengine.com)

创建和填充

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 创建
TArray<int32> IntArray;

// 填充
IntArray.Init(10,5);
// 等价于下面
// IntArray = [10, 10, 10, 10, 10]

// 在末尾增加用Add或者Emplace
// Add会创建一个新实例,Emplace不会

TArray<FString> StrArr;
StrArr.Add (TEXT("Hello"));
StrArr.Emplace(TEXT("World"));
// StrArr == ["Hello","World"]

// 利用 Append 可一次性添加其他 TArray 中的多个元素

FString Arr[] = { TEXT("of"), TEXT("Tomorrow") };
StrArr.Append(Arr, ARRAY_COUNT(Arr));
// StrArr == ["Hello","World","of","Tomorrow"]

// 仅在尚不存在等值元素时, AddUnique 才会向容器添加新元素。

StrArr.AddUnique(TEXT("!"));
// StrArr == ["Hello","World","of","Tomorrow","!"]

StrArr.AddUnique(TEXT("!"));
// StrArr is unchanged as "!" is already an element

// Insert

StrArr.Insert(TEXT("Brave"), 1);

// SetNum,如新数量大于当前数量,则使用元素类型的默认构造函数新建元素
// 如新数量小于当前数量, SetNum 将移除元素。


迭代

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// ranged-for
FString JoinedStr;
for(auto& Str :StrArr)
{
JoinedStr+=Str;
}

// 直接for循环
for (int32 Index = 0; Index != StrArr.Num(); ++Index)
{
JoinedStr += StrArr[Index];
JoinedStr += TEXT(" ");
}

// 数组迭代器:CreateIterator读写;CreateConstIterator只读
for(auto It = StrArr.CreateConstIterator();It;++It)
{
JoinedStr += *It;
JoinedStr += TEXT(" ");
}

排序

1
2
3
// Sort,基于快排
// HeapSort,堆排序
// StableSort,基于归并排序,可以保证等值元素的相对顺序,上面两个无法保证

查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// Num,查询数量

// GetData,返回指针元素

// Contains,查询是否包含特定元素

// ContainsByPredicate,可自己写规则

bool bHello = StrArr.Contains(TEXT("Hello"));
bool bGoodbye = StrArr.Contains(TEXT("Goodbye"));
// bHello == true
// bGoodbye == false

bool bLen5 = StrArr.ContainsByPredicate([](const FString& Str){
return Str.Len() == 5;
});
bool bLen6 = StrArr.ContainsByPredicate([](const FString& Str){
return Str.Len() == 6;
});
// bLen5 == true
// bLen6 == false

// Find,返回找到的第一个元素的索引
// FindLast,范围找到的最后一个元素的索引
int32 IndexLast;
if (StrArr.FindLast(TEXT("Hello"), IndexLast))
{
// IndexLast == 3, because there aren't any duplicates
}

// 上面时返回布尔值,也可以直接返回索引,未找到时INDEX_NONE
int32 Index2 = StrArr.Find(TEXT("Hello"));
int32 IndexLast2 = StrArr.FindLast(TEXT("Hello"));
int32 IndexNone = StrArr.Find(TEXT("None"));
// Index2 == 3
// IndexLast2 == 3
// IndexNone == INDEX_NONE

// IndexOfByKey
// IndexOfByPredicate

// FilterByPredicate可以直接找到匹配的元素数组
auto Filter = StrArray.FilterByPredicate([](const FString& Str){
return !Str.IsEmpty() && Str[0] < TEXT('M');
});

移除

1
2
3
4
5
6
7
8
9
10
// Remove,移除所有提供元素等值的元素
// RemoveSingle,移除首个匹配元素
// RemoveAt,移除特定位置元素
// RemoveAll,可以自己定义规则


// 如果不需要保证排序,可以用下面的来加快速度
// RemoveSwap,RemoveAtSwap,RemoveAllSwap

// Empty 清空

运算符

1
2
3
4
5
6
7
8
// +=,串联

// MoveTemp,移动语义,清空源数组
ValArr3 = MoveTemp(ValArr4);
// ValArr3 == [5,2,3,1,2,3]
// ValArr4 == []

// == 和 != 进行比较,必须排序和数量都一样

Slack

内存

TMap

TMap | 虚幻引擎4.27文档 (unrealengine.com)

TMap键不能重复,TMultiMap键不唯一

创建和填充

1
2
3
4
TMap<int32, FString> FruiMap;

// 填充都一样,但是要是填重复的键会覆盖之前的值

迭代

类似TArrays,不过迭代元素是TPair

查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// Contains来查询是否包含特定键

// Find,失败返回null

// FindOrAdd,不存在该键会新创建一个元素

// FindRef,没找到会返回默认值,不会创建新元素

FString& Ref7 = FruitMap.FindOrAdd(7);
// Ref7 == "Pineapple"
// FruitMap == [
// { Key:5, Value:"Mango" },
// { Key:2, Value:"Pear" },
// { Key:7, Value:"Pineapple" },
// { Key:4, Value:"Kiwi" },
// { Key:3, Value:"Orange" },
// { Key:9, Value:"Melon" }
// ]
FString& Ref8 = FruitMap.FindOrAdd(8);
// Ref8 == ""
// FruitMap == [
// { Key:5, Value:"Mango" },
// { Key:2, Value:"Pear" },
// { Key:7, Value:"Pineapple" },
// { Key:4, Value:"Kiwi" },
// { Key:3, Value:"Orange" },
// { Key:9, Value:"Melon" },
// { Key:8, Value:"" }
// ]

FString Val7 = FruitMap.FindRef(7);
FString Val6 = FruitMap.FindRef(6);
// Val7 == "Pineapple"
// Val6 == ""
// FruitMap == [
// { Key:5, Value:"Mango" },
// { Key:2, Value:"Pear" },
// { Key:7, Value:"Pineapple" },
// { Key:4, Value:"Kiwi" },
// { Key:3, Value:"Orange" },
// { Key:9, Value:"Melon" },
// { Key:8, Value:"" }
// ]

// FindKey,按值查找

移除

1
2
3
4
5
6
7
8
9
// Remove,移除对应键的元素
FruitMap.Remove(8);
// FindAndRemoveChecked,移除元素并返回该值
FString Removed7 = FruitMap.FindAndRemoveChecked(7);
// RemoveAndCopyValue 函数的作用与 Remove 相似,不同点是会将已移除元素的值复制到引用参数
FString Removed;
bool bFound2 = FruitMap.RemoveAndCopyValue(2, Removed);

// Empty和Reset,清空

排序

1
2
3
4
// KeySort或者ValueSort
FruitMap.KeySort([](int32 A, int32 B) {
return A > B; // sort keys in reverse
});

运算符

有复制和移动语义

Slack

KeyFuncs

TSet

UE4回调

委托 | 虚幻引擎4.26文档 (unrealengine.com)

一文理解透UE委托Delegate - 知乎 (zhihu.com)

UE支持三种委托:单点委托,组播委托(事件),动态委托

单点委托

多播

动态委托

UE4对象函数

UFunctions | 虚幻引擎4.26文档 (unrealengine.com)

UE4引用

引用资源 | 虚幻引擎4.26文档 (unrealengine.com)

引用 Actor | 虚幻引擎4.26文档 (unrealengine.com)

UE引用类型说明 - 飞书云文档 (feishu.cn)

引用分为两种,硬性引用,即对象 A 引用对象 B,并导致对象 B 在对象 A 加载时加载;软性引用,即对象 A 通过间接机制(例如字符串形式的对象路径)来引用对象 B。

一种是通过UPROPERTY

直接属性引用

通过设置变量的UPROPERTY直接在编辑器设置对应资源

1
2
3
4
5
/** construction start sound stinger */

UPROPERTY(EditDefaultsOnly, Category=Building)

USoundCue* ConstructionStartStinger;

构造时引用

构造时加载对应资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/** gray health bar texture */

UPROPERTY()

class UTexture2D* BarFillTexture;

AStrategyHUD::AStrategyHUD(const FObjectInitializer& ObjectInitializer) :
Super(ObjectInitializer)
{
static ConstructorHelpers::FObjectFinder<UTexture2D> BarFillObj(TEXT("/Game/UI/HUD/BarFill"));

...

BarFillTexture = BarFillObj.Object;

...

}

一种是用字符串来引用,如果UObject已经加载就用FindObject<>(),没有加载就使用LoadObject<>()

1
2
AFunctionalTest* TestToRun = FindObject<AFunctionalTest>(TestsOuter, *TestName);
GridTexture = LoadObject<UTexture2D>(NULL, TEXT("/Engine/EngineMaterials/DefaultWhiteGrid.DefaultWhiteGrid"), NULL, LOAD_None, NULL);

UE4FName

FName | 虚幻引擎4.26文档 (unrealengine.com)

内容浏览器 中为新资源命名时,变更动态材质实例中的参数或访问骨骼网格体中的一块骨骼时需要使用 FNames 。 FName 通过一个轻型系统使用字符串。在此系统中,特定字符串即使会被重复使用,在数据表中也只存储一次。

FNames 不区分大小写。它们为不可变,无法被操作。FNames 的存储系统和静态特性决定了通过键进行 FNames 的查找和访问速度较快。 FName 子系统的另一个功能是使用散列表为 FName 转换提供快速字符串。

FNames 不区分大小写,作为索引组合存储在唯一字符串和实例编号的表格中。

创建

1
FName TestName = FName(TEXT("Test"));

转换

1
2
3
4
5
6
7
8
9
10
11
12
// FMame到FString
TestString = TestName.ToSring();

// FName到FText
TestText = FText::FromName(TestName);

// Fstring和FText到Fname都不可靠,因为FName不区分大小写

// FString到FName
TestName = FName(*TextString);

// FText无法直接到FName

对比是否相同直接比较索引的数值,不用执行字符串的对比

TS和Lua有什么区别

TS

优点:

1.静态类型系统

  • 在编译时进行类型检查

  • 可以提前发现潜在错误

  • 提供更好的代码提示和自动完成

2.面向对象特性

  • 支持类、接口、泛型等现代OOP特性

  • 继承和多态的实现更加完整

3.JavaScript生态系统

  • 可以直接使用JavaScript的所有库和框架

  • 与现代前端开发工具链完美集成

  • 庞大的npm生态系统

4.工具支持

  • 优秀的IDE支持(VS Code等)

  • 强大的重构工具

  • 详细的文档和类型定义

缺点:

1.编译开销

  • 需要编译成JavaScript才能运行

  • 构建过程可能较慢

2.学习曲线

  • 类型系统较复杂

  • 需要理解装饰器、泛型等概念

lua

1.轻量级

  • 解释器小巧(约200KB)

  • 启动快速,内存占用少

  • 易于嵌入其他程序

2.性能

  • LuaJIT提供极高的执行效率

  • 垃圾回收效率高

3.简单易学

  • 语法简洁清晰

  • 核心概念少

  • 学习曲线平缓

4.嵌入性

  • 广泛用于游戏开发

  • 适合作为脚本语言嵌入应用

  • 容易与C/C++集成

缺点:

1.标准库较小

  • 内置功能相对有限

  • 需要依赖第三方库实现复杂功能

2.生态系统

  • 相比现代语言,生态系统较小

  • 工具链不如主流语言完善

3.面向对象支持

  • 没有原生的类支持

  • 需要通过元表模拟OOP特性

不同类型变量

值类型(按值传递):

number

string

boolean

undefined

null

symbol

引用类型(按引用传递):

Object

Array

Function

Date

RegExp

Map

Set

类实例


面经-Ts语法相关
https://rorschachandbat.github.io/找工作/面经-Ts语法相关/
作者
R
发布于
2024年12月30日
许可协议