跳到主要内容

核心类型系统✅

本主题涵盖TypeScript的核心类型系统,包括基础类型、联合类型、类型断言、类型守卫、类型兼容性等面试高频考点。

TypeScript中有哪些基础类型,各自的作用是什么?

答案

核心类型分类:

TypeScript的基础类型可以分为以下几类:

  • 原始类型: stringnumberbooleansymbolbigint
  • 特殊类型: anyunknownnevervoid
  • 空值类型: nullundefined
  • 对象类型: object{} 字面量类型

示例说明:

// TypeScript基础类型示例 - Node.js环境展示

// 1. 原始类型展示
const demoBasicTypes = () => {
  console.log('=== 原始类型示例 ===');
  
  const userName = "TypeScript";
  const version = 4.5;
  const isStrict = true;
  const userId = Symbol("userId");
  const bigNumber = 123n;
  
  console.log('userName:', userName, typeof userName);
  console.log('version:', version, typeof version);
  console.log('isStrict:', isStrict, typeof isStrict);
  console.log('userId:', userId, typeof userId);
  console.log('bigNumber:', bigNumber, typeof bigNumber);
};

// 2. 特殊类型演示
const demoSpecialTypes = () => {
  console.log('\n=== 特殊类型示例 ===');
  
  // any类型 - 可以是任何值
  let anything = "can be anything";
  console.log('anything (string):', anything);
  anything = 42;
  console.log('anything (number):', anything);
  anything = true;
  console.log('anything (boolean):', anything);
  
  // unknown类型 - 类型安全的any
  let uncertain = "type-safe any";
  console.log('uncertain:', uncertain);
  
  // 类型检查示例
  if (typeof uncertain === 'string') {
    console.log('uncertain length:', uncertain.length);
  }
};

// 3. null和undefined演示
const demoNullish = () => {
  console.log('\n=== null和undefined示例 ===');
  
  let empty = null;
  let notSet = undefined;
  
  console.log('empty:', empty, typeof empty);
  console.log('notSet:', notSet, typeof notSet);
  console.log('empty == notSet:', empty == notSet);
  console.log('empty === notSet:', empty === notSet);
};

// 4. 类型检查函数
const processValue = (value) => {
  console.log('\n=== 类型检查示例 ===');
  console.log('Processing value:', value);
  
  if (typeof value === 'string') {
    const result = value.toUpperCase();
    console.log('String result:', result);
    return result;
  } else if (typeof value === 'number') {
    const result = value.toString();
    console.log('Number result:', result);
    return result;
  } else {
    console.log('Unknown type:', typeof value);
    return String(value);
  }
};

// 执行所有示例
console.log('🚀 TypeScript基础类型示例');
demoBasicTypes();
demoSpecialTypes();
demoNullish();

// 测试类型检查函数
processValue("hello world");
processValue(42);
processValue(true);

面试官视角:

该题考察TypeScript类型系统基础,通过此题可以进一步探讨:

  • 要点清单: 能说出5-6种基础类型及其用途;理解原始类型vs对象类型区别;知道特殊类型的应用场景
  • 加分项: 提到symbolbigint新类型;解释{}object的区别;理解类型层次关系
  • 常见失误: 混淆nullundefined;不理解never类型的意义;把any当作默认选择

延伸阅读:

any、never、unknown、null、undefined和void有什么区别?

答案

核心概念:

每种特殊类型都有明确的使用场景和安全级别:

  • any: 类型检查的"逃生舱",绕过所有类型检查
  • unknown: 类型安全的any,使用前必须类型缩减
  • never: 永远不存在值的类型,常用于穷尽检查
  • void: 无返回值函数的返回类型
  • null/undefined: 明确的空值类型

示例说明:

// TypeScript特殊类型对比示例

console.log('🔍 TypeScript特殊类型深度对比');

// 1. any类型 - 最不安全的类型
const demoAnyType = () => {
  console.log('\n=== any类型示例 ===');
  
  let anything = "hello";
  console.log('anything (初始):', anything);
  
  // any可以做任何操作,但可能运行时报错
  try {
    console.log('anything.foo.bar:');
    // anything.foo.bar; // 这会在运行时报错,但编译时通过
  } catch (error) {
    console.log('❌ 运行时错误:', error.message);
  }
  
  anything = 42;
  console.log('anything (重新赋值):', anything);
  
  anything = { name: "test", value: 123 };
  console.log('anything (对象):', anything);
};

// 2. unknown类型 - 类型安全的any
const demoUnknownType = () => {
  console.log('\n=== unknown类型示例 ===');
  
  let uncertain = "hello";
  console.log('uncertain:', uncertain);
  
  // unknown必须先进行类型检查
  if (typeof uncertain === 'string') {
    console.log('✅ 类型安全访问 length:', uncertain.length);
  }
  
  uncertain = 42;
  if (typeof uncertain === 'number') {
    console.log('✅ 类型安全访问 toFixed:', uncertain.toFixed(2));
  }
  
  uncertain = { name: "test" };
  if (typeof uncertain === 'object' && uncertain !== null && 'name' in uncertain) {
    console.log('✅ 类型安全访问 name:', uncertain.name);
  }
};

