JS 紀錄5 - function

JavaScript function 紀錄1

function 背後運算的邏輯

  • 只要使用 function Global Execution Context(執行全域上下文)就會被建立,這時候會一併建立 this,global object (window)

函式物件(function)

  • 就是一般物件外加可以被呼叫的能力
  • 將 function 儲存成變數
  • function 當成參數代入另一個 function 中
  • 在 function 中回傳另一個 function
  • 分割片段功能 & 多層級組合
1
2
3
4
var a = function () { } 
console.log(typeof (a)) // function
a.x = 1;
console.log(a.x) // 1
  • 特色
    • 一級物件 (First-class object)
      • 可以被動態建立、可以指定給變數、可以複製給其他變數
      • 可以擁有自己的屬性或方法 (一般物件特性)
    • 提供了變數的作用域 (Scope)
  • 語法
    • name
      • 函式名稱
    • param
      • 要被傳入函式的引數名稱,一個函式最多可以擁有 255 個引數
    • statements
      • statements 構成了函式內容的陳述式
1
2
3
function name([param[, param[, ... param]]]) {
statements
}

function expressions 和 function declaration - 函數表達式&函數語句

  • 有什麼特性
    • 建立函式的方式
  • 判斷
    • expressions
      • 存成一個變數 hoisting 會產生錯誤
      • 無法立即知道該匿名函式的功能,可讀性較差
    • declaration
      • 匿名函式不會直接回傳任何的值
      • 最開始該函式就會透過 hoisting 先被儲存在記憶體中
函式表達式(function expression) 函式宣告式(function declaration)
具名函式 var add = function add(a, b) { return a + b}; add.name // add function add(a, b) { return a + b }; add.name // add
匿名函式 var add = function (a, b) { return a + b }; add.name // add function (a, b) { return a + b } // Uncaught SyntaxError

hoisting

  • 有什麼特性
    • 變數和 function 的宣告在編譯階段中先被放入記憶體,實際在程式碼中位置還是一樣
    • 當 function 與變數/常數同時提升時,function 的優先程度高於變數/常數
    • 如果 hosting 時有兩個 function,最後的 function 會被 hosting
  • 判斷
    • function 會被完全提升 所以能呼叫
    • 變數宣告 只有所進行的指定(assignments)動作不會提升 所以產生錯誤
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 全域變數
var num =5;
// 區域變數
function test(){
var a = 3
}
console.log(a) // 區域 is not defined 取不到 function 變數
console.log(num) // 全域 5

