進階 Javasctipt 概念 (1)

PY
8 min readMar 20, 2019

--

Javascript 背後運作與瀏覽器的前世今生

在 Udemy 上看到了一堂滿值得買下來的課 Advanced JavaScript Concepts,從 JS 的出生、底層運作,到非同步、OOP、Funtional JS 講得非常詳盡,所以打算用這課程來繼續深固一下我的 JS 觀念,分成以下七個部分

  1. Javascript 背後運作與瀏覽器的前世今生
  2. 重新認識 JS 奇怪部分
  3. JS 動態型別、一級與高階函式
  4. 閉包與原型繼承
  5. OOP vs Functional Programing
  6. 非同步原理解析
  7. 模組化與例外處理
  8. JS 資料結構與演算法

Javascript 背後運作與瀏覽器的前世今生

JS 起源

你知道 JavaScript 原型只花了10天設計嗎? 回到 1995 年,當時瀏覽器龍頭 Netscape Navigator ,為了要結合 html 與程式設計,招攬了美國工程師 Brendan Eich,目的是想讓 Scheme 這個語言塞入瀏覽器。

同時已經打算支援 JAVA 的 Netscape 公司覺得母湯,不想讓 html 變得這麼複雜,我們可以為這瀏覽器再創造一個與 JAVA 搭配使用並語法類似的語言,沒想到被指派為這簡單版 JAVA 負責人的 Brendan Eich 對 JAVA 一點興趣也沒,最後為了趕快搞個東西出來去跟 JAVA 一決高下,十天內創造出了 JS 原型。

JS 設計思路

(1)借鑑C語言的基本語法;

(2)借鑑Java語言的資料型別和記憶體管理;

(3)借鑑Scheme語言,將函式提升到”第一等公民”(first class)的地位;

當然這臨時弄出來的語言結構也相當鬆散,直到現在大家也對 Javascript 議論紛紛,甚至認為他根本不是一個語言。

一開始的 JS ,Brench 命名為 Mocha,1995年9月在 Netscape Navigator 2.0的Beta版中改名為LiveScript,同年12月,Netscape Navigator 2.0 Beta 3中部署時被重新命名為JavaScript ,目的就是為了當時最紅的JAVA(畢竟當時也準備要合作),最後昇陽與Netscape一起推出了JavaScript,就這樣誕生了。

Javascript engine

說到 JS engine 想必每個人都會先想到 V8 engine,而在這之前,得先提到世界上第一個 JS 引擎 — Spider Monkey,由 JS 創始者 Brendan Eich 用來為他的發明擦屁股,目前 Spider Monkey 也正在為 firefox 效力,這裡列出了 JS 所有引擎,其中使用率最高的莫過於 Google 於 2008 年創造使用C++寫的 V8 engine ,號稱速度最快,不管是 Chrome 還是 Nodejs 都用這個來 run JS file。

V8 engine

而 V8 引擎在做什麼呢? 電腦只讀得懂0跟1,所以當你說了 JS 這語言,JS 引擎會幫你翻譯為0跟1

JS file 進入 JS 引擎後分成7個流程 : Parser解析js -> AST抽象語法樹 -> Interpreter -> bytecode -> profiler -> compiler -> optimized code

解析完檔案後,AST抽象語法樹 將 JS 語法結構化並對映到相對節點成為樹狀結構,進入解譯器解成 Bytecode,或是使用編譯器重新編譯,例如 typescript, babel 等,利用編譯器來優化程式碼,所以其實 JS 同時可以被直譯也能被編譯。

Hidden class & Inline cache

JS 動態類別使得你可以隨意為類別增加或減少一個屬性,這是基於 key-value 類似 hash 的查找方法,所以查找這類別的屬性值相對地比其他語言要麻煩的多,所以 V8 engine 使用了隱藏類與內聯緩存這兩個方法來提高查找的效率

1. 隱藏類: 當new一個function,function會指向一個隱藏類,這隱藏類當有屬性被宣告時會通過轉換路徑指向新的隱藏類,並且接續下去

2. 內聯緩存: 隱藏類被創造時會獲得offset,他是參照隱藏類被創造的順序,所以當第二個同樣的function被宣告時,他就會直接用offset去查找,屬性調用的順序是影響效能的關鍵

隱藏類與內聯緩存是 JS 一大特點,JS 用這兩個方法提升了創造新類別的速度,先來個例子

1  function Point(x,y) {
2 this.x = x;
3 this.y = y;
4 }
5
7 var obj1 = new Point(1,2);
8 var obj2 = new Point(3,4);
9
10 obj1.a = 5;
11 obj1.b = 10;
12
13 obj2.b = 10;
14 obj2.a = 5;

Point 這個類別有兩個屬性 x 跟 y , 然後你創造了 obj1 & 2 此時就會創造一個隱藏類,同時 Point 這類別會指向這個隱藏類,接下來當你為這屬性賦值時,Point 會重新指向新的隱藏類並且將原本的也指向這個新的隱藏類,你賦值的屬性也會被此隱藏類下順序的偏移值,所以當我再接續創造下一個 obj2 時,他只需要共用 obj1 的隱藏類,不需要再創造一個類別的隱藏類,查找屬性時,只要先走到隱藏類去,再用偏移值去查找即可,所以當你的屬性順序不同,偏移值也會跟著不同,勢必得在創造一個隱藏類,大概是這樣,詳細一點的可以參照這篇這篇

WebAssembly (wasm)

簡單來講就是讓 C or C++ 跑在瀏覽器上,以提高效能,分享幾篇精華整理

Call stack & Memory heap

在提到 JS Run Time 前,先來了解一下什麼是 call stack 與 memory heap

Call stack 與其同名,當 JS 執行時 function 會進入 Stack ,不斷的堆疊上去後 pop off,Memory heap 負責分配(allocate)記憶體位置給這些執行的 function 與變數,當不停呼叫自己的 function 會造成stack overflow ,而不停地執行 function 沒有釋放內存(heap)則會導致 memory leak。

JS Run Time

JS 是個單執行緒的程式語言,代表無法同時做多個事情,所以執行的順序很重要,前面提到了 call stack 代表著進入 stack 的順序,那真正執行的順序真的是一行一行往下嗎?

setTimeout(function () {
console.log(3)
}, 0)
console.log(1)
console.log(2)

答案是123,在這裡可以看到 call stack 與 web api 的執行順序,下面的 call back queue 稱作 event loop, call stack pop off 後,事件循環會把 web api 加入事件佇列,等到 stack 執行完畢後才會執行。

NodeJS

nodejs 是個運行於 v8 engine 上的 runtime,你可以在上面執行伺服器端的程式,一樣是單執行緒,同步處理

js file = 音符
v8 engine = 作曲家
run time = 演奏者
browser = window
nodejs = global

跟瀏覽器其實大同小異,只是 window 改成 global

參考資料

--

--