// 3. never类型示例
const demoNeverType = () => {
  console.log('\n=== never类型示例 ===');
  
  // never类型在穷尽检查中的应用
  const processStatus = (status) => {
    switch (status) {
      case 'loading':
        return 'Loading...';
      case 'success':
        return 'Success!';
      case 'error':
        return 'Error occurred';
      default:
        // 在TypeScript中,这里的status会是never类型
        console.log('🚨 未处理的状态:', status);
        return 'Unknown status';
    }
  };
  
  console.log('Status loading:', processStatus('loading'));
  console.log('Status success:', processStatus('success'));
  console.log('Status error:', processStatus('error'));
  console.log('Status unknown:', processStatus('unknown'));
};

// 4. void类型示例
const demoVoidType = () => {
  console.log('\n=== void类型示例 ===');
  
  // void表示没有返回值的函数
  const logAction = () => {
    console.log('执行了某个操作');
    // 可以没有return,或者return undefined
  };
  
  const logWithReturn = () => {
    console.log('执行了另一个操作');
    return; // 等价于return undefined
  };
  
  console.log('logAction返回值:', logAction());
  console.log('logWithReturn返回值:', logWithReturn());
};

// 5. null和undefined的区别
const demoNullUndefined = () => {
  console.log('\n=== null vs undefined示例 ===');
  
  let nullValue = null;
  let undefinedValue = undefined;
  
  console.log('nullValue:', nullValue, typeof nullValue);
  console.log('undefinedValue:', undefinedValue, typeof undefinedValue);
  console.log('null == undefined:', null == undefined);
  console.log('null === undefined:', null === undefined);
  
  // 实际应用场景
  const user = {
    name: "Alice",
    avatar: null,        // 明确表示没有头像
    nickname: undefined  // 表示未设置昵称
  };
  
  console.log('用户信息:', user);
  
  if (user.avatar === null) {
    console.log('用户没有设置头像');
  }
  
  if (user.nickname === undefined) {
    console.log('用户没有设置昵称');
  }
};

// 6. 类型守卫示例
const demoTypeGuards = () => {
  console.log('\n=== 类型守卫示例 ===');
  
  const isString = (value) => typeof value === 'string';
  const isNumber = (value) => typeof value === 'number';
  
  const processValue = (value) => {
    if (isString(value)) {
      console.log(`字符串处理: ${value.toUpperCase()}`);
    } else if (isNumber(value)) {
      console.log(`数字处理: ${value.toFixed(2)}`);
    } else {
      console.log(`其他类型: ${typeof value}`);
    }
  };
  
  processValue("hello");
  processValue(3.14159);
  processValue(true);
  processValue(null);
};

// 执行所有示例
demoAnyType();
demoUnknownType();
demoNeverType();
demoVoidType();
demoNullUndefined();
demoTypeGuards();

console.log('\n✨ 特殊类型对比完成!');

面试官视角:

这道题是TypeScript类型安全的核心考点:

  • 要点清单: 理解每种类型的用途;知道unknownany更安全;理解never的防御性编程作用;掌握严格模式下null检查
  • 加分项: 举例说明never在联合类型中被过滤;解释unknown的类型缩减机制;提到strictNullChecks配置
  • 常见失误: 滥用any类型;不理解nevervoid的区别;在严格模式下忽略null检查

延伸阅读:

什么是联合类型?如何使用?

答案

核心概念:

联合类型使用 | 符号表示一个值可以是几种类型中的任意一种,是TypeScript实现类型灵活性的重要机制。

示例说明:

// TypeScript联合类型comprehensive示例

console.log('🔗 TypeScript联合类型全面演示');

// 1. 基础联合类型
const demoBasicUnions = () => {
  console.log('\n=== 基础联合类型 ===');
  
  // 字面量联合类型
  const statuses = ['loading', 'success', 'error'];
  const themes = ['light', 'dark', 'auto'];
  
  console.log('可用状态:', statuses);
  console.log('可用主题:', themes);
  
  // 类型缩减示例
  const processValue = (input) => {
    console.log(`处理输入: ${input} (${typeof input})`);
    
    if (typeof input === 'string') {
      const result = input.toUpperCase();
      console.log(`字符串处理结果: ${result}`);
      return result;
    } else if (typeof input === 'number') {
      const result = input.toString();
      console.log(`数字处理结果: ${result}`);
      return result;
    }
    
    return String(input);
  };
  
  processValue("hello");
  processValue(42);
  processValue(true);
};

// 2. 判别联合类型
const demoDiscriminatedUnions = () => {
  console.log('\n=== 判别联合类型 ===');
  
  // 形状计算示例
  const shapes = [
    { kind: 'circle', radius: 5 },
    { kind: 'rectangle', width: 10, height: 8 },
    { kind: 'triangle', base: 6, height: 4 }
  ];
  
  const getArea = (shape) => {
    switch (shape.kind) {
      case 'circle':
        return Math.PI * shape.radius ** 2;
      case 'rectangle':
        return shape.width * shape.height;
      case 'triangle':
        return (shape.base * shape.height) / 2;
      default:
        console.log('未知形状类型');
        return 0;
    }
  };
  
  shapes.forEach(shape => {
    const area = getArea(shape);
    console.log(`${shape.kind} 面积:`, area.toFixed(2));
  });
};

// 3. 复杂联合类型 - API结果
const demoComplexUnions = () => {
  console.log('\n=== 复杂联合类型 - API结果 ===');
  
  // 模拟不同的API响应
  const apiResults = [
    { success: true, data: { id: 1, name: 'Alice' } },
    { success: false, error: 'User not found', code: 404 },
    { success: true, data: { id: 2, name: 'Bob' } },
    { success: false, error: 'Network error', code: 500 }
  ];
  
  const handleApiResult = (result) => {
    if (result.success) {
      console.log('✅ 成功:', result.data);
      return result.data;
    } else {
      console.log(`❌ 错误 ${result.code}: ${result.error}`);
      return null;
    }
  };
  
  apiResults.forEach((result, index) => {
    console.log(`API请求 ${index + 1}:`);
    handleApiResult(result);
  });
};

