改进你 JS 代码要做的第一件事就是不写 JS。TypeScript(TS)是JS的“编译”超集(所有能在 JS 中运行的东西都能在 TS 中运行)。 TS 在 vanilla JS 体验之上增加了一个全面的可选类型系统。很长一段时间里,整个 JS 生态系统对 TS 的支持不足以让我觉得应该推荐它。但值得庆幸的是,那养的日子已经过去很久了,大多数框架都支持开箱即用的 TS。假设我们都知道 TS 是什么,现在让我们来谈谈为什么要使用它。
类型安全描述了一个过程,其中编译器验证在整个代码段中以“合法”方式使用所有类型。换句话说,如果你创建一个带有 number 类型参数的函数 foo:
function foo(someNum: number): number {
return someNum + 5;
}
只应使给 foo 函数提供 number 类型的参数:
good
console.log(foo(2)); // prints "7"
no good
console.log(foo("two")); // invalid TS code
除了向代码添加类型的开销之外,使用类型安全没有任何缺点。额外的好处太大了而不容忽视。类型安全提供额外级别的保护,以防止出现常见的错误或bug,这是对像 JS 这样无法无天的语言的祝福。
电影:无法无天,主演 shia lebouf
重构大型 JS 程序是一场真正的噩梦。重构 JS 过程中引起痛苦的大部分原因是它没有强制按照函数的原型执行。这意味着 JS 函数永远不会被“误用”。如果我有一个由 1000 种不同的服务使用的函数 myapi:
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% 确定每个使用此函数的位置(足足有1000个)都正确地更新了用法。哪怕我漏掉一个地方,函数也可能就会失效。这与使用 TS 的情况相同:
之前
function myAPITS(someNum: number, someString: string) { ... }
之后
function myAPITS(someString: string, someNum: number) { ... }
正如你所看到的,我对 myAPITS 函数进行了与 JavaScript 对应的相同更改。但是这个代码不是产生有效的 JavaScript,而是导致无效的 TypeScript,因为现在使用它的 1000 个位置提供了错误的类型。而且由于我们之前讨论过的“类型安全”,这 1000 个问题将会阻止编译,并且你的函数不会失效(这非常好)。
正确设置 TS 后,如果事先没有定义好接口和类,就很难编写代码。这也提供了一种简洁的分享、交流架构方案的方法。在 TS 出现之前,也存在解决这个问题的其他方案,但是没有一个能够真正的解决它,并且还需要你做额外的工作。例如,如果我想为自己的后端添加一个新的 Request 类型,我可以使用 TS 将以下内容发送给一个队友。
interface BasicRequest {
body: Buffer;
headers: { [header: string]: string | string[] | undefined; };
secret: Shhh;
}
尽管我不得不编写一些代码,但是现在可以分享自己的增量进度并获得反馈,而无需投入更多时间。我不知道 TS 本质上是否能比 JS 更少出现“错误”,不给我强烈认为,迫使开发人员首先定义接口和 API,从而产生更好的代码是很有必要的。
总的来说,TS 已经发展成为一种成熟且更可预测的 vanilla JS替代品。肯定仍然需要 vanilla JS,但是我现在的大多数新项目都是从一开始就是 TS。
JavaScript 是世界上最流行的编程语言之一。你可能会认为,有大约数百万人使用的 JS 现在已经有 20 多岁了,但事实恰恰相反。JS 已经做了很多改变和补充(是的我知道,从技术上说是 ECMAScript),从根本上改变了开发人员的体验。作为近两年才开始编写 JS 的人,我的优势在于没有偏见或期望。这导致了我关于要使用哪种语言更加务实。
很长一段时间里,异步、事件驱动的回调是 JS 开发中不可避免的一部分:
传统的回调
makeHttpRequest('google.com', function (err, result) {
if (err) {
console.log('Oh boy, an error');
} else {
console.log(result);
}
});
我不打算花时间来解释上述问题(我以前写过此类文章)。为了解决回调问题,JS 中增加了一个新概念 “Promise”。 Promise 允许你编写异步逻辑,同时避免以前基于回调的代码嵌套问题的困扰。
Promises
makeHttpRequest('google.com').then(function (result) {
console.log(result);
}).catch(function (err) {
console.log('Oh boy, an error');
});
Promise 优于回调的最大优点是可读性和可链接性。
虽然 Promise 很棒,但它们仍然有待改进。到现在为止,写 Promise 仍然感觉不到“原生”。为了解决这个问题,ECMAScript 委员会决定添加一种利用 promise,async 和 await 的新方法:
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) {
// ...
}
也可以直接 await 一个 Promise,因为 async 函数实际上只是一个花哨的 Promise 包装器。这也意味着,async/await代码和 Promise 代码在功能上是等价的。所以随意使用 async/await 并不会让你感到不安。
对于大多数 JS 只有一个变量限定符 var。 var 在处理方面有一些非常独特且有趣的规则。 var 的作用域行为是不一致而且令人困惑的,在 JS 的整个生命周期中导致了意外行为和错误。但是从 ES6 开始有了 var 的替代品:const 和 let。几乎没有必要再使用 var 了。使用 var 的任何逻辑都可以转换为等效的 const 和 let 代码。
至于何时使用 const 和 let,我总是优先使用 const。 const 是更严格的限制和 “永固的”,通常会产生更好的代码。我仅有 1/20 的变量用 let 声明,其余的都是 const。
我之所以说 const 是 “永固的” 是因为它与 C/C++ 中的 const 的工作方式不同。 const 对 JavaScript 运行时的意义在于对 const 变量的引用永远不会改变。这并不意味着存储在该引用中的内容永远不会改变。对于原始类型(数字,布尔等),const 确实转化为不变性(因为它是单个内存地址)。但对于所有对象(类,数组,dicts),const并不能保证不变性。
箭头函数是在 JS 中声明匿名函数的简明方法。匿名函数即描述未明确命名的函数。通常匿名函数作为回调或事件钩子传递。
vanilla 匿名函数
someMethod(1, function () { // has no name
console.log('called');
});
在大多数情况下,这种风格没有任何“错误”。 Vanilla 匿名函数在作用域方面表现得“有趣”,这可能导致许多意外错误。有了箭头函数,我们就不必再担心了。以下是使用箭头函数实现的相同代码:
匿名箭头函数
someMethod(1, () => { // has no name
console.log('called');
});
除了更简洁之外,箭头函数还具有更实用的作用域行为。箭头函数从它们定义的作用域继承 this。
在某些情况下,箭头函数可以更简洁:
const added = [0, 1, 2, 3, 4].map((item) => item + 1);
console.log(added) // prints "[1, 2, 3, 4, 5]"
第 1 行的箭头函数包含一个隐式的 return 声明。不需要具有单线箭头功能的括号或分号。
在这里我想说清楚,这和 var 不一样,对于 vanilla 匿名函数(特别是类方法)仍有效。话虽这么说,但如果你总是默认使用箭头函数而不是vanilla匿名函数的话,最终你debug的时间会更少。
提取一个对象的键值对,并将它们作为另一个对象的子对象添加,是一种很常见的情况。有几种方法可以实现这一目标,但它们都非常笨重:
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 arr1 = [1, 2];
const arr2 = [3, 4];
console.log([ ...arr1, ...arr2 ]); // prints [1, 2, 3, 4]
它可能不是最重要的 JS 功能,但它是我最喜欢的功能之一。
字符串是最常见的编程结构之一。这就是为什么它如此令人尴尬,以至于本地声明字符串在许多语言中仍然得不到很好的支持的原因。在很长一段时间里,JS 都处于“糟糕的字符串”系列中。但是文字模板的添加使 JS 成为它自己的一个类别。本地文字模板,方便地解决了编写字符串,添加动态内容和编写桥接多行的两个最大问题:
const name = 'Ryland';
const helloString =
`Hello
${name}`;
我认为代码说明了一切。多么令人赞叹。
对象解构是一种从数据集合(对象,数组等)中提取值的方法,无需对数据进行迭代或显的式访问它的 key:
旧方法
function animalParty(dogSound, catSound) {}
const myDict = {
dog: 'woof',
cat: 'meow',
};
animalParty(myDict.dog, myDict.cat);
解构
function animalParty(dogSound, catSound) {}
const myDict = {
dog: 'woof',
cat: 'meow',
};
const { dog, cat } = myDict;
animalParty(dog, cat);
不过还有更多方式。你还可以在函数的签名中定义解构:
解构2
function animalParty({ dog, cat }) {}
const myDict = {
dog: 'woof',
cat: 'meow',
};
animalParty(myDict);
它也适用于数组:
解构3
[a, b] = [10, 20];
console.log(a); // prints 10
还有很多你应该使用现代功能。
编写并行化程序时,你的目标是优化你一次性能够完成的工作量。如果你有 4 个可用的 CPU 核心,并且你的代码只能使用单个核心,则会浪费 75% 的算力。这意味着,阻塞、同步操作是并行计算的最终敌人。但考虑到 JS 是单线程语言,不会在多个核心上运行。那这有什么意义呢?
尽管 JS 是单线程的,它仍然是可以并发执行的。发送 HTTP 请求可能需要几秒甚至几分钟,在这期间如果 JS 停止执行代码,直到响应返回之前,语言将无法使用。
JavaScript 通过事件循环解决了这个问题。事件循环,即循环注册事件并基于内部调度或优先级逻辑去执行它们。这使得能够“同时”发送1000个 HTTP 请求或从磁盘读取多个文件。这是一个问题,如果你想要使用类似的功能,JavaScript 只能这样做。最简单的例子是 for 循环:
let sum = 0;
const myArray = [1, 2, 3, 4, 5, ... 99, 100];
for (let i = 0; i < myArray.length; i += 1) {
sum += myArray[i];
}
for 循环是编程中存在的最不并发的构造之一。在上一份工作中,我带领一个团队花了几个月的时间尝试将 R 语言中的 for-loops 转换为自动并行代码。这基本上是一个不可能的任务,只有通过等待深度学习技术的改善才能解决。并行化 for 循环的难度来自一些有问题的模式。用 for 循环进行顺序执行的情况是比较罕见的,但它们无法保证循环的可分离性:
let runningTotal = 0;
for (let i = 0; i < myArray.length; i += 1) {
if (i === 50 && runningTotal > 50) {
runningTotal = 0;
}
runningTotal += Math.random() + runningTotal;
}
如果按顺序执行迭代,此代码仅生成预期结果。如果你尝试执行多次迭代,则处理器可能会根据不准确的值进入错误地分支,从而使结果无效。如果这是 C 代码,我们将会进行不同的讨论,因为使用情况不同,编译器可以使用循环实现相当多的技巧。在 JavaScript 中,只有绝对必要时才应使用传统的 for 循环。否则使用以下构造:
map
// in decreasing relevancy :0
const urls = ['google.com', 'yahoo.com', 'aol.com', 'netscape.com'];
const resultingPromises = urls.map((url) => makHttpRequest(url));
const results = await Promise.all(resultingPromises);
带索引的 map
// in decreasing relevancy :0
const urls = ['google.com', 'yahoo.com', 'aol.com', 'netscape.com'];
const resultingPromises = urls.map((url, index) => makHttpRequest(url, index));
const results = await Promise.all(resultingPromises);
for-each
const urls = ['google.com', 'yahoo.com', 'aol.com', 'netscape.com'];
// note this is non blocking
urls.forEach(async (url) => {
try {
await makHttpRequest(url);
} catch (err) {
console.log(`${err} bad practice`);
}
});
下面我将解释为什么这是对传统 for 循环的改进:不是按顺序执行每个“迭代”,而是构造诸如 map 之类的所有元素,并将它们作为单独的事件提交给用户定义的映射函数。这将直接与运行时通信,各个“迭代”彼此之间没有连接或依赖,所以能够允许它们同时运行。我认为现在应该抛弃一些循环,应该去使用定义良好的 API。这样对任何未来数据访问模式实现的改进都将使你的代码受益。 for 循环过于通用,无法对同一模式进行有意义的优化。
map 和 forEach 之外还有其他有效的异步选择,例如 for-await-of。
没有一致风格的代码难以阅读和理解。因此,用任何语言编写高端代码的一个关键就是具有一致和合理的风格。由于 JS 生态系统的广度,有许多针对 linter 和样式细节的选项。我不能强调的是,你使用一个 linter 并强制执行同一个样式(随便哪个)比你专门选择的 linter 或风格更重要。最终没人能够准确地编写代码,所以优化它是一个不切实际的目标。
有很多人问他们是否应该用 eslint 或 prettier。对我来说,它们的目的是有很大区别的,因此应该结合使用。 Eslint 是一种传统的 “linter”,大多数情况下,它会识别代码中与样式关系不大的问题,更多的是与正确性有关。例如,我使用eslint与 AirBNB 规则。如果用了这个配置,以下代码将会强制 linter 失败:
var fooVar = 3; // airbnb rules forebid "var"
很明显,eslint 为你的开发周期增加价值。从本质上讲,它确保你遵循关于“is”和“isn't”良好实践的规则。因此 linters 本质上是固执的,只要你的代码不符合规则,linter 可能就会报错。
Prettier 是一个代码格式化程序。它不太关心“正确性”,更关注一致性。 Prettier 不会对使用 var 提出异议,但会自动对齐代码中的所有括号。在我的开发过程中,在将代码推送到 Git 之前,总是处理得很漂亮。很多时候让 Prettier 在每次提交到 repo 时自动运行是非常有意义的。这确保了进入源码控制系统的所有代码都有一致的样式和结构。
编写测试是一种间接改进你代码但非常有效的方法。我建议你熟悉各种测试工具。你的测试需求会有所不同,没有哪一种工具可以处理所有的问题。 JS 生态系统中有大量完善的测试工具,因此选择哪种工具主要归结为个人偏好。一如既往,要为你自己考虑。
测试驱动只是简单的框架,可以提供非常高级别的结构和工具。它们通常与其他特定测试工具结合使用,这些工具根据你的实际需求而有所不同。
Ava 是表达力和简洁性的完美平衡。 Ava 的并行和独立的架构是我的最爱。快速运行的测试可以节省开发人员的时间和公司的资金。Ava 拥有许多不错的功能,例如内置断言等。
替代品:Jest,Mocha,Jasmine
Sinon on Github(https://github.com/sinonjs/sinon)
Spies 为我们提供了“功能分析”,例如调用函数的次数,调用了哪些函数以及其他有用的数据。
Sinon 是一个可以做很多事的库,但只有少数的事情做得超级好。具体来说,当涉及到 Spies 和 Stubs 时,sinon非常擅长。功能集丰富而且语法简洁。这对于 Stubs 尤其重要,因为它们为了节省空间而只是部分存在。
替代方案:testdouble
Nock on Github(https://github.com/nock/nock?...)
HTTP 模拟是伪造 http 请求中某些部分的过程,因此测试人员可以注入自定义逻辑来模拟服务器行为。
http 模拟可能是一种真正的痛苦,nock 使它不那么痛苦。 Nock 直接覆盖 nodejs 内置的 request 并拦截传出的 http 请求。这使你可以完全控制 http 响应。
替代方案:我真的不知道 :(
Selenium on Github(https://github.com/SeleniumHQ...)
我对推荐 Selenium 有着一种复杂的态度。由于它是 Web 自动化最受欢迎的选择,因此它拥有庞大的社区和在线资源集。不幸的是学习曲线相当陡峭,并且它依赖许多外部库。尽管如此,它是唯一真正的免费选项,所以除非你做一些企业级的网络自动化,否则还是 Selenium 最适合这个工作。
作者:Ryland G
原文:https://dev.to/taillogs/practical-ways-to-write-better-javascript-26d4
近些日子,我使用了新语言编程,从JavaScript,切确地说是Elm,转成TypeScript。在本文中,我将继续深挖一些我非常喜欢的TypeScript特性。
TypeScript 和 JavaScript 是目前项目开发中较为流行的两种脚本语言,我们已经熟知 TypeScript 是 JavaScript 的一个超集,但是 TypeScript 与 JavaScript 之间又有什么样的区别呢?
Nerv_是一款由京东凹凸实验室打造的类 React 前端框架,基于虚拟 DOM 技术的 JavaScript(TypeScript) 库。它基于React标准,提供了与 React 16 一致的使用方式与 API。
交叉类型:将多个类型合并为一个类型、联合类型:表示取值可以为多种类型中的一种、混合类型:一个例子就是,一个对象可以同时做为函数和对象使用,并带有额外的属性、类型断言:可以用来手动指定一个值的类型
在做比较大的,多人合作的项目的时候,TypeScript会更加地适合,这得益于它的可读性,面向对象性以及易于重构的特点。但如果只是自己做小程序,不需要太多人参与的时候,JavaScript则会更加简单。
有两种方式安装TypeScript,如何创建第一个TypeScript文件,在TypeScript中,可以使用interface来描述一个对象有firstName和lastName两个属性,TypeScript支持JavaScript的新功能,其中很重要的一个功能就是基于类的面向对象编程
使用TypeScript已经有了一段时间,这的确是一个好东西,虽说在使用的过程中也发现了一些bug,不过都是些小问题,所以整体体验还是很不错的。有关TypeScript声明类型声明相关的目前就总结了这些比较常用的
谷歌在很早之前就张开双臂拥抱 Web 应用程序,Gmail 已经发布 14 年了。当时,JavaScript 的世界是疯狂的。Gmail 工程师不得不为 IE 糟糕的垃圾回收算法捏一把汗,他们需要手动将字符串文字从 for 循环中提取出来,以避免 GC 停顿
TypeScript是一种由微软开发的自由和开源的编程语言。它是JavaScript的一个超集,TypeScript是JavaScript类型的超集,它可以编译成纯JavaScript。TypeScript可以在任何浏览器、任何计算机和任何操作系统上运行,并且是开源的。
差不多两年前,我在一个创业团队中开始了一个全新的项目。用到的全都是类似Microservices,docker,react,redux这些时髦的东西。我在前端技术方面积累了一些类似的经验
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!