【TS 演化史 -- 14】拼写校订和动态导入表达式

做者:Marius Schulz
译者:前端小智
来源: https://mariusschulz.com/
点赞再看,养成习惯

本文 GitHub https://github.com/qq44924588... 上已经收录,更多往期高赞文章的分类,也整理了不少个人文档,和教程资料。欢迎Star和完善,你们面试能够参照考点复习,但愿咱们一块儿有点东西。html

TypeScript 2.4 为标识符实现了拼写纠正机制。即便我们稍微拼错了一个变量、属性或函数名,TypeScript 在不少状况下均可以提示正确的拼写。前端

拼写更正

假设我们想要调用window.location.reload()来从新加载当前页面。但不当心把location写成了locatoin或其余一些拼写错误,TypeScript 会提示正确的拼写并提供快速修复。jquery

clipboard.png

此更正机制对于一般拼写错误的名称特别有用。 以单词"referrer"为例,对于
document.referrer 我们有时候会写成以下的错误拼写:webpack

  • document.referrerer
  • document.referrawr
  • document.refferrrr

TypeScript 将识别全部这些拼写错误,并提示document.referrer为正确的拼写。 它甚至还能够提供如下几种选择:git

  • document.referrerer
  • document.referrawr
  • document.refferrrr

固然,若是我们只是输入document.ref,而后按TABENTER键让TypeScript为我们补全,则不须要拼写建议,可是若是本身快速输入整个属性名称,则可能会拼错。github

编辑距离 (Levenshtein Distance算法)

在内部,TypeScript 计算拼写错误的名称和程序中该位置可用的名称列表中每一个候选项之间的编辑距离。最佳匹配后(若是有的话)将做为拼写提示返回。web

该算法在 TypeScript 编译器的checker.ts文件中的getSpellingSuggestionForName函数中实现,以下所示面试

/**
 * Given a name and a list of symbols whose names are *not* equal to the name, return a spelling suggestion if there is one that is close enough.
 * Names less than length 3 only check for case-insensitive equality, not levenshtein distance.
 *
 * If there is a candidate that's the same except for case, return that.
 * If there is a candidate that's within one edit of the name, return that.
 * Otherwise, return the candidate with the smallest Levenshtein distance,
 *    except for candidates:
 *      * With no name
 *      * Whose meaning doesn't match the `meaning` parameter.
 *      * Whose length differs from the target name by more than 0.34 of the length of the name.
 *      * Whose levenshtein distance is more than 0.4 of the length of the name
 *        (0.4 allows 1 substitution/transposition for every 5 characters,
 *         and 1 insertion/deletion at 3 characters)
 */
function getSpellingSuggestionForName(name: string, symbols: Symbol[], meaning: SymbolFlags): Symbol | undefined {
    const maximumLengthDifference = Math.min(2, Math.floor(name.length * 0.34));
    let bestDistance = Math.floor(name.length * 0.4) + 1; // If the best result isn't better than this, don't bother.
    let bestCandidate: Symbol | undefined;
    let justCheckExactMatches = false;
    const nameLowerCase = name.toLowerCase();
    for (const candidate of symbols) {
        const candidateName = symbolName(candidate);
        if (!(candidate.flags & meaning && Math.abs(candidateName.length - nameLowerCase.length) <= maximumLengthDifference)) {
            continue;
        }
        const candidateNameLowerCase = candidateName.toLowerCase();
        if (candidateNameLowerCase === nameLowerCase) {
            return candidate;
        }
        if (justCheckExactMatches) {
            continue;
        }
        if (candidateName.length < 3) {
            // Don't bother, user would have noticed a 2-character name having an extra character
            continue;
        }
        // Only care about a result better than the best so far.
        const distance = levenshteinWithMax(nameLowerCase, candidateNameLowerCase, bestDistance - 1);
        if (distance === undefined) {
            continue;
        }
        if (distance < 3) {
            justCheckExactMatches = true;
            bestCandidate = candidate;
        }
        else {
            Debug.assert(distance < bestDistance); // Else `levenshteinWithMax` should return undefined
            bestDistance = distance;
            bestCandidate = candidate;
        }
    }
    return bestCandidate;
}

getSpellingSuggestionForName使用大量试探法来产生合理的拼写建议,该建议既不太严格也不太宽松,这是一个有趣的平衡点!算法

编辑距离 (Levenshtein Distance算法)
字符串的编辑距离,又称为Levenshtein距离,由俄罗斯的数学家Vladimir Levenshtein在1965年提出。是指利用字符操做,把字符串A转换成字符串B所须要的最少操做数。其中,字符操做包括:typescript

  • 删除一个字符
  • 插入一个字符
  • 修改一个字符

例如对于字符串"if"和"iff",能够经过插入一个'f'或者删除一个'f'来达到目的。

通常来讲,两个字符串的编辑距离越小,则它们越类似。若是两个字符串相等,则它们的编辑距离(为了方便,本文后续出现的“距离”,若是没有特别说明,则默认为“编辑距离”)为0(不须要任何操做)。不难分析出,两个字符串的编辑距离确定不超过它们的最大长度(能够经过先把短串的每一位都修改为长串对应位置的字符,而后插入长串中的剩下字符)。

