做者:Bret Cameron
翻译:疯狂的技术宅
原文: https://medium.com/@bretcamer...
本文首发微信公众号:前端先锋
欢迎关注,天天都给你推送新鲜的前端技术文章javascript
如何经过了解类型、内存以及低级语言使你成为更好的程序员
时间的结束?图片来自 Jens Kreuter,由Bret Cameron修改。html
像许多开发新手同样,JavaScript 是我学的第一门语言。它是一种 Web 前端编程语言 —— 感谢Node.js —— 它同时也是一种流行的后端工具。前端
我也相信,做为一种“更高级”的语言,JavaScript 是初学者的绝佳选择。你能够在任何 Web 浏览器上运行它,而且因为具备原型继承和动态类型等功能,学习者在编写和执行第一段代码以前克服的障碍更少。java
可是 JavaScript 让初学者更容易上手的因素也让它难以被掌握。它能以看上去不直观的方式运行,而且当涉及到更多不透明的功能时,许多程序员更依赖于试错法,例如隐式类型强制转换或 this
关键字。 知道这些功能比理解它们要容易得多。node
“Any fool can know. The point is to understand.” —— Albert Einstein
所以要成为更高级的 JavaScript 开发人员,试着更深刻地了解幕后发生的事情是有很大帮助的。归根结底,最精彩的地方是 V8 JavaScript 引擎:它是使用最普遍的 JavaScript 编译器(Google Chrome、Node.js等的基础之一),它是开源的,所以你能够准确地看到 JavaScript 是怎样在 C ++ 中执行的。ios
可是本文不是 V8 的指南。相反,它是有关像 C++ 这样的低级语言如何帮助咱们提升对 JavaScript 等高级语言的理解的一篇文章。 C++ 不只能够帮助咱们理解底层的编译器代码,并且经过研究 C++ 程序员必需要作而 JavaScript 程序员没必要作的事,能够更好地了解在 JavaScript 中提高效率的地方,以及为何有时会引起问题。git
特别是咱们将会研究 C++ 中的数据类型和内存管理,以及这些知识如何帮助咱们避免类型错误,并防止 JavaScript 中的内存泄漏。还会研究内存管理与时间溢出之间的关系。程序员
在进入 C++ 以前,先让咱们看看 JavaScript 是如何处理数据类型以及“类型强制”系统的一些陷阱的。面试
JavaScript 使用类型强制转化自动将一种数据类型转换为另一种:字符串转为数字、数字转为字符串、数字或字符串转为布尔值等等。换句话说,若是你没有明确指定所需的类型,JavaScript 将根据一组规则进行猜想。有时这很管用,它能够帮助咱们快速简洁地编写代码。但有时候多是引起混乱的缘由。算法
实际上即便这种行为从根本上来说是可预测的,但某些自动推测也不那么直观,而且在不少大型项目的代码库中,很容易看到类型强制转换致使了意外错误的发生。例如如下是使用组合字符串和数字的进行运算的一些演示:
"10" - 4 // 6 "10" + 4 // "104" "20" - "5" // 15 "20" + "5" // 205 "20" + + "5" // 205 "foo" + "bar" // "foobar" "foo" + + "bar" // "fooNaN" "6" - 3 + 3 // 6 "6" + 3 - 3 // 60
在这些例子中, +
运算符形成了大量的混乱,它能够强制把字符串转为数字,也能够做为链接运算符组合两个或多个字符串。
最后一个例子多是最使人困惑的。在 "6" + 3 — 3
中,若是首先处理 3 — 3
,而后再进行字符串链接,"6" + 0
会返回一个字符串,可是在这里返回的结果竟然是一个数字!
虽然类型强制转换能够帮助开发人员更快速、简洁地编写代码,可是它使初学者思考得更少,从而也就不清楚为何这样的转换系统可能会致使错误,特别是在更大、更复杂的代码库中。上面的结果对于经验丰富的 JavaScript 程序来讲多是彻底合理的,但它们并不直观!
考虑到 JavaScript 类型强制系统的优势和缺点,如今让咱们看看 C++ 是如何处理数据类型的。
C++ 之类的低级语言没有这种潜在缺陷,由于必须在定义时声明数据类型。虽然 JavaScript 也有三个关键字 var
、 let
和 const
用于声明新变量,但在C ++中每一个数据类型都有本身的关键字。
例如 C++ 中的 7 种基本数据类型是整型、浮点型、双精度浮点型,字符型,宽字符型,布尔型和无类型。用于定义它们的关键字分别是 int
、float
、double
、bool
、char
、wchar_t
和 void
。
下面的代码段包含了每种类型的示例声明,并添加了注释:
#include <iostream> #include <string> using namespace std; int main() { // BOOLEANS bool isChecked = true; // INTEGERS int age = 24; // FLOATS // In general, a float has 7 decimal digits of precision, while a double has 15 float pi7 = 3.1415926; double pi15 = 3.141592653589793; // CHARACTERS // Regular characters can contain only values stored in the ISO Latin tables // Wide characters, however, can contain unicode values char englishGreeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'}; wchar_t mandarinGreeting[7] = { 'n', 'ǐ', ' ', 'h', 'ǎ', 'o', '\0' }; // STRINGS // In C++, string is not a data type (as it is in JavaScript and many other languages) // It is a class, and so we must write #include <string> at the top of the document string greeting = "Hello"; // VOID // A common use of void is to define functions which don't return anything void printMessage() { cout << "Hello, world!"; }; return 0; }
与 JavaScript 不一样,C++ 为开发人员提供了大量内存管理的方法。在 C++ 中,每声明一个变量时,咱们也会决定要保留多少内存。例如,普通的 char
一般只包含8位(1字节),这就将其用途限制为 ISO Latin 表的255个字符。相比之下,wchar_t
包含16或32位,虽然占用了更多内存,但容许咱们可以访问更多种类的 Unicode 字符。
在整型中能够找到最多的种类,其中基本的 int
关键字能够与关键字 short
,long
和 long long
以及 “signedness” 关键字 signed
和 unsigned
结合使用。 。
基本的 int
类型的取值范围是系统体系建议的天然范围。在 64 位操做系统上一般是 32 位。这就意味着这样的一个有符号的变量的取值范围在 -2,147,483,648 和 2,147,483,647 之间,而无符号变量的取值范围是 0 到 4,294,967,295 之间。
若是你可以确认本身的变量取值范围比较小,可使用 short int
来节省内存。或者若是你正在处理很是大的整数,你可使用 unsigned long long int
来处理 64 位的数字,其取值上限为 2^64 - 1
。
使用 64 位变量(例如 long long int
)可让计算机表示将来约 2.92 亿年的日期。这彷佛是没什么必要的,但它实际上解决了一个很是实际的问题。
按照惯例,计算中的大多很多天期都是用 Unix 时间来表示的,该时间的起始日期是 1970 年 1 月 1 日午夜,精确到秒。若是将 Unix 时间存储在有符号的 32 位变量中,可记录的最大值为 2,147,483,647。虽然看起来很大,但考虑到它每一秒都在增加,实际上 20 亿并不能让咱们用得过久。
实际上 32 位系统上记录的日期将在 2038 年 1 月 19 日 UTC(刚好是 03:14:07 )达到最大值。当这种状况发生时,日期将会变为负的 2,147,483,647,这个时间是 1901 年 12 月 13 日。它被称为 2038 问题,而且它致使了许多标题的出现,例如“全部计算机将在 2038 年完蛋” —— 由英国小报 Metro 提供。
这个使人震惊的标题可能并不是事实,可是当 2038 年到来时,这个问题可能会致使 32 位操做系统甚至是整个旧版本的编程语言出现问题。我第一次遇到这个问题时正在用 PHP,在 5.2 版本以前没有内置的方式可以记录超过 2038 年的日期。(JavaScript 使用了 64 位系统来处理日期,因此咱们 JavaScript 程序员不用担忧这个)
2038 问题证实了咱们本身管理内存的潜在用处。在须要较小取值范围的地方能够节省内存。在须要更大取值范围的场合,能够确保咱们的系统可以拥有足够的内存。
“JavaScript 在建立对象时自动分配内存,并在再也不使用时释放它( 垃圾回收)。这种自动化处理可能会引发混乱:它可能会给程序员带来错误的暗示,即他们不须要担忧内存管理问题。“ —— MDN
JavaScript被称为“自动垃圾回收”语言。它用 mark-and-sweep 算法来检查哪些内存是活动的,哪些是“垃圾”。而后收集器能够释放“垃圾”,将未使用的内存还给操做系统。
自动垃圾回收是高级语言的一个特征,它有助于释放内存——不须要经过程序员的明确指示就能够告诉它再也不须要。有关 JavaScript 中垃圾回收机制的信息,请查看这篇文章和 MDN’s page on Memory Management。
垃圾回收是一个强大的自动内存管理系统,但它并不是万无一失。特别是所谓的“不须要的引用”可能会致使内存泄漏,这意味着程序占用的内存比实际须要的多,从而下降了内存的效率。可是若是咱们可以意识到内存泄漏的风险,就能够采起措施将其删除。
意外的使用全局变量是致使内存泄漏的一个常见缘由。当咱们在 JavaScript 代码中没有用关键字 var
、let
或 const
定义变量时,那么它会自动被认为是一个全局变量。除非已定义了 foo
,不然表达式 foo =“bar”
至关于 window.foo = "bar"
。
像 ESLint 这样的 linting 工具能够帮助你找出这样的错误,可是 JavaScript 内置的严格模式也能够将它们标记为错误,从而防止意外使用全局变量。要激活严格模式,只需在脚本或函数的开头加入"use strict";
。有关从代码中去除内存泄漏风险的更多方法,请参阅这篇文章。
还有一些方法能够指定变量类型并在 JavaScript 中建立本身的类型,这种方式让人想到低级语言。最流行和最全面的解决方案是 TypeScript,它是 JavaScript 的语法超集,为语言添加了静态类型选项。
在 TypeScript 上有不少不错的资源,足以说明它是能确保你代码可扩展性并且没有错误的好方法,它能够帮助咱们避免本文在前面关于“强制类型”那一节中看到的那种不直观的结果。 TypeScript 的文件扩展名是 .ts
,还有一个等效的 .jsx
:.tsx
。对初学者来讲最好的一篇文章是5分钟入门 TypeScript 。
值得注意的是,还有一些针对不一样 JavaScript 技术的类型注释解决方案。例如你能够将官方的 PropTypes node module 添加到你的 React 项目中。这使你能够记录传递给组件的 props 的预期数据类型以及设置默认值。特别是当与像 ESLint 这样的 linter 结合使用时,PropTypes 是基于 React 的设置的强大补充。
总的来讲,我但愿本文有助于阐明 C++ 这样的低级语言和 JavaScript 这类高级语言之间的一些差别。
我也但愿它可以为你提供一种工具,以 TypeScript 或 PropTypes 的形式将 C++ 中的一些好处带入 JavaScript,并能够影响和改进 JavaScript 中的内存管理。
若是你对 C++ 有深刻的理解,而且想要了解更多关于 JavaScript 的实现方式,最好的去处多是官方 V8 网站或者官方 Git repo。Happy coding!