iT邦幫忙

1

JavaScript 有無 IIFE 大不同?

大家好,下面一段程式碼是 typescript五分鐘教學 的範例程式碼編譯後的 JS。

// 重點在下面
var Student = /** @class */ (function () {
    function Student(firstName, middleInitial, lastName) {
        this.firstName = firstName;
        this.middleInitial = middleInitial;
        this.lastName = lastName;
        this.fullName = firstName + " " + middleInitial + " " + lastName;
    }
    return Student;
}());
// 重點在上面

function greeter(person) {
    return "Hello, " + person.firstName + " " + person.lastName;
}
var user = new Student("Jane", "M.", "User");
document.body.innerHTML = greeter(user);

想請教一下,

  1. /** @class */ 是什麼,為什麼要加這麼多的符號,還有class要加"@"?
  2. 為什麼變數Student要用 IIFE 然後返回函式Student,這樣不是很麻煩嗎?我乾脆把它改成如下,經過測試後,也是行得通的。
var Student =  function Student(firstName, middleInitial, lastName) {
        this.firstName = firstName;
        this.middleInitial = middleInitial;
        this.lastName = lastName;
        this.fullName = firstName + " " + middleInitial + " " + lastName;
    }
fillano iT邦超人 1 級 ‧ 2018-12-18 17:52:34 檢舉
https://stackoverflow.com/questions/47998042/does-typescripts-class-have-a-purpose

我google了一下...關鍵字是「typescript @class」
Peter iT邦新手 5 級 ‧ 2018-12-19 00:34:49 檢舉
感謝,我來去瞧一瞧~

2 個回答

2
wingkawa
iT邦新手 4 級 ‧ 2018-12-19 09:28:38
  1. 那只是註解而已,但註解也有一定格式的「規範」建議開發者遵循(像是jsdoc、PSR),有助於閱讀理解,也便於某些文件產生器等等的工具能夠讀取。
  2. 用IIFE是為了避免汙染到全域變數
// 這是你的程式,你已經寫了一個Student了
function Student(firstName, middleInitial, lastName) {
        this.firstName = firstName + "This is my function";
        this.middleInitial = middleInitial;
        this.lastName = lastName;
        this.fullName = firstName + " " + middleInitial + " " + lastName;
    }
    
// 然後你載入一個外部程式,他也有一個Student
// <script type="text/javascript" src="AnotherStudent.js"></script>
function Student(firstName, middleInitial, lastName) {
        this.firstName = firstName + "This is another function";
        this.middleInitial = middleInitial;
        this.lastName = lastName;
        this.fullName = firstName + " " + middleInitial + " " + lastName;
    }

然後就爆炸了,其中有一個Student會被蓋掉。
所以你若去看一些常用的套件工具,像是jQuery,會發現他們程式都會用IIFE給包起來。

// 像這樣,最後會回傳一個不同的Student給你用
// 但又不會把你自己寫的Student給蓋掉
// 或是你蓋掉他的
// <script type="text/javascript" src="AnotherStudent.js"></script>
var anotherStudent = (function () {
    function Student(firstName, middleInitial, lastName) {
        this.firstName = firstName + "IIFE";
        this.middleInitial = middleInitial;
        this.lastName = lastName;
        this.fullName = firstName + " " + middleInitial + " " + lastName;
    }
    return Student;
}());

// IIFE
var user1 = new anotherStudent("Jane", "M.", "User");
// Function
var user2 = new Student("Jane", "M.", "User");

大概就是這樣,我說明的可能不是那麼明確,你可以再找找其他介紹IIFE用法的心得看看。
重點就是避免汙染到全域變數。

看更多先前的回應...收起先前的回應...
Peter iT邦新手 5 級 ‧ 2018-12-19 15:34:52 檢舉

Hi,wingkawa,感謝你回答,我剛學程式碼,希望可以教學相長,一起來討論一下吧

Peter iT邦新手 5 級 ‧ 2018-12-19 15:35:05 檢舉

我剛做了一個錯誤示範,不曉得是不是你要表達的,如下

// 先聲明一個 Student 函式
function Student(firstName) {
    this.firstName = firstName + "尚未改變";
}
// 不使用 IIFE
var anotherStudent = function Student(firstName) {
    this.firstName = firstName + "已經改變";
}
   
// 不使用 IIFE
var user1 = new anotherStudent("Jane");
// Function
var user2 = new Student("Jane");
// 測試 Student 結果有沒有受到改變
console.log(Student); // "... 尚未改變"
Peter iT邦新手 5 級 ‧ 2018-12-19 15:42:38 檢舉

我有再去研究一下IIFE,這邊可以發現其實沒有汙染,我在猜,注意!以下都是假設

對於anotherStudent 來說,它被賦予的函式其實跟匿名函式是一樣的,或者說因為我有var anotherStudent,在此前提,記憶體存放的位置是不一樣的,所以有沒有IIFE是不會有影響的。

不曉得你可不可以提出個錯誤範例~/images/emoticon/emoticon41.gif

