Skip to content

Latest commit

 

History

History
543 lines (376 loc) · 9.07 KB

3.2._物件與函數_IIFE_closure.md

File metadata and controls

543 lines (376 loc) · 9.07 KB

物件與函數_2_IIFE_closure_函數編程

IIFE

Immediately Invoked Function Expressions (IIFE) 立即呼叫的函數表示式。

// function expression
var greetFunc = function(name){
    console.log("hello"+name);
};
greetFunc("John");
// results:
// hello John

// using IIFE
var greeting = function(name){
    return "hello" + name;
}("John");

console.log(greeting);
console.log(greeting());

// results:
// hello John
// error, greeting is a string not a function.

do nothing 的 expression

3;

"This a string";

{
    "a": "A",
    "b": "B"
};

// 執行以上程式都不會出錯,
// 但若是 function 的話:
funtion(name){
    // do something
};

// results:
//
// error
//
// 因為當 function 在該行一開始就出現時,
// JavaScript 會預期你是要寫 function statement,
// 所以它預期的東西是 function functionName(para),
// 所以會出錯。

IIFE => 將 function 包進 () 裡

要立即執行一個 function extrpeesion, 必須將 funtion 放進 () 中, 並且在最後用 () invoke 它。

var firstName = "Newt";

(function(name){
    var greeting = "hello";
    console.log(greeting + " " + name);
}(firstName));

// result:
//
// hello Newt
//
// 上面是一個典型的 IIFE

用 IIFE 建立安全的程式碼

function 中的變數與全域環境隔絕。

將引入的 lib 與全域環境隔開

// import a.js
var greeting = "hola";
// import b.js
(function(name){
    var greeting = "hello";
    console.log(greeting + " " + name);
}("John"));

// results
//
// hello John

a.js 與 b.js 彼此之間不會互相影響、衝突。

將全域環境傳入 lib 中

如果利用 IIFE 建立的 lib 想要使用全域物件, 則將 window 傳入:

var greeting = "hola";

(function(global, name){
    var greeting = "hello";
    global.greeting = "hello";
    console.log(greeting + " " + name);
}(window, "John"));

console.log(greeting);

// results
//
// hello John
// hello

這也可以用來讓 lib 的某些功能影響全域物件, 可以要露出的東西寫入 global 中。

閉包 closures

example

function greet(whatToSay){
    return function(name){
        ;console.log(whatToSay + " " + name)
    }
}

var sayHi = greet("Hi");
sayHi("Tony");

// results:
//
// Hi Tony

運作過程:

一開始,call greet() 創造出 execution context, 同時變數環境裡有 whatToSay => "Hi"。

在回傳一個 function 後, greet() 的 execution context 就結束了, 也就是離開 execution stack 了。

正常而言,execution context 結束後, 它所留下的記憶體空間會被(garbage collection)清除, 但在 closure 中的因為還被仍存在的 function 使用, 所以會留下來。

當執行 sayHi() 時, js engine 會順著 scope chain 向外尋找 whattosay 並順利找到當初在執行 greet("Hi") 時所留下的 whattosay。

closure 經典範例

for 迴圈執行函式的變數參考

function buildFunctions(){
    var arr =[];

    for(var i = 0; i < 3; i++){
        arr.push(function(){
            console.log(i);
        });
    }

    return arr;
}

var fs = buildFunctions();

fs[0]();
fs[1]();
fs[2]();

// results:
//
// 3
// 3
// 3

invoke buildFunction() 會創造 execution context, 一路執行到最後要 return arr 時, i 等於 3, 而 arr 中存有 3 個獨立的 function。

然後 buildFunction() 結束, 記憶體空間留下。

所以之後 fs[] functions 執行時, 都會參照到 i = 3。

for 迴圈紀錄記下當下的 i

承上所述, 若是我們今天是想要輸出 1, 2, 3 的話, 則有兩種解法:

利用 ES6 let
function buildFunctions(){
    var arr =[];

    for(var i = 0; i < 3; i++){
        let j = i;
        arr.push(function(){
            console.log(j);
        });
    }

    return arr;
}

var fs = buildFunctions();