// 4. 状态管理中的联合类型
const demoStateManagement = () => {
  console.log('\n=== 状态管理中的联合类型 ===');
  
  const states = [
    { status: 'loading' },
    { status: 'success', data: ['item1', 'item2', 'item3'] },
    { status: 'error', error: 'Failed to fetch data' }
  ];
  
  const renderState = (state) => {
    switch (state.status) {
      case 'loading':
        return '⏳ Loading...';
      case 'success':
        return `✅ Data loaded: ${state.data.join(', ')}`;
      case 'error':
        return `❌ Error: ${state.error}`;
      default:
        return '❓ Unknown state';
    }
  };
  
  states.forEach((state, index) => {
    console.log(`状态 ${index + 1}: ${renderState(state)}`);
  });
};

// 5. 事件处理联合类型
const demoEventUnions = () => {
  console.log('\n=== 事件处理联合类型 ===');
  
  // 模拟不同类型的事件
  const events = [
    { type: 'click', target: 'button', x: 100, y: 200 },
    { type: 'keydown', key: 'Enter', code: 'Enter' },
    { type: 'input', value: 'hello world', target: 'textfield' },
    { type: 'custom', name: 'user-action', data: { userId: 123 } }
  ];
  
  const handleEvent = (event) => {
    switch (event.type) {
      case 'click':
        console.log(`点击事件: ${event.target} at (${event.x}, ${event.y})`);
        break;
      case 'keydown':
        console.log(`按键事件: ${event.key} (${event.code})`);
        break;
      case 'input':
        console.log(`输入事件: "${event.value}" in ${event.target}`);
        break;
      case 'custom':
        console.log(`自定义事件: ${event.name}`, event.data);
        break;
      default:
        console.log('未知事件类型:', event);
    }
  };
  
  events.forEach(event => handleEvent(event));
};

// 6. 类型过滤示例
const demoTypeFiltering = () => {
  console.log('\n=== 类型过滤示例 ===');
  
  const mixedArray = [
    "hello", 42, true, "world", 3.14, false, null, "typescript", 100
  ];
  
  console.log('原始数组:', mixedArray);
  
  // 过滤字符串
  const strings = mixedArray.filter(item => typeof item === 'string');
  console.log('字符串:', strings);
  
  // 过滤数字
  const numbers = mixedArray.filter(item => typeof item === 'number');
  console.log('数字:', numbers);
  
  // 过滤布尔值
  const booleans = mixedArray.filter(item => typeof item === 'boolean');
  console.log('布尔值:', booleans);
  
  // 过滤非空值
  const nonNull = mixedArray.filter(item => item !== null && item !== undefined);
  console.log('非空值:', nonNull);
};

// 执行所有示例
demoBasicUnions();
demoDiscriminatedUnions();
demoComplexUnions();
demoStateManagement();
demoEventUnions();
demoTypeFiltering();

console.log('\n🎉 联合类型演示完成!');

面试官视角:

联合类型是TypeScript实用性的重要体现:

  • 要点清单: 理解联合类型语法;掌握类型缩减机制;能实现判别联合类型;理解交叉类型&的区别
  • 加分项: 提到分布式条件类型;使用never进行穷尽检查;结合泛型使用联合类型
  • 常见失误: 不理解类型缩减;混淆联合类型和交叉类型;忽略穷尽性检查

延伸阅读:

什么是类型断言?有哪些方式?

答案

核心概念:

类型断言是告诉编译器"相信我,我知道这个值的确切类型"的机制。有两种语法:as关键字和尖括号语法。

示例说明:

// TypeScript类型断言comprehensive示例

console.log('🎯 TypeScript类型断言全面演示');

// 1. 基础类型断言
const demoBasicAssertions = () => {
  console.log('\n=== 基础类型断言 ===');
  
  // 模拟unknown类型的值
  let someValue = "this is a string";
  console.log('someValue:', someValue);
  
  // 在JavaScript中,我们无法直接演示as语法
  // 但可以展示类型断言的实际应用场景
  
  // 模拟DOM元素类型断言的效果
  const mockCanvas = {
    tagName: 'CANVAS',
    getContext: (type) => {
      if (type === '2d') {
        return { fillRect: () => console.log('Canvas 2D context created') };
      }
      return null;
    }
  };
  
  console.log('模拟canvas元素:', mockCanvas.tagName);
  const ctx = mockCanvas.getContext('2d');
  if (ctx) {
    ctx.fillRect(); // 模拟canvas操作
  }
};