fysh711426 iT邦研究生 4 級 ‧ 2018-12-19 20:13:42 檢舉

https://blog.gtwang.org/programming/defining-javascript-functions/

var anotherStudent = function Student(firstName) {
    this.firstName = firstName + "已經改變";
}

上面這個寫法好像叫做 named function expression

wingkawa iT邦新手 4 級 ‧ 2018-12-20 15:01:07 檢舉

欸...其實像這樣寫

var anotherStudent = function Student(firstName) {
    this.firstName = firstName + "已經改變";
}

你會發現,如果你要使用function時,其實是用anotherStudent()而不是Student()(用Student()會告訴你not defined)

var user1 = new anotherStudent('Peter'); // { firstName: 'Peter'}
var user2 = new Student('Peter'); // Student is not defined

換言之後面的 = function Student(firstName) {是多寫的。

所以你的範例其實跟這個一樣

// 先聲明一個 Student 函式
function Student(firstName) {
    this.firstName = firstName + "尚未改變";
}
// 不使用 IIFE
function anotherStudent(firstName) {
    this.firstName = firstName + "已經改變";
}
   
// 不使用 IIFE
var user1 = new anotherStudent("Jane");
// Function
var user2 = new Student("Jane");
// 測試 Student 結果有沒有受到改變,因為本來就是使用不同的 function
console.log(Student); // "... 尚未改變"

至於污染的問題,看看這樣有沒有比較好理解。
首先有一個前提,那就是開發程式往往是很多人協作,所以程式能模組化是更好的。

這裡是你的主程式Student.js

function Student(firstName, middleInitial, lastName) {
    this.firstName = firstName;
    this.middleInitial = middleInitial;
    this.lastName = lastName;
    this.fullName = firstName + " " + middleInitial + " " + lastName;
}

// 你會用這個函式來 new 一個學生出來
var student = new Student("Jane", "", "M.");
// student.fullName = "Jane  M."

使用起來一切都很美好,但有一天你的同事寫了另一支程式StudentExpand.js,提供一些你原本沒有的功能

/**
一些其他函式,像是輸入學生成績之類的
*/

// 其中有一個函式,和你的函式同名
function Student(firstName, middleInitial, lastName) {
    this.firstName = firstName;
    this.middleInitial = middleInitial;
    this.lastName = lastName;
    // fullName 組合方式和原本不同
    this.fullName = lastName + " " + middleInitial + " " + firstName;
    
    // 有一些紀錄成績的屬性
    this.gradeCode = 0;
    this.gradeMath = 0;
}

你會在html裡載入他的程式來使用

<!-- 這是你的 -->
<script type="text/javascript" src="/Peter/Student.js"></script>
<!-- 這是同事的 -->
<script type="text/javascript" src="/wingkawa/Student.js"></script>

然後一切都不美好了,你new出來的學生變成壞學生跟你唱反調
(原本很乖的,一定是交了壞朋友)

var student = new Student("Jane", "", "M.");
// student.fullName = "M.  Jane"

而IIFE,是能夠讓兩個人的程式能夠不起衝突的其中一種方式
/wingkawa/Student.js

var WStudent = (function(first, middle, last) {
    function Student(first, middle, last) {
        this.fullName = last + " " + middle + " " + first;
        /**
        當然其他屬性和一些有的沒的
        */
    }
    return Student;
})();

這麼一來,你的學生還是你的學生,他的學生就是他的學生,彼此都是好學生

var student = new Student("Jane", "" ,"M.");
var wStudent = new WStudent("Jane", "", "M.");

看起來好像請你同事換一個名稱就行了,但原本他整個Studetn.js檔裡面是裸身的,一大堆 function 都赤裸裸地開趴,要人工檢查每個function name有沒有跟你重複,有重複就要改,很不容易吧?

sevenpo iT邦新手 5 級 ‧ 2018-12-20 17:28:38 檢舉

第一點,其實觀念上有些不對。

var anotherStudent = function Student(firstName) {
    this.firstName = firstName + "已經改變";
}

anotherStudent在這邊是變數,可以指向各種物件,然後上面的範例指的是將Student這個function放在anotherStudent這個變數之中(指向Student這個function),所以你的文件裡面有宣告的是anotherStudent而Student這個function是被放在變數裡面的,所以你沒辦法直接呼叫他,必須先使用anotherStudent這個變數,把Student這個function取出來,然後用小括號呼叫他執行。

可以試著將 anotherStudent console.log()出來,可以看到他是一個function。

第二點,你後面舉的例子之所以不打架,是因為你把變數名稱區別開來了,不是因為你用IIFE的方式,你new的東西本身就不一樣了,兩個東西是不會污染的,會污染的是裡面的變數,如果跟你全域的變數名稱相同的時候,可能就會互相汙染。

一些見解提出來討論。有錯誤再麻煩指正囉。

wingkawa iT邦新手 4 級 ‧ 2018-12-20 18:25:05 檢舉

第一點你說的對,是我太不嚴謹了/images/emoticon/emoticon41.gif

關於第二點,其實我想說的是,引入外部程式時,若外部程式沒有多包一層處理,會很容易發生命名衝突的問題。
不過老實說,我自己也搞得很混亂了XD

這兩天一直在想變數污染的問題,但在function內宣告的變數,其實都只作用在function內,根本不會影響到外面不是嗎?

var aVar = 'i am a variable';
function foo() {
    var aVar = 'another variable';
    var bVar = 'test';
}
foo();
console.log(aVar); // i am a variable
console.log(bVar); // not defined

若不是function,就有影響了

var aVar = 'i am a variable';
if (true) {
    var aVar = 'another variable';
    var bVar = 'test';
}
console.log(aVar); // another variable
console.log(bVar); // 然後全域變數會多長一個bVar出來

let就可限定變數在scope內

var aVar = 'i am a variable';
if (true) {
    let aVar = 'another variable';
}
console.log(aVar); // i am a variable
sevenpo iT邦新手 5 級 ‧ 2018-12-21 08:47:28 檢舉
var aVar = 'i am a variable';
function foo() {
    aVar = 'another variable';
    bVar = 'test';
}
foo();

試試看這樣,如果在function裡面你有var的話,的確只在裡面有效,但如果你沒有用var宣告,那他就會往外面一層去找有沒有這個變數,所以是會污染的唷~

wingkawa iT邦新手 4 級 ‧ 2018-12-21 09:04:33 檢舉

對啊,但是IIFE同樣也有這樣的問題

var aVar = 'i am a variable';
(function() {
    aVar = 'another variable';
    bVar = 'test';
})()
console.log(aVar); // another variable
console.log(bVar); // test

想想也是很正常,javascript會去找上一層,而上一層就是window

所以我才會開始混亂,啊不是說好的IIFE可以處理變數污染的問題嗎?
後來去查一下ES6改變了什麼,才發現似乎ES6已經把這些問題處理掉了
http://es6-features.org/#BlockScopedVariables

所以IIFE很單純就是一個會「立即執行的匿名函式」而已。

2
sevenpo
iT邦新手 5 級 ‧ 2018-12-19 16:55:07
class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }
    greet() {
        return "Hello, " + this.greeting;
    }
}

