Typescript  export 导出分析-是引用还是值的形式?

1 阅读5分钟

Typescript  export 导出分析-是引用还是值的形式?

在 TypeScript 里,export 导出的内容是否为引用,要依据具体导出的内容类型来判断。

 

  1. 导出原始类型(如数字、字符串、布尔值)

原始类型以值的形式导出,并非引用。在导出原始类型时,导出的是值的副本,而非引用。

// 导出原始类型

export const num = 10;

// 导入原始类型

import { num } from './module';

// 这里的 num 是原始值 10 的副本

 

  1. 导出const 对象和数组

如果用 export const 导出,导入的绑定是只读的(不能直接 myObj = {}),虽然不能重新赋值,但可以修改对象属性。

当你导出一个对象或者数组时,实际上导出的是对该对象或数组的引用。这意味着导入模块修改这个对象或数组时,会影响到导出模块中的原始对象或数组。

 

// 导出对象

export const person = { name: 'Alice', age: 25 };

// 导入对象

import { person } from './module';

person.age = 26;

// 这里修改的是同一个对象,导出模块中的 person 对象的 age 也会变为 26

TypeScript/ES 模块的这种设计使得状态共享和模块间通信更加灵活高效。

 

3. 导出 let 对象

在 TypeScript 中,使用 export let 导出对象时,导出的是引用(reference),而不是副本。这与 JavaScript 的 ES 模块规范一致。

导入方获取的是对原始对象的引用,任何修改都会反映在所有导入该对象的地方。由于是 let 声明,原始模块可以重新赋值,而导入方会看到新值。如果导出的是对象,修改其属性会在所有导入方可见

// module.ts

export let myObj = { value: 1 };

export function updateObj() {

myObj.value = 2; // 修改对象属性

}

export function replaceObj() {

myObj = { value: 3 }; // 重新赋值(因为是 let)

}

// main.ts

import { myObj, updateObj, replaceObj } from './module';

console.log(myObj); // { value: 1 }

updateObj();

console.log(myObj); // { value: 2 } - 属性修改对所有导入方可见

replaceObj();

console.log(myObj); // { value: 3 } - 重新赋值也可见(因为是 let 导出)

 

 

  1. 导出函数

函数同样是以引用的形式导出的。函数导出后,导入模块使用的是同一个函数实例。

// 导出函数

export function greet() {

return 'Hello!';

}

// 导入函数

import { greet } from './module';

// 这里调用的是同一个函数实例

 

  1. 1.导出class类

在 TypeScript 里,导出类定义是按引用的形式进行的。当把一个类导出并在其他模块中导入时,导入的是同一个类定义。这意味着,无论是在导出模块还是导入模块里对类进行静态属性修改、添加方法等操作,都会影响到这个类的所有引用。

以下是示例代码:

// exportClass.ts

// 定义一个类并导出

export class ExampleClass {

static staticProperty = 'Initial value';

constructor() {}

public instanceMethod() {

return 'This is an instance method.';

}

}

// importClass.ts

// 导入类

import { ExampleClass } from './exportClass';

// 修改类的静态属性

ExampleClass.staticProperty = 'Modified value';

// 创建实例

const exampleInstance = new ExampleClass();

console.log(exampleInstance.instanceMethod());

console.log(ExampleClass.staticProperty);

 

在上述代码中,ExampleClass 被导出后在另一个模块导入,修改其静态属性 staticProperty 会影响到类的所有引用。

5.2. 类实例化的情况

虽然类是按引用导出,但每次实例化类时都会创建一个新的对象实例。不同的实例之间是相互独立的,对一个实例的属性或方法进行修改,不会影响到其他实例。

// 创建两个不同的实例

const instance1 = new ExampleClass();

const instance2 = new ExampleClass();

// 修改 instance1 的属性不会影响 instance2

 

// 如果是值拷贝(实际不是这样):

let exportedObj = { value: 1 };

let importedObj = exportedObj; // 实际行为类似这样,但模块系统有更多约束

// 修改会互相影响

exportedObj.value = 2;

console.log(importedObj.value); // 2

总结来说,原始类型是按值导出,对象、数组和函数则是按引用导出。TypeScript 中导出类是按引用形式,即导出的是类定义的引用。不过,每次实例化类时会创建独立的对象实例。

 

  1. 如何设置:属性是只读的

虽然类是按引用导出,但每次实例化类时都会创建一个新的对象实例。不同的实例之间是相互独立的,对一个实例的属性或方法进行修改,不会影响到其他实例。

但如果你希望导出的对象的某些属性是只读的(即导入方不能修改),有几种方法可以实现:

在导出对象的类型中标记某些属性为 readonly,这样导入方就无法修改这些属性。

方法 1:使用 readonly 修饰符

在导出对象的类型中标记某些属性为 readonly,这样导入方就无法修改这些属性。

 

// module.ts

export interface MyObject {

readonly id: number; // 只读属性

name: string; // 可修改属性

}

export const myObj: MyObject = {

id: 1,

name: "Alice"

};

// main.ts

import { myObj } from './module';

myObj.name = "Bob"; // ✅ 允许修改

myObj.id = 2; // ❌ 编译错误:无法修改只读属性

方法 2:使用 Readonly 或 Readonly<Partial>

如果你希望整个对象的所有属性都是只读的,可以使用 Readonly 泛型。

// module.ts

export interface MyObject {

id: number;

name: string;

}

export const myObj: Readonly = {

id: 1,

name: "Alice"

};

// main.ts

import { myObj } from './module';

myObj.name = "Bob"; // ❌ 编译错误:所有属性都是只读的

myObj.id = 2; // ❌ 编译错误

方法 3:使用 Object.freeze()(运行时只读)

如果你不仅想在编译时限制修改,还想在运行时阻止修改,可以使用 Object.freeze()。

// module.ts

export const myObj = Object.freeze({

id: 1,

name: "Alice"

});

// main.ts

import { myObj } from './module';

myObj.name = "Bob"; // ❌ 运行时静默失败(严格模式下会报错)

myObj.id = 2; // ❌ 同上

注意:Object.freeze() 是浅冻结,如果对象包含嵌套对象,内部对象仍然可以修改。如果需要深冻结,可以使用 deep-freeze 等库。

 

总结

方法编译时检查运行时保护适用场景readonly 修饰符✅❌只需要 TS 类型检查Readonly✅❌整个对象只读Object.freeze()❌✅需要运行时保护Readonly + Object.freeze()✅✅最严格保护

如果你希望:

仅限制 TypeScript 编译时修改 → 用 readonly 或 Readonly

确保运行时也不能修改 → 用 Object.freeze() 遥运德 2