// 2. 常量断言的效果演示
const demoConstAssertion = () => {
  console.log('\n=== 常量断言效果 ===');
  
  // 普通数组 vs 常量断言效果
  const colors1 = ['red', 'green', 'blue'];
  console.log('普通数组:', colors1);
  console.log('可以修改:', colors1.push('yellow'), colors1);
  
  // 模拟常量断言的效果(实际中会是readonly)
  const colors2 = Object.freeze(['red', 'green', 'blue']);
  console.log('冻结数组(模拟as const):', colors2);
  
  try {
    colors2.push('yellow'); // 这会失败
  } catch (error) {
    console.log('❌ 无法修改冻结数组:', error.message);
  }
  
  // 对象的常量断言效果
  const theme1 = { primary: '#007acc', secondary: '#f1f1f1' };
  console.log('普通对象:', theme1);
  theme1.primary = '#ff0000'; // 可以修改
  console.log('修改后:', theme1);
  
  const theme2 = Object.freeze({ primary: '#007acc', secondary: '#f1f1f1' });
  console.log('冻结对象(模拟as const):', theme2);
  
  try {
    theme2.primary = '#ff0000'; // 这会静默失败或在严格模式下抛出错误
  } catch (error) {
    console.log('❌ 无法修改冻结对象:', error.message);
  }
  
  console.log('冻结对象依然是:', theme2);
};

// 3. API响应类型断言场景
const demoAPIResponseAssertion = () => {
  console.log('\n=== API响应类型断言 ===');
  
  // 模拟API响应
  const mockApiResponse = {
    id: 1,
    name: "Alice",
    email: "alice@example.com"
  };
  
  // 类型验证函数(模拟类型守卫)
  const isUser = (obj) => {
    return typeof obj === 'object' &&
           obj !== null &&
           typeof obj.id === 'number' &&
           typeof obj.name === 'string' &&
           typeof obj.email === 'string';
  };
  
  console.log('API响应:', mockApiResponse);
  
  if (isUser(mockApiResponse)) {
    console.log('✅ 验证通过,这是一个用户对象');
    console.log(`用户: ${mockApiResponse.name} (${mockApiResponse.email})`);
  } else {
    console.log('❌ 验证失败,不是有效的用户对象');
  }
  
  // 演示错误的API响应
  const invalidResponse = { name: "Bob" }; // 缺少必要字段
  console.log('无效响应:', invalidResponse);
  
  if (isUser(invalidResponse)) {
    console.log('✅ 验证通过');
  } else {
    console.log('❌ 验证失败,缺少必要字段');
  }
};

// 4. 非空断言的应用场景
const demoNonNullAssertion = () => {
  console.log('\n=== 非空断言应用 ===');
  
  // 模拟可能为undefined的值
  const processInput = (input) => {
    if (input === undefined) {
      console.log('⚠️ 输入为undefined,但我们确信它不为空');
      return; // 在实际TypeScript中,这里会用非空断言 input!
    }
    
    console.log(`处理输入: ${input}, 长度: ${input.length}`);
  };
  
  // 安全的处理方式
  const processInputSafe = (input) => {
    if (input !== undefined && input !== null) {
      console.log(`✅ 安全处理输入: ${input}, 长度: ${input.length}`);
    } else {
      console.log('❌ 输入为空,无法处理');
    }
  };
  
  console.log('=== 非空断言风险演示 ===');
  processInput("hello world"); // 正常情况
  processInput(undefined);     // 危险情况
  
  console.log('=== 安全处理方式 ===');
  processInputSafe("hello world");
  processInputSafe(undefined);
  processInputSafe(null);
};

// 5. 类型断言 vs 类型转换
const demoAssertionVsConversion = () => {
  console.log('\n=== 类型断言 vs 类型转换 ===');
  
  const value = "123";
  console.log('原始值:', value, typeof value);
  
  // 类型断言在运行时不改变值(在TypeScript中)
  const assertedNum = value; // 在TS中会是 value as number
  console.log('类型断言结果:', assertedNum, typeof assertedNum);
  console.log('⚠️ 类型断言不改变运行时类型!');
  
  // 类型转换实际改变运行时的值
  const convertedNum = Number(value);
  console.log('类型转换结果:', convertedNum, typeof convertedNum);
  console.log('✅ 类型转换改变了运行时类型!');
  
  // 更多转换示例
  console.log('\n--- 更多转换示例 ---');
  const samples = ["456", "3.14", "abc", ""];
  
  samples.forEach(sample => {
    const converted = Number(sample);
    const parseInted = parseInt(sample);
    const parseFloated = parseFloat(sample);
    
    console.log(`"${sample}" -> Number: ${converted}, parseInt: ${parseInted}, parseFloat: ${parseFloated}`);
  });
};

// 6. 实际应用:安全的类型断言工具
const demoSafeAssertion = () => {
  console.log('\n=== 安全的类型断言工具 ===');
  
  // 安全的类型断言函数
  const safeAssertion = (value, guard, errorMessage) => {
    if (guard(value)) {
      return value;
    }
    throw new Error(errorMessage || 'Type assertion failed');
  };
  
  // 带默认值的断言
  const assertionWithDefault = (value, guard, defaultValue) => {
    return guard(value) ? value : defaultValue;
  };
  
  // 用户类型守卫
  const isUser = (obj) => {
    return typeof obj === 'object' &&
           obj !== null &&
           'id' in obj &&
           'name' in obj &&
           'email' in obj;
  };
  
  const isString = (value) => typeof value === 'string';
  const isNumber = (value) => typeof value === 'number';
  
  // 测试数据
  const testData = [
    { id: 1, name: 'Alice', email: 'alice@example.com' },
    { name: 'Bob' }, // 缺少字段
    "hello world",
    42,
    null
  ];
  
  testData.forEach((data, index) => {
    console.log(`\n测试数据 ${index + 1}:`, data);
    
    try {
      const validUser = safeAssertion(data, isUser, 'Invalid user data');
      console.log('✅ 用户验证通过:', validUser.name);
    } catch (error) {
      console.log('❌ 用户验证失败:', error.message);
    }
    
    // 带默认值的断言
    const stringValue = assertionWithDefault(data, isString, "default string");
    console.log('字符串断言结果:', stringValue);
    
    const numberValue = assertionWithDefault(data, isNumber, 0);
    console.log('数字断言结果:', numberValue);
  });
};