class Greeter2 extends Greeter { };

把這段扣拿去編譯以後,會發現這樣很容易實現繼承的概念。

看更多先前的回應...收起先前的回應...
Peter iT邦新手 5 級 ‧ 2018-12-19 17:42:58 檢舉

我不太懂 extends,且那邊第一次看到 Greeter{} 而不是 Greeter(),給我一些時間研究一下。

sevenpo iT邦新手 5 級 ‧ 2018-12-20 11:10:01 檢舉

抱歉,沒有回答的很完整,extends是物件導向的繼承概念,可以朝這個方向查詢一下。

簡單的講,就是假如說你今天要做一個動物園,裡面有Dog、Cat、Bird的Class。

每個動物都有相同的屬相像是: name、sex、color
但每個動物也都有不同的技能。

這個時候你就可以先寫一個Animal的Class作為父類別,
把共通的屬性寫在Animal這個Class上面,
其他動物只要繼承Animal這個類別,就可以有共通的方法,這個時候再去寫不同動物的技能,可以減少code的重複撰寫。

class Animal {
    name: string;
    color: string;
    sex: string;

    constructor(name: string,color: string,sex: string) {
        this.name = name;
        this.color = color;
        this.sex = sex;
    }
}

class Dog extends Animal {
    bark() {
        console.log('汪汪!')
    }
}

var dog = new Dog('schnappi', 'whit', 'male');

console.log(dog.name)   // schnappi
dog.bark();             // 汪汪!

不知道這樣有沒有解答到你的問題

Peter iT邦新手 5 級 ‧ 2018-12-20 12:05:19 檢舉

喔喔~原來是這樣的概念,就是extend(複製、延伸)原本的屬性,然後還可新增方法或是物件屬性。
不知道什麼是物件導向,但是我的理解是這很模組化!

其實我一開始是跑去看他編譯後的JS,我完全矇掉了,所以才想說需要一點時間去研究 ( 跑去研究它編譯後的JS... XD

這解釋真是太棒了,我相信這解釋對有相同需求的人也會有幫助的!

Peter iT邦新手 5 級 ‧ 2018-12-20 12:08:51 檢舉

有趣的是,我把它去掉 TS 的部分,然後在 JS 上竟然可以應用,看來 TS 的某些優勢也被原生給取代掉了呢

fysh711426 iT邦研究生 4 級 ‧ 2018-12-20 16:28:14 檢舉

幫貼編譯後的程式 /images/emoticon/emoticon16.gif

var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var Greeter = /** @class */ (function () {
    function Greeter(message) {
        this.greeting = message;
    }
    Greeter.prototype.greet = function () {
        return "Hello, " + this.greeting;
    };
    return Greeter;
}());
var Greeter2 = /** @class */ (function (_super) {
    __extends(Greeter2, _super);
    function Greeter2() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    return Greeter2;
}(Greeter));
;

我要發表回答

立即登入回答