Skip to content

JS 的符号绑定

如果 export 一个变量(使用 let 声明),那么再 import 到其他模块中,这时会发现这个变量是无法在其他模块中进行更改的,因为会被转换为常量。
但是如果在这个变量的同一模块中有其他导出的函数,其作用是去更改该变量,则可以在其他模块中调用该函数来修改这个变量,例如:

ts
export let a = 1;
export const increase = () => {
  a += 1;
};

在其他模块中访问 a 会访问到该模块内部定义的这个 a(而不是复制后重新赋值的变量 a),但是无法直接进行修改(例如a += 1),却可以通过increase进行修改,所以为了预防这种情况,导出的值必须是常量。

而上面提到的导入的变量 a 和导出的 a 是同一个变量,而非和解构时一样,会重新创建一个同名变量并赋值,这就是 ESM 的具名导出特性。

浏览器的 ESM 解析过程

【ESModule 的工作原理【渡一教育】】

静态导入解析

在解析 HTML 文件时,遇到了 script 标签,就会开始执行以下过程:

  1. 静态导入解析:
    1. 将相对地址转换为绝对地址,进行记录,下载 js 文件;
    2. 得到 js 文件后,解析 import 语句,下载对应的 js 文件(注意相同地址的 js 文件不会重复下载);
    3. 递归执行步骤 2,得到所有从 script 标签的内容开始递归依赖的 js 文件;
    4. 从入口文件开始一行一行执行 js 文件,遇到 import 语句就进入到对应的 js 文件进行执行;
    5. 执行的过程中如果遇到 export 则会对该文件建立一个映射,记录其导出内容,以方便后续其他模块使用相同的导出内容;
  2. 动态导入解析:
    1. 如果在执行过程中遇到动态导入语句,则也会进行上面相同的依赖分析,建立相应的导出影射;

TS 类型——去除对象类型中的所有必选值

【实现 GetOptionals【渡一教育】

ts
type GetOptional<T> = {
  [P in keyof T as T[P] extends Required<T>[P] ? never : P]: T[P];
};

type Test = {
  a: 1;
  b: 2;
  c?: 3;
  d?: 4;
};

type TestOptional = GetOptional<Test>;
// { c?: 3; d?: 4; }

TS 类型——方法可填参数根据对象属性进行限定

【从字段到函数的推导【渡一教育】】

ts
type Watcher<T> = {
  on<K extends keyof T & string>(
    event: `${K}Changed`,
    listener: (oldValue: T[K], newValue: T[K]) => void,
  );
};

declare function watch<T>(obj: T): Watcher<T>;

const obj = { a: 1, b: 2 };

// on方法的参数类型会根据对象的属性动态生成
watch(obj).on('aChanged', (oldValue, newValue) => {
  console.log(oldValue, newValue);
});

将任务放在微队列中执行

【模拟微队列【渡一教育】】
实现一个方法,其参数为一个函数,执行后会将这个函数放到微队列中执行。这个方法可以在 Promise.then 方法中用到。
解决的关键在于考虑多种不同的执行环境,实现如下:

ts
function runMicroTask(func) {
  // 如果有Promise,就使用Promise.resolve
  if (typeof Promise !== 'undefined') {
    Promise.resolve().then(func);
    return;
  }

  // 如果有MutationObserver,就使用MutationObserver
  if (typeof MutationObserver !== 'undefined') {
    const observer = new MutationObserver(func);
    const textNode = document.createTextNode('1');
    observer.observe(textNode, {
      characterData: true,
    });
    textNode.data = '2';
    return;
  }

  // 如果有process.nextTick,就使用process.nextTick(一般在Node.js环境下)
  if (process && process.nextTick) {
    process.nextTick(func);
    return;
  }

  // 如果以上都不支持,就使用setTimeout
  setTimeout(func, 0);
}

NPM 的对等依赖问题

在进行一些插件开发时,通常会指定项目中需要某个包的对应版本,这时候就会在package.json中加上peerDependencies这个选项用于限制,例如:

json
{
  "peerDependencies": {
    "vue": "2.0.0"
  }
}

这时,如果使用了该插件,就会限制项目中使用的 vue 为2.0.0版本,如果这时候使用的 vue 版本对不上,则 npm 可能会出现ERESOLVE unable to resolve dependency tree的报错。
npm 本来在 v7 之前的版本是不对这种问题进行报错的,不过后续的版本中就会进行处理了,如果要让 npm 忽略这种问题(在确认了这个包可用于当前项目后),可以在npm i时添加--legacy-peer-deps从而按照旧版本的方式进行处理。