/*----------------------------------
var a = 1
fnc() // hosting 呼叫 function
function fnc(){
console.log(a) // hosting undefined
var a = 2
console.log(a) // 2
}
console.log(a) // 1

Scope - lexical scoping(語彙範疇)

  • 「鴨子轉圈圈」就是把大家都匡在這裡,好好待不要亂跑,想出去可是有條件的
  • 有什麼特性
    • 「切分變數有效範圍的最小單位是 “function” 」= scope
    • 避免相同變數所造成的衝突,這當中包含了避免污染全域命名空間和模組的管理
  • 判斷
    • 能夠被存取的位置
      • global
      • function
    • 找尋一個變數的 lexical scoping rule
      • 會從此變數所存在的 function 開始找,如果找到了就返回該變量,如果找不到就往上一層繼續找以此類推直到 global 的環境
    • scope chain 範圍鏈是在函式被定義的當下 決定的,不是看呼叫(return)的時候決定
      • 可以找到上一層變數&function
        • function 的 execution context 已經離開 execution stack 了,但這個 execution context 在記憶體中所建立的位置並沒有消失,因此 JS 仍然可以透過 scope chain 找到變數(scope chain 跟變數 close 在一起)而這樣的現象就稱為 closure
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
let a = 6
function foo(x){
let b = x * 3
function bar(y){
let c = y * b
return c
}
return bar(b)
}
console.log(foo(a))

gobale scope: a,foo()
foo scope: x,b,bar()
bar scope: y,c

以 bar( ) 來說在這 scope 裡面找變數 b,找不到往 foo( ) 找,找到回傳 b = x * 3
往上找的過程叫 scope chain 範圍鏈是在函式被定義的當下決定的,不是在被呼叫(return)的時候決定

/*----------------------------------
function foo() {
var secretData = 'HelloWorld';
function bar() {
return secretData.split('').join('-');
}
return {
bar
}
}
var baz = foo();
var publicData = baz.bar();

console.log(publicData); // H-e-l-l-o-W-o-r-l-d
console.log(secretData); // Uncaught ReferenceError

closure

  • 有什麼特性
    • 不想讓變數直接被操作或修改
    • 避免變數汙染全域的問題 - IIFE 的應用
    • 透過 closure 讓 function 能夠有 private 變數,讓變數保留在該 function 中避免暴露在 global 導致運算出錯
    • closure 會 keep 住變數佔用記憶體空間,如果這變數沒有在使用 JS 會自己回收
  • 判斷
    • 運用在巢狀函式的定義中
    • function 內 return 另一個 function,通常就是有用到 closure 的概念
    • 每執行一次 function,就會產生一個新的 execution context,而且即使有多個參數值被儲存在記憶體中,JS 會自己找到屬於該 execution context 的變數
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// 基本 clouser 範例
function add(a){
return function (b){
return function (c){
return a+b+c
}
}
}
add(1)(2)(3) // 6

/*----------------------------------
// 基本 closure 共用 private function
// 運用的是同一個 bar() 時,變數間也都是獨立的執行環境不會汙染全域
function foo(name) {
var x = "hi”;

// 真正執行的 function
function bar() {
console.log( x +" "+name) ;
}
return bar; // 取不到變成 private === closure
}
let a = foo('a');
let b = foo('b');
a() // hi a
b() // hi b

/*----------------------------------
// 寫法簡化
// 不必為裡面的函式命名,可用 return 匿名函式的方式直接把它回傳出來
function foo(name) {
var x = 'hi';

// 真正執行的 function
return function() {
console.log( x +' '+name) ;
}
}
let a = foo('a');
let b = foo('b');
a() // hi a
b() // hi b

/*----------------------------------
// 共用一個環境 在 closure 中使用迴圈
// 用 IIFE (closure方式)可解
// 用 bind 指定 this 可解

for(var i =0;i<5;i++){
setTimeout(function(){
console.log(i)
},1000) // 5 5 5 5 5
}

/*----------------------------------
// 改成 IIFE(closure的應用)
for(var i =0;i<5;i++){

// 外部函數被呼叫,並把 i 作為它第一個參數,此時函數內 e 變數就擁有了一個 i 的拷貝
(function(e){
setTimeout(function(){
console.log(e)
},1000)
})(i) // 0 1 2 3 4
}

/*----------------------------------
// 為了瀏覽器兼容性用 bind 指定
for(var i =0;i<5;i++){
setTimeout(function(e){
console.log(e)
}.bind(this,i),1000) // 0 1 2 3 4
}

/*----------------------------------
// 用 let 的特性鎖住區塊
for(let i=0;i<5;i++){
setTimeout(()=>console.log(i),1000) // 0 1 2 3 4
}
  • 範例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// 點擊按鈕取出對應的文字
<button id="first">First</button>
<button id="second">Second</button>
<button id="third">Third</button>

// 一般寫法
var buttons = document.querySelectorAll('button')
for (var i = 0; i < buttons.length; i ++) {
// buttonName 暴露於 global
var buttonName = buttons[i].innerHTML
buttons[i].addEventListener('click', function(){
console.log(buttonName) // Third Third Third
})
}

/*----------------------------------
// es5 建立一個 closure 把資料存在這個 function 當中
var buttons = document.querySelectorAll('button')
for (var i = 0; i < buttons.length; i ++) {
// 要執行的
var buttonName = buttons[i].innerHTML
buttons[i].addEventListener('click', saveButtonName(buttonName),false)
}

// 建立 closure 簡化寫法
// buttonName 被儲存在 closure 當中
// buttonName 參數傳入要監聽的 saveButtonName function
function saveButtonName(e){
return function(){
console.log(e) // First Second Third
}
}

/*----------------------------------
// IIFE(closure)
var buttons = document.querySelectorAll('button')
for (var i = 0; i < buttons.length; i ++) {

(function(e){
buttons[i].addEventListener('click', function(){
console.log(e) // First Second Third
})
})(buttons[i].innerHTML)

}

/*----------------------------------
// es6 用 let 建立到一個新的記憶體位置,因此最後指稱到的地方會是不一樣的
var buttons = document.querySelectorAll('button')
for (let i = 0; i < buttons.length; i ++) {
// 每次記錄到不同記憶體位置
let buttonName = buttons[i].innerHTML
buttons[i].addEventListener('click', function () {
console.log(buttonName) // First Second Third
})
}

return

  • 終止函式執行 & 指定函式返回的值
  • 沒有 return 陳述式(或者光是 return,沒有值),JavaScript 便會傳回 undefined
  • 呼叫 return 的地方後,函式會立即停止
  • return [[expression]]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// return 沒有值為 undefined
function getRectArea(width, height) {
if (width > 0 && height > 0) {
return width * height;
}
return ;
}
console.log(getRectArea(3, 4)); // 12
console.log(getRectArea(-3, 4)); // undefined

/*----------------------------------
// 呼叫 return 停止動作
function counter() {
for (var count = 1; count++) { // 無限迴圈
console.log(count + 'A'); // 直到 5
if (count === 5) {
return; // 終止迴圈
}
console.log(count + 'B'); // 直到 4
}
console.log(count + 'C'); // 永不顯示
}
counter(); // “1A” “2A” “2B” “3A” “3B” “4A” “4B” “5A"
0%