// 执行所有示例
demoBasicAssertions();
demoConstAssertion();
demoAPIResponseAssertion();
demoNonNullAssertion();
demoAssertionVsConversion();
demoSafeAssertion();

console.log('\n🎉 类型断言演示完成!');

面试官视角:

类型断言体现了TypeScript的渐进式特性:

  • 要点清单: 掌握as语法和尖括号语法;理解非空断言操作符;知道常量断言;区分断言和类型转换
  • 加分项: 提到双重断言的使用场景;解释为什么as语法更好;理解断言的运行时成本为零
  • 常见失误: 滥用类型断言绕过类型检查;混淆断言和转换;在严格模式下过度使用非空断言

延伸阅读:

什么是类型守卫?如何实现?

答案

核心概念:

类型守卫是运行时检查,用于在特定的代码块中缩减变量的类型范围,确保类型安全。包括内置守卫和用户自定义守卫。

示例说明:

// typeof类型守卫
function processValue(value: string | number): string {
if (typeof value === 'string') {
return value.toUpperCase(); // value被缩减为string
}
return value.toString(); // value被缩减为number
}

// instanceof类型守卫
class Bird { fly() { console.log('flying'); } }
class Fish { swim() { console.log('swimming'); } }

function move(animal: Bird | Fish) {
if (animal instanceof Bird) {
animal.fly(); // animal被缩减为Bird类型
} else {
animal.swim(); // animal被缩减为Fish类型
}
}

// in操作符类型守卫
interface Rectangle { width: number; height: number; }
interface Circle { radius: number; }

function getArea(shape: Rectangle | Circle): number {
if ('width' in shape) {
return shape.width * shape.height; // shape被缩减为Rectangle
}
return Math.PI * shape.radius ** 2; // shape被缩减为Circle
}

// 用户自定义类型守卫
function isString(value: unknown): value is string {
return typeof value === 'string';
}

function processUnknown(value: unknown) {
if (isString(value)) {
console.log(value.length); // value被缩减为string
}
}

// 复杂类型守卫
interface User { name: string; email: string; }
function isUser(obj: unknown): obj is User {
return typeof obj === 'object' &&
obj !== null &&
'name' in obj &&
'email' in obj &&
typeof (obj as User).name === 'string' &&
typeof (obj as User).email === 'string';
}

// 断言函数(assert函数)
function assertIsNumber(value: unknown): asserts value is number {
if (typeof value !== 'number') {
throw new Error('Expected number');
}
}

function processInput(input: unknown) {
assertIsNumber(input);
console.log(input.toFixed(2)); // input在此处被断言为number
}

面试官视角:

类型守卫是TypeScript实现动态类型安全的核心机制:

  • 要点清单: 理解内置类型守卫(typeof、instanceof、in);能实现自定义类型守卫;理解类型缩减机制;掌握断言函数
  • 加分项: 提到断言函数与类型守卫的区别;实现复杂对象的类型守卫;结合泛型使用类型守卫
  • 常见失误: 类型守卫逻辑不严谨;不理解类型缩减的作用域;混淆类型断言和类型守卫

延伸阅读:

介绍TypeScript中的类型兼容性——逆变、协变、双向协变和不变

答案

核心概念:

类型兼容性描述了类型之间的赋值关系。在TypeScript中,不同的类型位置具有不同的型变特性:

  • 协变: 子类型可以赋值给父类型
  • 逆变: 父类型可以赋值给子类型
  • 双向协变: 父子类型可以相互赋值
  • 不变: 类型必须完全相同才能赋值

示例说明:

// 定义类型层次
interface Animal { name: string; }
interface Dog extends Animal { breed: string; }

// 1. 协变(Covariance)- 数组、返回值
const animals: Animal[] = [];
const dogs: Dog[] = [{ name: 'Buddy', breed: 'Golden' }];

// 数组是协变的
const animalArray: Animal[] = dogs; // ✅ Dog[] 可以赋值给 Animal[]

// 函数返回值是协变的
type AnimalFactory = () => Animal;
type DogFactory = () => Dog;
const dogFactory: DogFactory = () => ({ name: 'Max', breed: 'Husky' });
const animalFactory: AnimalFactory = dogFactory; // ✅ 返回Dog的函数可以赋值给返回Animal的函数

// 2. 逆变(Contravariance)- 严格模式下的函数参数
type AnimalHandler = (animal: Animal) => void;
type DogHandler = (dog: Dog) => void;

const handleAnimal: AnimalHandler = (animal) => console.log(animal.name);
const handleDog: DogHandler = handleAnimal; // ✅ 处理Animal的函数可以处理Dog

// 3. 双向协变(Bivariance)- 默认模式下的函数参数
// 在非严格函数类型检查下,函数参数是双向协变的
function processWithAnimalHandler(handler: AnimalHandler) {}
function processWithDogHandler(handler: DogHandler) {}

// 在默认情况下这些都是允许的(不推荐)
const flexibleHandler1: AnimalHandler = (dog: Dog) => console.log(dog.breed);
const flexibleHandler2: DogHandler = (animal: Animal) => console.log(animal.name);

// 4. 不变(Invariance)- 泛型容器
interface Container<T> {
value: T;
setValue: (value: T) => void;
}

