返回首页

Angular 和 .NET 的助记笔记

C# 多线程、JS / C# GC、TypeScript 类型小技巧、Angular 生命周期与依赖注入。

发布 2019年4月10日 标签 #csharp #angular #typescript #notes

~/posts/angular-dotnet-notes $ cat post.md

/ 语言 EN / 中文
/ 主题 / /

C#

多线程

class Program
{
    static void Main(string[] args)
    {
        Thread t1 = new Thread(new ThreadStart(WithOut));
        Thread t2 = new Thread(new ParameterizedThreadStart(With));
        t1.IsBackground = true;
        t2.IsBackground = true;
        t1.Start();
        t2.Start("Hello");
    }

    public static void WithOut()
    {
        // Run
    }

    public static void With(object data)
    {
        string str = data as string; // "Hello"
    }
}

Sleep vs Wait

  • Sleep 基于时间,不需要别的线程唤醒,到点自动恢复。可以放在任何位置。
    • Sleep 期间线程不会释放对象的使用权或锁。在同步方法 / 同步块里 sleep,其它线程仍然访问不到。
  • Wait 让当前线程暂时放弃对象使用权进入等待。必须放在同步方法 / 同步块里。
    • Wait 会释放锁。
    • 唤醒方式:Monitor.Pulse()Monitor.PulseAll()

线程池

线程的创建和销毁有额外开销。线程池在后台维护一组复用的线程、对刚加入的任务排队,省掉了反复创建销毁的成本。

class Program
{
    static void Main(string[] args)
    {
        ThreadPool.QueueUserWorkItem(TestMethod, "Hello");
        Console.ReadKey();
    }

    public static void TestMethod(object data)
    {
        string str = data as string;
        Console.WriteLine(str);
    }
}

垃圾回收

JavaScript 的 GC

可达性(reachability)模型:从 Root 出发自上而下扫,所有 Root 无法到达的对象都可以被回收。相比”引用计数”有几个优势:

  • 能处理相互引用的对象。
  • 能整体剔除一个”孤岛”——一组互相引用但与外部无连通的对象——而无需逐个判断。

缺点是每次都要从头扫一遍。优化手段:

  • 分代收集(generational collection):把对象按代分。老生代对象的清理频率更低。
  • 增量收集:把扫描切成片,分散在多次运行的间隔里执行。
  • 闲时收集:在空闲时段触发。

C# 还有一个 JS 没有的优化是并行回收

C# 的 GC

引用追踪:变量没有任何引用时即可被回收,GC 在合适的时机触发。

GC 无法回收非托管资源(Unmanaged Resources)——例如文件句柄、原生指针等。非托管资源通常实现 IDisposable 接口,配合 using 语句释放。

手动触发:GC.Collect()

非托管资源的回收模式:

  • 继承 IDisposable
  • 实现 Dispose(),在其中释放托管和非托管资源,并把对象本身从 GC 中移除。
  • 实现析构函数,作为兜底再次释放非托管资源。

TypeScript

函数重载的类型定义

和 Java 一样,直接声明多个同名函数、不同参数:

on(event: "close", listener: () => void): this;
on(event: "data", listener: (chunk: any) => void): this;
on(event: "end", listener: () => void): this;

自引用的递归类型

直接写 type T = ... T ... 编译器会拒绝。但通过外部传入泛型参数代替自身就能实现,因为泛型在使用前已经编译好了。

给函数加上 key/value 约束

const func = (obj, key) => obj[key].toString();

let x = { a: 1, b: 2, c: 3, d: 4 };
getProperty(x, "a"); // OK
getProperty(x, "m"); // 期望报错

可以用 K extends keyof T 把 key 约束到 obj 的实际属性集合:

const getProperty = <T, K extends keyof T>(obj: T, key: K): T[K] => obj[key];

按事件名约束回调签名

.d.ts 里可以写很多 overload:

on(event: "close", listener: () => void): this;
on(event: "data", listener: (chunk: any) => void): this;
on(event: "end", listener: () => void): this;
on(event: "readable", listener: () => void): this;
on(event: "error", listener: (err: Error) => void): this;
on(event: string | symbol, listener: (...args: any[]) => void): this;

例子来自 @types/node 里的 stream

更现代的写法是用 extendskeyofA[T] 组合表达:

type Listeners = {
    close: () => void;
    data: (chunk: any) => void;
    // ...
};

const on = <T extends keyof Listeners>(event: T, listener: Listeners[T]) => {
    // ...
};

Angular 2+

组件 / 指令的生命周期

适用于组件和指令:

  • ngOnChanges:当 Angular 设置或重设数据绑定属性时触发。
  • ngOnInit:在第一次 ngOnChanges 之后,初始化组件 / 指令。最常用于从后端拉取模板需要的数据。
  • ngDoCheck:每次变更检测运行时触发,用来在 Angular 上下文变化时手动响应。
  • ngOnDestroy:销毁前清理,比如取消订阅、解除事件处理,防止内存泄漏。

只适用于指令:

  • ngAfterContentInit:组件投影内容初始化完成。
  • ngAfterContentChecked:Angular 检查完投影内容的绑定之后。
  • ngAfterViewInit:Angular 创建组件视图之后。
  • ngAfterViewChecked:Angular 检查完组件视图的绑定之后。

调用顺序:

ngOnChanges → ngOnInit → ngDoCheck → ngAfterContentInit
→ ngAfterContentChecked → ngAfterViewInit → ngAfterViewChecked → ngOnDestroy

依赖注入

  • 服务上加 @Injectable()
  • 组件上加 @Component

依赖注入的本质是控制反转(IoC):应用接口和实现解耦,接口不变的前提下实现可以随意替换。这对测试非常有用。

注册 Provider 时 useClass vs useExisting 的区别:useExisting 会复用同一个已存在的实例(单例),useClass 每次会创建新实例。

变更检测

默认是自上而下:当某个组件发生变化时,它下面所有子组件依次被检查。这是 Default 模式。

OnPush 模式下,当组件的 @Input 没有变化时,整个子树的检查会被跳过。

changeDetection: ChangeDetectionStrategy.OnPush;

detachreattach:把组件从变更检测队列里手动摘下来,再在合适的时机加回去。适用于”自身频繁变化但需要手动控制更新时机”的组件。

constructor(private ref: ChangeDetectorRef) {}

// 摘下
this.ref.detach();
// ...做事...
// 加回
this.ref.reattach();
返回首页