fs[0]();
fs[1]();
fs[2]();
利用 IIFE 創造不同 execution context
function buildFunctions() {
    var arr = [];

    for (var i = 0; i < 3; i++) {
        arr.push(
            (function(j) {
                return function() {
                    console.log(j);
                }
            }(i))
        );
    }

    return arr;
}

var fs = buildFunctions();

fs[0]();
fs[1]();
fs[2]();

design pattern: function factories

function makeGreeting(language) {
    return function(fristname, lastname) {
        if (language === "en") {
            console.log("hello " + firstname + " " + lastname);
        }
        if (language === "es") {
            console.log("holai " + firstname + " " + lastname);
        }
    }
}

var greetEnglish = makeGreeting("en");
var greetSpanish = makeGreeting("es");

greetEnglish("John", "Doe");
greetSpanish("John", "Doe");

執行過程:

第一次 call makeGreeting()時, 會創造一個獨立 execution context 與 memory space, 裡面包含 language = "en"。


執行結束後, execution context 關閉, 但 memory space 留下。


第二次 call makeGreeting(); 再一次創造另外一個獨立的 execution context。

執行結束後, exexution context 關閉, 但 memory space 同樣會留著。


執行 greetEnglish("John", "Doe");

執行 greetSpanish("John", "Doe");

another closure example

function sayHiLater(){
    var greeting = "Hi!";
    setTimeout(function(){
        console.log(greeting);
    }, 3000);
}

sayHiLater();

// results:
//
// (after 3 seconds)
// Hi!

call(), apply() and bind()

用來操控 this 為何。

所有 function 都有這三個方法

function 是一種特殊的物件, 因為是物件所以可以含有方法, call(), apply() 和 bind() 便是 function 都擁有的方法。

bind()

this 錯的狀況:

var person = {
    firtname: "John",
    lastname: "Doe",
    getFullName: function() {

        //          這裡的 this 會指向整個 person 物件,
        //          因為它是由 person 的 method 所 invoke
        var fullName = this.firtname + this.lastname;
        return fullName;
    }
}

var logName = function(lang1, lang2) {
    console.log("Logged:" + this.getFullName());
}

logName();

// results:
//
// error
// 因為 logName 中的 this 是指向全域物件 window。

bind() 會回傳一個新函式

所以將上面的 code 改成:

var person = {
    firtname: "John",
    lastname: "Doe",
    getFullName: function() {
        var fullName = this.firtname + this.lastname;
        return fullName;
    }
}

var logName = function(lang1, lang2) {
    console.log("Logged:" + this.getFullName());
}

// 用一個變數去接新的 function
var logPersonName = logName.bind(person);
logPersonName();

// results:
//
// Logged John Doe

use bind() on the fly and pass arguments

所以將上面的 code 改成:

var person = {
    firtname: "John",
    lastname: "Doe",
    getFullName: function() {
        var fullName = this.firtname + this.lastname;
        return fullName;
    }
}

var logName = function(lang1, lang2) {
    console.log(lang1 + lang2 + this.getFullName());
}.bind(person);

logName();
logName("en ", "es ")

// results:
//
// undefined undefined John Doe
// en es John Doe

call() and apply()

不同於 bind() 是回傳一個新函式, call() 跟 apply() 會直接執行該函式, 兩者的差別在於傳參數的方式。

延續上面的例子:

var person = {
    firtname: "John",
    lastname: "Doe",
    getFullName: function() {
        var fullName = this.firtname + this.lastname;
        return fullName;
    }
}

var logName = function(lang1, lang2) {
    console.log(lang1 + lang2 + this.getFullName());
};

logName.call(person, "en ", "es ");
logName.apply(person, ["en ", "es "]);

// results
//
// en es JohnDoe
// en es JohnDoe

function borrowing

using .call() or .apply()

利用改變 execution context 的 this 來讓 object 可以借用其他 object 的 method。

var person2 = {
    firstname: "Jane",
    lastname: "Doe"
};

console.log(person.getFullName.apply(person2));

// results:
//
// JaneDoe

function currying

using .bind(),

拷貝一個 function,並可以限制它的參數。

function multiply(a, b){
    return a*b;
}

// 設定第一個參數永遠是 2
var multipleByTwo = multiply.bind(this, 2);
console.log(multipleByTwo(3));

// result:
//
// 6