const animalContainer: Container<Animal> = {
value: { name: 'Generic Animal' },
setValue: (animal) => console.log(animal.name)
};

const dogContainer: Container<Dog> = {
value: { name: 'Specific Dog', breed: 'Labrador' },
setValue: (dog) => console.log(`${dog.name} is a ${dog.breed}`)
};

// 泛型容器是不变的
// const container1: Container<Animal> = dogContainer; // ❌ 错误
// const container2: Container<Dog> = animalContainer; // ❌ 错误

// 启用严格函数类型检查的效果
// tsconfig.json: "strictFunctionTypes": true
type StrictAnimalHandler = (animal: Animal) => void;
type StrictDogHandler = (dog: Dog) => void;

const strictAnimalHandler: StrictAnimalHandler = (animal) => console.log(animal.name);
// const strictDogHandler: StrictDogHandler = strictAnimalHandler; // ✅ 逆变
// const invalidHandler: StrictAnimalHandler = (dog: Dog) => {}; // ❌ 严格模式下错误

面试官视角:

类型兼容性是TypeScript高级特性,考察对类型系统的深度理解:

  • 要点清单: 理解四种型变关系;知道数组和返回值的协变特性;理解函数参数在严格模式下的逆变;掌握泛型的不变性
  • 加分项: 解释strictFunctionTypes配置的作用;提到方法和函数的型变差异;理解型变在实际开发中的意义
  • 常见失误: 混淆协变和逆变的概念;不理解双向协变的问题;忽略严格模式对型变的影响

延伸阅读:

is关键字的作用是什么?

答案

核心概念:

is关键字用于创建用户定义的类型守卫函数,它是一种类型谓词,帮助TypeScript在运行时进行类型判断和类型缩减。

示例说明:

// 基础类型守卫
function isString(value: unknown): value is string {
return typeof value === 'string';
}

function processValue(input: unknown) {
if (isString(input)) {
// input在这里被缩减为string类型
console.log(input.toUpperCase());
console.log(input.length);
}
}

// 复杂对象类型守卫
interface User {
id: number;
name: string;
email: string;
}

function isUser(obj: unknown): obj is User {
return typeof obj === 'object' &&
obj !== null &&
'id' in obj &&
'name' in obj &&
'email' in obj &&
typeof (obj as User).id === 'number' &&
typeof (obj as User).name === 'string' &&
typeof (obj as User).email === 'string';
}

// API响应处理
async function fetchUser(id: number): Promise<User | null> {
const response = await fetch(`/api/users/${id}`);
const data = await response.json();

if (isUser(data)) {
return data; // data被确认为User类型
}
return null;
}

// 联合类型过滤
interface Circle { kind: 'circle'; radius: number; }
interface Rectangle { kind: 'rectangle'; width: number; height: number; }
type Shape = Circle | Rectangle;

function isCircle(shape: Shape): shape is Circle {
return shape.kind === 'circle';
}

function processShapes(shapes: Shape[]) {
const circles = shapes.filter(isCircle); // circles的类型是Circle[]
circles.forEach(circle => {
console.log(`Circle area: ${Math.PI * circle.radius ** 2}`);
});
}

// 泛型类型守卫
function isArrayOf<T>(
value: unknown,
guard: (item: unknown) => item is T
): value is T[] {
return Array.isArray(value) && value.every(guard);
}

const data: unknown = ['hello', 'world', 123];

if (isArrayOf(data, isString)) {
// data被缩减为string[]类型
data.forEach(str => console.log(str.toUpperCase()));
}

// 与断言函数的区别
function assertIsString(value: unknown): asserts value is string {
if (typeof value !== 'string') {
throw new Error('Expected string');
}
}

// 使用对比
function example(input: unknown) {
// 类型守卫 - 返回boolean
if (isString(input)) {
console.log(input.length); // input是string
}

// 断言函数 - 抛出异常或继续执行
assertIsString(input);
console.log(input.length); // input被断言为string
}

面试官视角:

is关键字是TypeScript实现类型安全的重要工具:

  • 要点清单: 理解类型谓词的语法;能编写复杂对象的类型守卫;掌握与断言函数的区别;理解类型缩减机制
  • 加分项: 实现泛型类型守卫;结合数组方法使用类型守卫;提到运行时类型验证的重要性
  • 常见失误: 类型守卫函数逻辑不严谨;混淆类型守卫和断言函数;忽略null和undefined的检查

延伸阅读:

typeof、instanceof、in操作符在TypeScript中的作用

答案

核心概念:

这三个操作符都用于运行时类型检查和编译时类型缩减,但适用场景不同:

  • typeof: 检查原始类型和函数
  • instanceof: 检查类实例和原型链
  • in: 检查对象属性存在

示例说明:

// 1. typeof操作符
function processValue(input: string | number | boolean | Function) {
if (typeof input === 'string') {
console.log(input.toUpperCase()); // input缩减为string
} else if (typeof input === 'number') {
console.log(input.toFixed(2)); // input缩减为number
} else if (typeof input === 'boolean') {
console.log(input ? 'true' : 'false'); // input缩减为boolean
} else if (typeof input === 'function') {
input(); // input缩减为Function
}
}

// typeof用于类型查询
const config = {
apiUrl: 'https://api.example.com',
timeout: 5000,
retries: 3
};

type Config = typeof config; // 获取config的类型
// Config = { apiUrl: string; timeout: number; retries: number; }

