JavaScript 程序員能夠從C++中學到什麼

做者:Bret Cameron
翻譯:瘋狂的技術宅
原文: https://medium.com/@bretcamer...

本文首發微信公衆號:前端先鋒
歡迎關注,天天都給你推送新鮮的前端技術文章javascript


如何經過了解類型、內存以及低級語言使你成爲更好的程序員

clipboard.png

時間的結束?圖片來自 Jens Kreuter,由Bret Cameron修改。html

像許多開發新手同樣,JavaScript 是我學的第一門語言。它是一種 Web 前端編程語言 —— 感謝Node.js —— 它同時也是一種流行的後端工具。前端

我也相信,做爲一種「更高級」的語言,JavaScript 是初學者的絕佳選擇。你能夠在任何 Web 瀏覽器上運行它,而且因爲具備原型繼承和動態類型等功能,學習者在編寫和執行第一段代碼以前克服的障礙更少。java

可是 JavaScript 讓初學者更容易上手的因素也讓它難以被掌握。它能以看上去不直觀的方式運行,而且當涉及到更多不透明的功能時,許多程序員更依賴於試錯法,例如隱式類型強制轉換或 this 關鍵字。 知道這些功能比理解它們要容易得多。node

clipboard.png

「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 中的內存泄漏。還會研究內存管理與時間溢出之間的關係。程序員

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++ 中的類型和內存管理

C++ 之類的低級語言沒有這種潛在缺陷,由於必須在定義時聲明數據類型。雖然 JavaScript 也有三個關鍵字  varletconst  用於聲明新變量,但在C ++中每一個數據類型都有本身的關鍵字。

例如 C++ 中的 7 種基本數據類型是整型、浮點型、雙精度浮點型,字符型,寬字符型,布爾型和無類型。用於定義它們的關鍵字分別是 intfloatdoubleboolcharwchar_tvoid

下面的代碼段包含了每種類型的示例聲明,並添加了註釋:

#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 關鍵字能夠與關鍵字 shortlonglong long 以及 「signedness」 關鍵字 signedunsigned 結合使用。 。

基本的 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 中的內存管理

「JavaScript 在建立對象時自動分配內存,並在再也不使用時釋放它( 垃圾回收)。這種自動化處理可能會引發混亂:它可能會給程序員帶來錯誤的暗示,即他們不須要擔憂內存管理問題。「 —— MDN

JavaScript被稱爲「自動垃圾回收」語言。它用 mark-and-sweep 算法來檢查哪些內存是活動的,哪些是「垃圾」。而後收集器能夠釋放「垃圾」,將未使用的內存還給操做系統。

自動垃圾回收是高級語言的一個特徵,它有助於釋放內存——不須要經過程序員的明確指示就能夠告訴它再也不須要。有關 JavaScript 中垃圾回收機制的信息,請查看這篇文章MDN’s page on Memory Management

垃圾回收是一個強大的自動內存管理系統,但它並不是萬無一失。特別是所謂的「不須要的引用」可能會致使內存泄漏,這意味着程序佔用的內存比實際須要的多,從而下降了內存的效率。可是若是咱們可以意識到內存泄漏的風險,就能夠採起措施將其刪除。

意外的使用全局變量是致使內存泄漏的一個常見緣由。當咱們在 JavaScript 代碼中沒有用關鍵字 varletconst 定義變量時,那麼它會自動被認爲是一個全局變量。除非已定義了 foo,不然表達式 foo =「bar」 至關於 window.foo = "bar"

像 ESLint 這樣的 linting 工具能夠幫助你找出這樣的錯誤,可是 JavaScript 內置的嚴格模式也能夠將它們標記爲錯誤,從而防止意外使用全局變量。要激活嚴格模式,只需在腳本或函數的開頭加入"use strict";。有關從代碼中去除內存泄漏風險的更多方法,請參閱這篇文章

JavaScript 中的類型

還有一些方法能夠指定變量類型並在 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!


本文首發微信公衆號:前端先鋒

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章


歡迎繼續閱讀本專欄其它高贊文章:

相關文章
相關標籤/搜索