使用 TypeScript
这是你可以通过不编写 JS 来改善 JS 的第一件事. 对于初学者来说, TypeScript(TS)是 JS 的 "已编译" 的超集(在 JS 中运行的所有内容都可以在 TS 中运行).TS 在原生 JS 体验的基础上增加了一个全面的类型静态检查系统. 长期以来, 整个生态系统中对 TS 的支持都不够好, 所以以前我不建议大家使用.
幸运的是, 那些日子已经过去很久了, 大多数框架都开箱即用地支持 TS. 现在我们都在讨论了什么是 TS, 下面我们来谈谈为什么要使用它.
我自己是一名从事了多年开发的 web 前端老程序员, 目前辞职在做自己的 Web 前端私人定制课程, 今年年初我花了一个月整理了一份最适合 2019 年学习的 Web 前端学习干货, 各种框架都有整理, 送给每一位前端小伙伴, 想要获取的可以添加我的 Web 前端交流群 600610151, 即可免费获取.
TypeScript 可以保证类型安全
类型安全性可以确保在编译阶段验证整个代码段中所有类型是否都以正确方式使用. 换句话说, 如果您创建一个数字参数的函数 foo:
- function foo(someNum: number): number {
- return someNum + 5;
- }
函数 foo 只接受 number 类型的参数
正确的例子
console.log(foo(2)); // 打印 "7"
错误的例子
console.log(foo("two")); // 不正确的 TS 代码
除了在代码中添加类型检查的开销之外, 类型安全性的负面影响几乎为零. 另一方面, 它带来了极大的好处, 类型安全性提供了针对常见错误的额外保护, 这对于像 JS 这样有缺陷的语言来说是一种幸运.
TypeScript 让重构大型项目成为可能
重构大型 JS 应用程序可能是一场噩梦. 重构 JS 的最大苦恼是由于它不强制检查函数参数签名. 这意味着绝对不能滥用 JS 函数, 假设, 如果我有一个函数 myAPI 提供给 1000 个不同的调用方使用:
- function myAPI(someNum, someString) {
- if (someNum> 0) {
- leakCredentials();
- } else {
- console.log(someString);
- }
- }
当我改变了一下这个函数签名:
- function myAPI(someString, someNum) {
- if (someNum> 0) {
- leakCredentials();
- } else {
- console.log(someString);
- }
- }
我必须 100%确定使用此功能的每个地方(成千上万个地方), 我都正确地更新了用法. 如果我错过一个, 系统就会崩溃.
如果使用 TS 的话:
修改前
function myAPITS(someNum: number, someString: string) { ... }
修改后
function myAPITS(someString: string, someNum: number) { ... }
如您所见, myAPITS 函数与 JavaScript 对应函数进行了相同的更改. 但是, 此代码不是生成有效的 JavaScript, 而是导致生成无效的 TypeScript, 因为它所使用的成千上万个位置现在出现了错误的提示. 而且由于我们前面讨论的类型安全性, 这数千种情况将阻止编译, 这样你可以从容进行修改重构.
TypeScript 使得团队架构沟通更为顺畅
正确配置 TS 后, 如果不先定义接口和类就很难编写代码. 这提供了一种共享简洁, 可交流的体系结构建议的方法. 在 TS 之前, 存在解决此问题的其他解决方案, 但是没有一个解决方案可以在不使您做额外工作的情况下自动解决. 例如, 如果我想为后端提出新的请求类型, 则可以使用 TS 将以下内容发送给队友.
- interface BasicRequest {
- body: Buffer;
- headers: {
- [header: string]: string | string[] | undefined;
- };
- secret: Shhh;
- }
我必须已经编写代码, 但是现在我可以共享我的增量进度并获得反馈, 而无需花费更多时间. 我不知道 TS 本质上是否比 JS 更不容易出错. 我坚信, 强制开发人员首先定义接口和 API 会产生更好的代码. 总体而言, TS 已发展成为比原生 JS 的更为成熟且更可预测的替代方案. 开发人员绝对仍然需要熟练使用原生 JS, 但是我最近开始的大多数新项目从一开始就是 TS.
使用现代语法
JavaScript 是世界上最流行 (即使不是最多) 的编程语言之一. 您可能希望到现在为止, 大多数人已经知道了数以亿计的人使用 20 多年的语言, 但实际上相反. 最近, 对 JS 进行了许多更改和添加(是的, 我知道, 从技术上讲 ECMAScript), 从根本上改变了开发人员的体验. 作为最近两年才开始写 JS 的人, 我的优势是没有偏见或期望. 这就导致了关于语言的哪些功能要使用和哪些要避免的更加务实的选择.
async 和 await
在长时间内, 异步的基于事件驱动的回调方式是 JS 开发中常规做法:
传统回调
- makeHttpRequest('google.com', function (err, result) {
- if (err) {
- console.log('Oh boy, an error');
- } else {
- console.log(result);
- }
- });
我不会花时间解释上述问题的原因, 为了解决回调问题, 在 JS 中添加了一个新概念 Promises. 使用 Promise, 您可以编写异步逻辑, 同时避免以前困扰基于回调的代码的嵌套问题.
- Promises
- makeHttpRequest('google.com').then(function (result) {
- console.log(result);
- }).catch(function (err) {
- console.log('Oh boy, an error');
- });
与回调相比, Promises 的最大优点是可读性和可链式调用. 尽管 Promise 很棒, 但他们仍有一些不足之处. 对于许多人来说, Promise 的体验仍然让人联想到回调. 具体来说, 开发人员正在寻求 Promise 模型的替代方案. 为了解决这个问题, ECMAScript 委员会决定添加一种新的利用 promise,async 和 await 的方法:
- try {
- const result = await makeHttpRequest('google.com');
- console.log(result);
- } catch (err) {
- console.log('Oh boy, an error');
- }
一个警告是, 你 await 的任何内容都必须声明为 async, 上一示例中 makeHttpRequest 的必须定义成
- async function makeHttpRequest(url) {
- // ...
- }
由于异步功能实际上只是精美的 Promise 包装器, 因此也可以直接等待 Promise. 这也意味着 async/await 代码和 Promise 代码在功能上是等效的. 因此, 可以在不感到内疚的情况下随意使用 async/await.
let 和 const
对于 JS 的大部分存在, 只有一个可变范围限定符: var.var 在处理范围方面有一些非常独特 / 有趣的规则. var 的作用域范围不一致且令人困惑, 并且导致了意外的行为, 因此导致整个 JS 生命周期中的错误. 但是从 ES6 开始, 还有 var 的替代方法: const 和 let. 几乎不再需要使用 var 了, 任何使用 var 的逻辑都可以始终转换为等效的 const 和 let 的代码.
至于何时使用 const 和 let, 我总是从声明所有 const 开始. const 的限制要严格得多且 "固定不变", 这通常会导致更好的代码. 没有大量的 "真实场景" 需要使用 let, 我会说我用 let 声明的 1/20 变量, 其余的都是 const.
我说 const 是 "不变的", 因为它不能以与 C / C++ 中的 const 相同的方式工作. const 对 JavaScript 运行时的含义是, 对该 const 变量的引用永远不会改变. 这并不意味着存储在该引用上的内容将永远不会改变. 对于原始类型(数字, 布尔值等),const 确实会转换为不变性(因为它是单个内存地址). 但是对于所有对象(类, 数组, 字典),const 不能保证不变性.
箭头 =>函数
箭头函数是在 JS 中声明匿名函数的简洁方式, 匿名函数描述未明确命名的函数. 通常, 匿名函数作为回调或事件挂钩传递.
原生匿名函数
- someMethod(1, function () {
- // has no name
- console.log('called');
- });
在大多数情况下, 这种风格没有任何 "错误". 原生匿名函数在作用域方面表现出 "有趣", 这可能会 (或已经) 导致许多意外错误. 借助箭头功能, 我们不必再为此担心. 这是使用箭头功能实现的相同代码:
- someMethod(1, () => {
- // has no name
- console.log('called');
- });
除了更加简洁之外, 箭头功能还具有更多实用的作用域行为. 箭头函数从定义它们的范围继承作用域. 在某些情况下, 箭头函数功能可能更简洁:
- const obj1 = {
- dog: 'woof'
- };
- const obj2 = {
- cat: 'meow'
- };
- const merged = Object.assign({
- }, obj1, obj2);
- console.log(merged) // prints {
- dog: 'woof', cat: 'meow'
- }
我想说清楚, 这不是可变的情况; 仍然存在原生匿名函数 (特别是类方法) 的有效用例. 话虽这么说, 但我发现, 如果始终使用默认箭头功能, 则与默认使用原始匿名函数相比, 您进行的调试要少得多.
展开操作符 ...
提取一个对象的键 / 值对并将它们添加为另一个对象的子代是一种非常常见的事情. 从历史上看, 有几种方法可以做到这一点, 但是所有这些方法都相当笨拙:
- const obj1 = {
- dog: 'woof'
- };
- const obj2 = {
- cat: 'meow'
- };
- const merged = Object.assign({
- }, obj1, obj2);
- console.log(merged) // prints {
- dog: 'woof', cat: 'meow'
- }
这种模式非常普遍, 因此上述方法很快变得乏味. 多亏了展开操作符, 您无需再使用它:
- const obj1 = {
- dog: 'woof'
- };
- const obj2 = {
- cat: 'meow'
- };
- console.log({
- ...obj1, ...obj2
- }); // prints {
- dog: 'woof', cat: 'meow'
- }
而且更棒的是它也能和数组搭配使用:
- const name = 'Ryland';
- const helloString =
- `Hello
- ${name}`;
这个也许不是最重要的 JS 新特性, 但它是我最喜欢的一个.
模板字符串
字符串是最常见的编程结构之一. 这就是为什么它如此尴尬, 以至于在许多语言中仍然很少支持原生声明字符串. 长期以来, JS 属于 "坡脚的字符串" 家族. 但是, 模板字符串的添加使 JS 成为了自己的类别. 模板字符串本身可以方便地解决编写字符串的两个最大问题: 添加动态内容和编写多行的字符串:
- const name = 'Ryland';
- const helloString =
- `Hello
- ${name}`;
对象解构
对象解构是一种从数据集合 (对象, 数组等) 中提取值的方法, 而无需遍历数据或显式访问其键值:
老方法
- [a, b] = [10, 20];
- console.log(a); // prints 10
对象解构
- function animalParty(dogSound, catSound) {
- }
- const myDict = {
- dog: 'woof',
- cat: 'meow',
- };
- const {
- dog, cat
- } = myDict;
- animalParty(dog, cat);
你还可以在函数参数签名中进行解构:
- function animalParty({
- dog, cat
- }) {
- }
- const myDict = {
- dog: 'woof',
- cat: 'meow',
- };
- animalParty(myDict);
解构也能和数组配合使用:
- [a, b] = [10, 20];
- console.log(a); // prints 10
永远默认你的系统是分布式的
编写并行化的应用程序时, 您的目标是一次优化您的工作量. 如果您有四个可用的核心, 而您的代码只能使用一个核心, 那么您的潜力就浪费了 75%. 这意味着阻塞, 同步操作是并行计算的最终敌人. 但是考虑到 JS 是单线程语言, 事情就不会在多个内核上运行. 那有什么意义呢?
JS 是单线程的, 但不是单文件的(如学校里的代码). 即使不是并行的, 它仍然是并发的. 发送 HTTP 请求可能需要几秒钟甚至几分钟, 因此, 如果 JS 停止执行代码, 直到请求返回响应, 该语言将无法使用.
JavaScript 通过事件循环解决了这个问题. 事件循环遍历已注册的事件, 并根据内部调度 / 优先级逻辑执行它们. 这使发送数千个同时的 HTTP 请求或同时从磁盘读取多个文件成为可能. 要注意的是: 只有您使用正确的功能, JavaScript 才能利用此功能.
最简单的例子是 for 循环:
- const items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
- async function testCall() {
- // do async stuff here
- }
- for (let i = 0; i <10; i += 1) {
- testCall();
- }
原始 for 循环是编程中存在的最少并行构造之一. 在我的上一份工作中, 我带领一个团队花费了几个月的时间尝试将传统的 R lang for 循环转换为自动并行代码. 从根本上讲, 这是一个不可能解决的问题, 只有等待深度学习的改善才能解决. 并行化 for 循环的困难源于一些有问题的模式. 顺序 for 循环非常少见, 但仅凭它们就无法保证 for 循环可分解性:
- const items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
- items.map(async (item) => {
- // do async stuff here
- });
如果按顺序执行该代码, 并且每次迭代执行, 则只会产生预期的结果. 如果尝试一次执行多个迭代, 则处理器可能会基于不正确的值错误的分支, 这会使结果无效. 如果这是 C 代码, 我们将有不同的讨论, 因为用法不同, 并且编译器可以使用循环完成很多技巧. 在 JavaScript 中, 仅在绝对必要时才应使用传统的 for 循环. 否则, 请使用以下构造:
- const items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
- const allResults = await Promise.all(items.map(async (item) => {
- // do async stuff here
- }));
我将解释为什么这些是对传统 for 循环的改进. 诸如 map 之类的结构无需按顺序执行每个迭代, 而是采用所有元素并将它们作为单独的事件提交给用户定义的 map 函数. 在大多数情况下, 各个迭代之间没有固有的联系或依赖性, 因此它们可以并行运行. 这并不是说您无法使用 for 循环来完成同一件事. 实际上, 它看起来像这样:
- const items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
- async function testCall() {
- // do async stuff here
- }
- for (let i = 0; i <10; i += 1) {
- testCall();
- }
如您所见, for 循环并不能阻止我以正确的方式进行操作, 但是它肯定也不会使其变得更容易. 与 map 版本比较:
var fooVar = 3; // airbnb rules forebid "var"
如您所见, map 就可以了. 如果要在所有单个异步操作完成之前进行阻塞, 则 map 的优势将变得更加明显. 使用 for 循环代码, 您将需要自己管理一个数组. 这是 map 版本:
- const items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
- const allResults = await Promise.all(items.map(async (item) => {
- // do async stuff here
- }));
真的很容易, 在许多情况下, 与 map 或 forEach 相比, for 循环的性能相同(或可能更高). 我仍然认为, 现在丢失几个周期值得使用定义良好的 API 的优点. 这样, 将来对该数据访问模式实现的任何改进都会使您的代码受益. for 循环过于笼统, 无法对同一模式进行有意义的优化.
在 map 和 forEach 之外还有其他有效的异步选项, 例如 for-await-of.
静态检查你的代码
没有一致风格 (外观) 的代码很难阅读和理解. 因此, 以任何语言编写高端代码的一个关键方面是保持一致且明智的风格. 由于 JS 生态系统的广度, 对于 lint 风格和 style 细节有很多选择. 我要强调的是, 与使用特制的 lint/style 相比, 使用 lint 并强制使用一种 style(其中的任何一种)更为重要. 归根结底, 没有人会完全按照我的意愿来编写代码, 因此对此进行优化是不切实际的目标.
我看到很多人问他们应该使用 eslint 还是 prettier. 对我来说, 它们的用途非常不同, 因此应结合使用. ESlint 大部分时间都是传统的 linter. 它将确定与 style 无关, 而与正确性有关的代码问题. 例如, 我将 eslint 与 Airbnb 规则结合使用. 使用该配置, 以下代码将强制 linter 失败:
var fooVar = 3; // airbnb rules forebid "var"
很明显, eslint 如何为您的开发周期增加价值. 从本质上讲, 它可以确保您遵循关于什么是好习惯的准则. 因此, lint 在本质上是强制执行的. 与所有意见一样, 将其与盐一同食用. Linter 可能是错误的.
Prettier 是代码格式化程序. 它较少关注正确性, 而更担心均匀性和一致性. Prettier 不会抱怨使用 var, 但是它将自动对齐代码中的所有方括号. 在我的个人开发过程中, 在将代码推送到 Git 之前, 我总是使用 Prettier 来美化代码格式. 在许多情况下, 让 Prettier 在每次对存储库的每次提交时自动运行甚至是有意义的. 这样可以确保所有进入源代码管理的代码都具有一致的样式和结构.
测试你的代码
编写测试是改善您编写的 JS 代码的一种间接但非常有效的方法. 我建议你需要适应使用各种各样的测试工具. 您的测试需求会有所不同, 并且没有一种工具可以处理所有问题. JS 生态系统中有大量完善的测试工具, 因此选择工具主要取决于个人喜好. 与往常一样, 请自己考虑.
Test Driver - Ava
Test drivers 是一种简单的框架, 它可以提供高级别的结构和实用性. 它们通常与其他特定的测试工具结合使用, 具体取决于您的测试需求.
Ava 是表达力和简洁性的完美平衡. Ava 的并行, 隔离的体系结构是我大部分爱的源泉. 运行速度更快的测试可节省开发人员时间和公司资金. Ava 拥有大量不错的功能, 例如内置断言, 同时设法将其保持在最小限度.
其他选项: Jest, Mocha, Jasmine
Mocks - Nock
HTTP 模拟是伪造 http 请求过程的一部分的过程, 因此测试人员可以注入自定义逻辑来模拟服务器行为.
Http 模拟可能是一个真正的痛苦, 但是 Nock 减轻了痛苦. Nock 直接覆盖 Node.JS 的内置请求, 并拦截传出的 http 请求. 这又使您可以完全控制响应.
其他选项: 我也不知道其他
测试自动化 - Selenium
我对推荐 Selenium 感到喜忧参半. 由于它是网络自动化的最受欢迎的选项, 因此它拥有庞大的社区和在线资源集. 不幸的是, 学习曲线相当陡峭, 并且它依赖于许多实际使用的外部库. 话虽如此, 它是唯一真正的免费选项, 因此, 除非您要进行企业级的网络自动化, 否则 Selenium 会是一个好选择.
其他选项: Cypress, PhantomJS
永无终点的旅途
与大多数事情一样, 编写更好的 JavaScript 是一个持续的过程. 代码始终可以变得更加简洁, 一直在添加新功能, 并且永远没有足够的测试. 看起来似乎势不可挡, 但是由于有很多潜在的方面需要改进, 因此您可以按照自己的步调真正进步. 一次接着一步, 在不知不觉中, 您将成为 JavaScript 的王者.
来源: http://www.jianshu.com/p/d292d9462300