// 2. instanceof操作符
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
}

class Dog extends Animal {
breed: string;
constructor(name: string, breed: string) {
super(name);
this.breed = breed;
}

bark() {
console.log('Woof!');
}
}

class Cat extends Animal {
meow() {
console.log('Meow!');
}
}

function handlePet(pet: Animal) {
if (pet instanceof Dog) {
pet.bark(); // pet缩减为Dog类型,可以访问bark方法
console.log(`${pet.name} is a ${pet.breed}`);
} else if (pet instanceof Cat) {
pet.meow(); // pet缩减为Cat类型,可以访问meow方法
}

// 对于内置类型
const date = new Date();
if (date instanceof Date) {
console.log(date.getFullYear()); // date是Date类型
}

const arr = [1, 2, 3];
if (arr instanceof Array) {
console.log(arr.length); // arr是Array类型
}
}

// 3. in操作符
interface Rectangle {
width: number;
height: number;
}

interface Circle {
radius: number;
}

type Shape = Rectangle | Circle;

function getArea(shape: Shape): number {
if ('width' in shape) {
// shape缩减为Rectangle类型
return shape.width * shape.height;
} else {
// shape缩减为Circle类型
return Math.PI * shape.radius ** 2;
}
}

// in操作符用于可选属性检查
interface User {
name: string;
email: string;
phone?: string;
}

function displayUser(user: User) {
console.log(`Name: ${user.name}`);
console.log(`Email: ${user.email}`);

if ('phone' in user && user.phone) {
console.log(`Phone: ${user.phone}`);
}
}

// 高级用法:组合使用
function processData(data: unknown) {
// 先检查是否为对象
if (typeof data === 'object' && data !== null) {
// 再检查特定属性
if ('id' in data && 'name' in data) {
console.log('Found user-like object');
}

// 检查是否为特定类的实例
if (data instanceof Error) {
console.log(`Error: ${data.message}`);
}
}
}

// 类型守卫函数中的组合使用
function isUserWithPhone(obj: unknown): obj is User & { phone: string } {
return typeof obj === 'object' &&
obj !== null &&
'name' in obj &&
'email' in obj &&
'phone' in obj &&
typeof (obj as any).name === 'string' &&
typeof (obj as any).email === 'string' &&
typeof (obj as any).phone === 'string';
}

面试官视角:

这三个操作符是TypeScript类型安全的基础:

  • 要点清单: 掌握三个操作符的使用场景;理解类型缩减机制;能组合使用进行复杂判断;了解typeof的类型查询功能
  • 加分项: 提到typeof null === 'object'的JavaScript陷阱;理解instanceof的原型链检查;掌握可选属性的in检查技巧
  • 常见失误: 混淆instanceof和typeof的使用场景;忽略null检查;不理解类型缩减的作用域

延伸阅读:

如何使用unknown类型替代any?

答案

核心概念:

unknown是类型安全的any,它表示任何值,但在使用前必须进行类型检查或类型断言。这强制开发者编写更安全的代码。

示例说明:

// ❌ 不安全的any用法
function processAny(data: any): string {
return data.toString().toUpperCase(); // 可能运行时报错
}

// ✅ 安全的unknown用法
function processUnknown(data: unknown): string {
// 必须先进行类型检查
if (typeof data === 'string') {
return data.toUpperCase();
} else if (typeof data === 'number') {
return data.toString().toUpperCase();
} else if (data && typeof data === 'object' && 'toString' in data) {
return String(data).toUpperCase();
}
return 'UNKNOWN';
}

// API响应处理
interface User {
id: number;
name: string;
email: string;
}

async function fetchUser(id: number): Promise<User | null> {
const response = await fetch(`/api/users/${id}`);
const data: unknown = await response.json(); // 不确定API返回的结构

// 使用类型守卫验证数据
if (isUser(data)) {
return data;
}

return null;
}

function isUser(obj: unknown): obj is User {
return typeof obj === 'object' &&
obj !== null &&
'id' in obj &&
'name' in obj &&
'email' in obj &&
typeof (obj as User).id === 'number' &&
typeof (obj as User).name === 'string' &&
typeof (obj as User).email === 'string';
}

// 错误处理
function handleError(error: unknown): string {
// 检查是否为Error实例
if (error instanceof Error) {
return error.message;
}

// 检查是否为字符串
if (typeof error === 'string') {
return error;
}

// 检查是否有toString方法
if (error && typeof error === 'object' && 'toString' in error) {
return String(error);
}

return 'Unknown error occurred';
}

// JSON解析安全处理
function safeJsonParse(jsonString: string): unknown {
try {
return JSON.parse(jsonString);
} catch {
return null;
}
}

function processJsonData(jsonString: string) {
const data = safeJsonParse(jsonString);

if (data === null) {
console.log('Invalid JSON');
return;
}

// 进一步类型验证
if (typeof data === 'object' && data !== null && 'users' in data) {
const users = (data as any).users;
if (Array.isArray(users)) {
users.forEach(user => {
if (isUser(user)) {
console.log(`User: ${user.name}`);
}
});
}
}
}

// 泛型中使用unknown
function createValidator<T>(validator: (value: unknown) => value is T) {
return (input: unknown): T | null => {
if (validator(input)) {
return input;
}
return null;
};
}

const validateUser = createValidator(isUser);
const validateString = createValidator((value): value is string => typeof value === 'string');

