or
or
By clicking below, you agree to our terms of service.
New to HackMD? Sign up
Syntax | Example | Reference | |
---|---|---|---|
# Header | Header | 基本排版 | |
- Unordered List |
|
||
1. Ordered List |
|
||
- [ ] Todo List |
|
||
> Blockquote | Blockquote |
||
**Bold font** | Bold font | ||
*Italics font* | Italics font | ||
~~Strikethrough~~ | |||
19^th^ | 19th | ||
H~2~O | H2O | ||
++Inserted text++ | Inserted text | ||
==Marked text== | Marked text | ||
[link text](https:// "title") | Link | ||
 | Image | ||
`Code` | Code |
在筆記中貼入程式碼 | |
```javascript var i = 0; ``` |
|
||
:smile: | ![]() |
Emoji list | |
{%youtube youtube_id %} | Externals | ||
$L^aT_eX$ | LaTeX | ||
:::info This is a alert area. ::: |
This is a alert area. |
On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?
Please give us some advice and help us improve HackMD.
Do you want to remove this version name and description?
Syncing
xxxxxxxxxx
JavaScript 底層機制與相關執行原理
tags:
javascript
、advanced
、concept
Hoisting
當我們輸入這樣的程式碼所得的結果會是
undefined
,很合理,因為變數a
還沒被附值。而上面這段程式碼因為 hoisting 提升的作用,其實和下面的程式碼是一樣的:
這也就是為什麼結果會是
undefined
,而不是顯示「a is not defined」的錯誤訊息。我們再以 function 來看:
結果會是
123
的字串,也就是說在呼叫函式時還沒跑到函式的那一行就有函式的作用,只因為 hoisting 的關係,所以不管 function 被放到哪裡都可以被呼叫。另外下面這種方式也是賦值的一種,因此不能看做 function 的 hoisting:
可以把它看作:
因為在第一行的時候
test
還沒被賦值,因此為undefined
,所以test()
的結果會是test is not a function
。hoisting 的順序
下面這個例子的結果是
undefined
的原因是在test
function 裡面,因為宣告了var a = 5
,導致說var a
做了一個 hoisting 的動作,再加上作用域的關係,因此接收到的a
值是undefined
,而不是全域變數的10
。上面的例子可以看成這樣:
而因為每個變數如果在被宣告的時候沒有賦值,預設值是
undefined
,所以結果才是undefined
。今天如果是放入 function:
結果會是抓到 function a,表示 function 也變數提升了。
那如果
var
和function
同時出現:結果也是抓到 function a,表示說 function 比變數宣告佔有優先權。
那如果是重複出現的 function:
則會顯示最後的 function 結果,表示後面的 function 會覆蓋掉前面一樣名稱的 function 結果。
如果同時傳參數與
var
宣告:結果則是引數傳進來的值,因此參數會優先於
var
的變數宣告,表示說這邊的變數提升:var a
實則沒什麼作用,會被忽略。這邊的
var
變數提升被忽略的理由是:當今天變數已經被前面宣告過了(這個例子是參數已經宣告過變數)的話,那麼接下來所有純粹的
var a
的變數宣告都會被無視,像下面的例子:結果只有
var a = 123
這行被採納,下面兩行的var a
在這邊沒有作用產生。但原本那個例子的順序如果換一下就會被
var
的值(後來新增的值)覆蓋:那如果傳參數的同時,存在 function:
結果會是抓到 function,表示 function 的提升順序優於參數。
總結上面我們可以知道 hoisting 順序為:
而 Hoisting 底層的運作實際上與 Execution Context 有關,詳情請見參考資料。
參考資料:
我知道你懂 hoisting,可是你了解到多深?
Closure
直接看範例:
Closure 主要目的是為了不讓變數值被外面其他的變數或是因重複命名而影響到,所以利用 function 的 Closure 特性把所有涵蓋的變數值給包起來。
如果不包起來則會是這樣:
雖然一樣可以運作,但是在
counter()
function 外面的count
變數值可能就會被更改到或者是因重複命名而有所影響再舉一個運用閉包特性的例子:
藉由閉包,也就是 cache function 回傳的 function,這個 function 可以判定是否已經計算過這個值了,如果已經計算過了,那就直接輸出結果,而不是再回到
vol
function 重新計算,因此在上面console.log()
的結果會發現只有第一次進到vol
function 計算數值,後面再呼叫cacheVol
傳回來的值實際上都是已經儲存在cache
function 的obj
物件裡面,只是單純從物件裡面取出值來而已。而這樣的好處有幫助程式執行運算的速度,假如vol
function 今天是個裡面佈滿複雜的運算才有辦法得出結果的話,每次我們要拿到vol
function 的結果就得需要經過這道複雜的運算才有辦法拿到。我們藉由閉包的特性,使用cache
function 傳入 function 參數進去(這邊即為vol
),變成我們可以靈活運用這個輸入進來的 function ,可以決定我們何時使用它,有一種工具包裡面的其中一項工具的概念,需要用到的時候就拿出來,不需要的時候就簡便處理,而且就算不需要的時候這個工具一直都放在裡面,這也是閉包的特性,執行過的 function 卻依然存在。參考資料:
所有的函式都是閉包:談 JS 中的作用域與 Closure
Prototype
上面
console.log
的結果為False
,因為兩次呼叫物件的記憶體是不同的。但是如果我們使用
prototype
的話,兩次呼叫就會是一樣的,結果會是True
,程式碼如下:那麼為什麼會一樣?因為
nick.getName === Person.prototype.getName
,或是mike.getName === Person.prototype.getName
,也就是說都會是相等的。實務上會先去尋找
Person
這個 instance 有沒有getName
的方法,假如沒有,它就會往prototype
當中去尋找,那它為什麼會有這個特性?因為其實在每個 instance 被建立的時候,javascript 的底層都會偷偷建立一個叫做Person.__proto__
的方法,而這個方法等於Person.prototype
。那如果在
Person.__proto__
找不到getName
的方法的話,那又會繼續往下找,也就是Person.__proto__.__proto__
,而這個方法等於Person.prototype.__proto__
,也等於Object.prototype
也就是說 protoype 的尋找順序為:
nick
nick.__proto__
(=Person.protoype
)nick.__proto__.__proto__
( =Person.prototype.__proto__
=Object.prototype
)nick.__proto__.__proto__.__proto__
(這層即為底層,顯示出來的結果為 null)這種一層一層一直尋找底層有沒有相符的物件的機制,叫做「原型鏈(prototype chain)」。
而 ES5 以前因為沒有
class
的語法,所以是以prototype
的方式取代class
的method
設定(如上方例子的Person.prototype.getName = function() {}
),更深入探討,其實 ES6 之後新增的class
語法它的底層運作其實就是prototype
機制。延伸閱讀
該來理解 JavaScript 的原型鍊了
程式執行原理
Call Stack
以 Stack 資料結構為原理的程式執行方式。
比如:
這段程式碼會先 call
function a
,再來 callfunction b
,再來 callfunction c
,接著console.log('123')
。以 stack 來看,就是這樣的順序進入 call stack: a->b->c->console.log('123');然後再以這樣的順序離開 call stack:console.log('123')->c->b->a。
執行環境(Execution Context:EC)
在 Javascript 的程式碼執行順序,可能不是一行一行執行的,而是由編譯→執行這兩個順序來完成,否則 hoisting 的機制沒辦法做解釋。雖然 javascript 常被稱作「直譯語言」,可是並不是說它所有的機制都是直譯的,這邊我們要介紹的這個機制就是其中之一由編譯→執行這兩個動作所運作,有關於執行環境(EC)的機制。
以上面的 call stack 為例,其實每個 function 的 stack 都有單獨的執行環境,可以看做有 EC of function a、EC of function b、EC of function C,另外還有個 global EC 處理非函數的執行環境,像是變數之類的。
可以把 EC 想像為 Javascript 的物件,每個 EC 當中有這樣子的結構:
詳細的結構:
arguments
為 function 的參數,scopeChain
就像是 prototype chain 那種結構,如果找不到,那就一層一層往上找的結構。(this
這邊先跳過,之後有機會補)