动态导入表达式

TypeScript 2.4 添加了对动态import()表达式的支持,容许用户在程序的任何位置异步地请求某个模块。

静态导入模块

我们先从静态导入模块开始,而后看看我们须要动态导入的状况。假设我们已经为一些客户端小部件编写了一个widget.ts模块:

import * as $ from "jquery";

export function render(container: HTMLElement) {
  $(container).text("Hello, World!");
}

我们的小部件须要 jQuery,所以从jquery npm包中导入$。 请注意,我们在第1行中使用的是彻底静态的导入声明,而不是动态的import()表达式。

如今,我们切换到main.ts模块,并假设我们要将小部件呈现到特定的<div>容器中。 若是打到 DOM 刚渲染,不然不渲染。

import * as widget from "./widget";

function renderWidget() {
  const container = document.getElementById("widget");
  if (container !== null) {
    widget.render(container);
  }
}

renderWidget();

若是如今使用webpackRollup之类的工具将main.ts做为输入模块捆绑应用程序,则生成的JS 捆绑包(处于未缩小状态)的长度超过10,000行。 这是由于在widget.ts模块中,须要要导入很大的jquery npm 包。

问题在于,即便不渲染该窗口小部件,我们也要导入其窗口小部件及其全部依赖项。 新用户第一次打开我们的Web应用程序时,其浏览器必须下载并解析大量无效代码。 这对于具备不稳定网络链接,低带宽和有限处理能力的移动设备尤为不利。

接着来看看动态的 import() 如何解决这个问题。

动态导入模块

更好的方法是仅在须要时导入小部件模块。可是,ES6 导入声明是彻底静态的,必须位于文件的顶层,这意味着我们不能将它们嵌套在if语句中,以便有条件地导入模块。这就是动态import()出现的缘由。

main.ts模块中,删除文件顶部的import声明,并使用import()表达式动态加载小部件,但前提是我们确实找到了小部件容器:

function renderWidget() {
  const container = document.getElementById("widget");
  if (container !== null) {
    import("./widget").then(widget => {
      widget.render(container);
    });
  }
}

renderWidget();

因为按需获取 ECMAScript 模块是异步操做,所以import()表达式始终返回promise

将 await 运算符与 import() 一块儿使用

进行一些重构,以使renderWidget函数的嵌套更少,从而更易于阅读。 由于import()返回一个普通的ES2015 Promise(具备.then()方法),因此我们可使用await运算符来等待Promise解析:

async function renderWidget() {
  const container = document.getElementById("widget");
  if (container !== null) {
    const widget = await import("./widget");
    widget.render(container);
  }
}

renderWidget();

清晰明了,不要忘记经过在其声明中添加async关键字来使renderWidget函数异步化。

针对各类模块系统

TypeScript 编译器支持各类 JS 模块系统,例如 ES2015,CommonJS 或 AMD。 根据目标模块系统的不一样,为 import() 表达式生成的 JS 代码将大不相同。

若是我们使用--module esnext编译我们的 TypeScript 应用程序,将生成如下 JS 代码。 它几乎与咱们本身编写的代码相同:

"use strict";
function renderWidget() {
  var container = document.getElementById("widget");
  if (container !== null) {
    var widget = import("./widget").then(function(widget) {
      widget.render(container);
    });
  }
}
renderWidget();

注意import()表达式没有以任何方式进行转换。若是我们在这个模块中使用了任何importexport声明,那么它们也不会受到影响。

将此代码与如下代码进行比较,当我们使用--module commonjs编译应用程序时,会生成如下代码(为了便于阅读,还有一些换行符):

"use strict";
function renderWidget() {
  var container = document.getElementById("widget");
  if (container !== null) {
    var widget = Promise.resolve()
      .then(function() {
        return require("./widget");
      })
      .then(function(widget) {
        widget.render(container);
      });
  }
}
renderWidget();

CommonJS 对于 Node 应用程序将是一个不错的选择。 全部import()表达式都将转换为require()调用,这些调用能够在程序中的任意位置有条件地执行,而没必要事先加载,解析和执行模块。

那么,在使用import()按需延迟加载模块的客户端web应用程序中,应该针对哪一个模块系统呢?我建议将——module esnext与 webpack 的代码分割特性结合使用。检查带有import()和webpack的TypeScript 应用程序的代码分解,以进行演示应用程序设置。


代码部署后可能存在的BUG无法实时知道,过后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给你们推荐一个好用的BUG监控工具 Fundebug

原文:
https://mariusschulz.com/blog...
https://mariusschulz.com/blog...
https://www.tslang.cn/docs/re...


交流

文章每周持续更新,能够微信搜索「 大迁世界 」第一时间阅读和催更(比博客早一到两篇哟),本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,整理了不少个人文档,欢迎Star和完善,你们面试能够参照考点复习,另外关注公众号,后台回复福利,便可看到福利,你懂的。

相关文章
相关标签/搜索