// 事件处理器
function handleEvent(event: unknown) {
// DOM事件检查
if (typeof event === 'object' && event !== null && 'target' in event) {
const domEvent = event as Event;
console.log('DOM event:', domEvent.type);
}

// 自定义事件检查
if (typeof event === 'object' &&
event !== null &&
'type' in event &&
'data' in event) {
const customEvent = event as { type: string; data: unknown };
console.log('Custom event:', customEvent.type);
}
}

// 迁移策略:从any到unknown
// 第一步:将any改为unknown
function processLegacyData(input: unknown /* 原来是any */) {
// 第二步:添加类型检查
if (typeof input === 'object' && input !== null) {
// 第三步:渐进式类型验证
if ('id' in input && typeof (input as any).id === 'number') {
console.log(`Processing item ${(input as any).id}`);
}
}
}

面试官视角:

unknown类型体现了TypeScript渐进式类型安全的理念:

  • 要点清单: 理解unknown与any的区别;掌握类型缩减技巧;能实现安全的API响应处理;了解迁移策略
  • 加分项: 提到unknown在泛型中的应用;实现复杂的类型验证逻辑;理解unknown的性能优势
  • 常见失误: 直接使用类型断言绕过检查;类型守卫逻辑不完整;忽略边界情况处理

延伸阅读:

TypeScript中的类型推断机制是如何工作的?

答案

核心概念:

TypeScript的类型推断通过上下文分析自动推导变量和表达式的类型,减少显式类型注解的需要。主要包括变量推断、返回值推断、上下文推断等。

示例说明:

// 1. 变量类型推断
let name = "TypeScript"; // 推断为string
let version = 4.5; // 推断为number
let isStrict = true; // 推断为boolean

// 数组类型推断
let numbers = [1, 2, 3]; // 推断为number[]
let mixed = [1, "two", true]; // 推断为(string | number | boolean)[]
let empty = []; // 推断为any[]

// 2. 函数返回值推断
function add(a: number, b: number) {
return a + b; // 推断返回类型为number
}

function getUser() {
return {
id: 1,
name: "Alice",
email: "alice@example.com"
}; // 推断返回类型为 { id: number; name: string; email: string; }
}

// 3. 上下文类型推断
interface User {
id: number;
name: string;
onClick: (event: MouseEvent) => void;
}

const user: User = {
id: 1,
name: "Bob",
onClick: (e) => { // e被推断为MouseEvent类型
console.log(e.clientX);
}
};

// 数组方法的上下文推断
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2); // n被推断为number,返回number[]

// 4. 最佳公共类型推断
let values = [1, "hello", true]; // 推断为(string | number | boolean)[]

class Animal { name: string = ""; }
class Dog extends Animal { breed: string = ""; }
class Cat extends Animal { color: string = ""; }

let pets = [new Dog(), new Cat()]; // 推断为(Dog | Cat)[]
let animals: Animal[] = [new Dog(), new Cat()]; // 显式指定为Animal[]

// 5. 泛型类型推断
function identity<T>(arg: T): T {
return arg;
}

let result1 = identity(42); // T推断为number
let result2 = identity("hello"); // T推断为string

// 多个泛型参数推断
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}

let stringNumberPair = pair("age", 25); // 推断为[string, number]

// 6. 条件类型中的推断
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

function getString(): string { return "hello"; }
type StringReturnType = ReturnType<typeof getString>; // 推断为string

// 7. 字面量类型推断
let status = "loading"; // 推断为string
const theme = "dark"; // 推断为"dark"字面量类型

// 使用as const进行字面量推断
let config = {
mode: "development",
port: 3000
} as const; // 推断为 { readonly mode: "development"; readonly port: 3000; }

// 8. 函数参数推断
function processCallback<T>(items: T[], callback: (item: T) => void) {
items.forEach(callback);
}

processCallback([1, 2, 3], (num) => {
console.log(num.toFixed(2)); // num被推断为number
});

// 9. Promise类型推断
async function fetchData() {
const response = await fetch('/api/data');
const data = await response.json(); // 推断为any,需要类型断言
return data;
}

// 更好的Promise推断
async function fetchUser(): Promise<User> {
const response = await fetch('/api/user');
return response.json(); // 返回类型被约束为User
}

// 10. 索引访问类型推断
interface Config {
database: {
host: string;
port: number;
};
cache: {
ttl: number;
};
}

function getConfig<K extends keyof Config>(key: K): Config[K] {
// ... 实现
return {} as Config[K];
}

let dbConfig = getConfig("database"); // 推断为 { host: string; port: number; }

// 11. 映射类型推断
type Partial<T> = {
[P in keyof T]?: T[P];
};

interface Original {
name: string;
age: number;
}

let partial: Partial<Original> = {}; // 推断为 { name?: string; age?: number; }

// 12. 类型推断的限制和提示
// 有时需要显式类型注解来帮助推断
function combine<T>(items: T[]): T[] {
return items.concat(items);
}

// 这种情况推断可能不准确,需要显式指定
let result = combine([]); // T推断为never,通常不是我们想要的
let betterResult = combine<string>([]); // 显式指定T为string

面试官视角:

类型推断是TypeScript开发体验的核心:

  • 要点清单: 理解变量、函数、泛型的推断机制;掌握上下文类型推断;了解最佳公共类型算法;知道as const的作用
  • 加分项: 提到类型推断的性能考量;理解推断算法的限制;掌握复杂泛型场景的推断技巧
  • 常见失误: 过度依赖推断导致类型不明确;忽略上下文推断的作用;不理解字面量类型推断

延伸阅读: