JavaScript 語言特性之先 ++ 跟後 ++ 到底哪裡不同?

今天我在看 Angular 的原始碼的時候我發現了一段程式碼如下:

1
2
3
4
5
6
let _nextReactiveId: number = 0;

export abstract class ReactiveNode {
private readonly id = _nextReactiveId ++;
// 以下省略
}

然後我就覺得,咦?!第一次看到別人這樣用(被發現我很少看別人的程式碼了),然後就很好奇所以這樣 id 會是從多少開始、然後也很好奇如果改為使用 ++ _nextReactiveId 或是 _nextReactiveId += 1 的話會怎樣,所以我就做了一下實驗,並且分享給大家!

實驗 1 - i += 1

首先我們一樣先宣告一個變數 i 等於 0 :

1
let i = 0;

然後宣告一個變數 a 等於 i += 1 之後的結果:

1
const a = i += 1;

接著把它印出來看看:

1
2
console.log(a) // 1
console.log(i) // 1

從結果回推,看起來 i += 1 這部份會先運算 (為避免有人不知道,提示一下:i += 1 ⇒ i = i + 1),然後變數 a 會再把變數 i 的值給記錄下來,所以最終兩個變數的值都會是運算完之後的結果。

實驗 2 - i ++

再來是第二種方式 - i ++ ,我們一樣宣告一個變數 b 來記錄 i ++ 之後的結果:

1
const b = i ++;

猜猜看,bi 的值分別會是什麼呢?(小提示,剛剛 i 的值已經變為 1 了唷!)

1
2
console.log(b) // 1
console.log(i) // 2

看到結果的時候,大家是不是跟我最初剛知道這件事情的時候一樣驚訝呢?

從結果回推之後我們了解到,原來這樣子使用時, b 會先把 i 的值給記錄下來,然後 i 再執行如同 i += 1 的運算,所以 b 的值會是 i 運算前的值,而 i 則會記錄運算後的值。寫成程式碼的話有點像是這樣:

1
2
const b = i;
i += 1;

所以實驗到這邊我就明白為什麼 Angular 的原始碼會那樣寫了!

實驗 3 - ++ i

最後,我們來看看 ++ i 吧!宣告一個變數 c 用以記錄 ++ i 之後的結果:

1
const c = ++ i;

大家再來猜猜看 c 跟 i 的值分別會是什麼吧!(小提示:i 的值剛剛已經變為 2 囉!)

1
2
console.log(c) // 3
console.log(i) // 3

大家都有猜到嗎? ++ i 的結果跟 i += 1 是一樣的,都是先運算,再記錄值。

結論

從實驗結果來看,我們可以下一個結論:「在這個使用情境中,使用 i += 1 等於使用 ++ i 不等於使用 i ++

而在此之前,雖然我知道這三種方式有所差異,但其實並沒有真的很清楚的知道差異的所在,而最近自己也在改變自己讀書與學習的方式(其實也就只是好好做筆記,建立自己的知識庫罷了),希望這一點一滴的累積,都可以幫助到自己,也幫助到大家。

JavaScript 小技巧:數字字串補零時超好用的原生方法 - padStart

今天我想跟大家分享一個數字字串補零時超好用的原生方法: padStart;

當然所謂的「數字字串補零時超好用的原生方法」這件事情是我自己覺得最適合用它的應用場景,而如果你有想到更適合的應用場景也拜託請跟我分享!!

假設我們今天收到需求是不管是 0~999 之間的任何一個數字,都要以三位數的形式顯示給使用者看。例如:

  1. 如果資料是 9 的話,要顯示 009
  2. 如果資料是 99 ,要顯示 099
  3. 如果是 999 ,就顯示 999

所以,以前的我們大概會這樣寫:

1
2
3
4
5
6
7
8
9
// 隨機產生一個 0~999 的數字
const randomNum = Number((Math.random() * 1000).toFixed());

let numString = `${randomNum}`;
if (randomNum < 10) {
numString = `00${randomNum}`;
} else if (randomNum < 100) {
numString = `0${randomNum}`;
}

如果今天需求是需要一個超長的位數的話,那就會有很多個 if/else ,看起來是不是很笨?!是不是很醜?!

但有了 padStart 之後,我們只要這樣寫:

1
const num = (Math.random() * 1000).toFixed().padStart(3, "0");

一行就解決了,是不是超讚的?!!!

不管需求要幾位數,都只要改那個 3 就好,而如果不想補 "0" ,想補 "*" 也沒問題,把 "0" 改成 "*" 就好,方便的不得了!!

而這個方法在各大瀏覽器的支援程度高達 95.72% ,所以各位可以放心使用!!

瀏覽器支援程度

如果你還要支援 IE 的話就抱歉了XD

同場加映

聰明的你一定會想,既然有 padStart ,那是不是也有 padEnd

沒錯,其使用的方式一模一樣,只是改為在後面補上你想要補的字而已!!

像是:

1
2
3
4
const name = 'Leo';
const result = name.padEnd(5, "a");

console.log(result); // 'Leoaa'

是不是非常簡單呢?!

今天的 JavaScript 小技巧就到這邊囉,我們下次見,掰掰!

Reference

用 RxJS 翻轉你的 coding 人生 - 以 Timer 為例

自製梗圖

昨天同事在某個頁面裡新增了一個「背景自動刷新頁面」的功能,而我在幫他 code review 之後趕緊請他改用 RxJS 實作這個功能,這是為什麼呢?

沒有要鞭同事的意思,純粹是藉機分享!

一般實作方式

一般用 JavaScript 實作的計時器大概會長這樣:

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
var REFRESH_PERIOD_SECOND = 30;

var timer;
var counter = 0;

function startTimer() {
timer = setInterval(function () {
counter += 1;
if (counter >= REFRESH_PERIOD_SECOND) {
// do something..
resetTimer();
}
}, 1000);
}

function resetTimer() {
stopTimer();
startTimer();
}

function stopTimer() {
clearInterval(timer);
counter = 0;
}

startTimer();

完整程式碼範例: JS Bin

用 RxJS 的實作方式

換用 RxJS 來實作的話大概會長這樣(以在 Angular 裡面實作為例):

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
@Component({
// ...
})
export class AppComponent implements OnInit {
count = 0;

private readonly REFRESH_PERIOD_SECOND = 30;

private start$ = new Subject();
private stop$ = new Subject();

private timer$ = this.start$.pipe(
switchMap(() => interval(1000).pipe(map((count) => count + 1))),
tap((count) => (this.count = count)),
filter((count) => count === this.REFRESH_PERIOD_SECOND),
takeUntil(this.stop$),
repeat()
);

ngOnInit(): void {
this.timer$.subscribe(() => this.resetTimer());
this.startTimer();
}

startTimer(): void {
this.start$.next(null);
}

stopTimer(): void {
this.count = 0;
this.stop$.next(null);
}

resetTimer(): void {
this.stopTimer();
this.startTimer();
}
}

完整程式碼範例: Stackblitz

所以說,改用 RxJS 來實作到底好在哪裡呢?

我個人覺得有以下三個好處:

  1. 計時器要做的事情在初始化的時候就做好了,後續不用再重做第二次
  2. 由於有上一點的好處,所以實際在執行動作的時候,就只要用 start$stop$ 送出通知即可。
  3. 更好閱讀與更美觀

你覺得呢?

JavaScript 之那些年我們都腦補錯的事

原來都是我腦補補錯了

我有一個很尊敬的老師兼程式入門導師 ─ 馬老師,他曾經這樣跟我說過:

電腦很乖很聽話,你叫他往東他不會往西,如果它做錯了那一定你的錯!

這句話我一直放在心上並引以為鑑,所以凡遇到錯誤我一定會先仔細檢查個兩三遍,因為用膝蓋想都知道一定是我的錯,哈哈!

後來我在上另一位我同樣也很尊敬的老師兼人生導師 ─ 保哥的 JavaScript 核心教戰時,他曾經這樣跟我說:

我們在寫 JavaScript 的時候常常自己在腦補,但我們自己常常腦補錯。

所以我為什麼會寫這篇文呢?

事情是這樣子的,今天我在 Angular Taiwan 社群時看到有人發文提問:

群友提問的問題

其實我個人覺得這是一個滿好的問題,怎麼說呢?

因為這個問題其實是出在對於 JavaScript 執行機制的認知不夠深刻所造成的 「腦補補錯」 的問題。

而我個人其實有的時候也會犯這種錯誤,曾經我異想天開地想在前端做一個讓所有頁籤都能夠在同一個時間點(例如整點)才發出事件的功能,但最後就因為這個 JavaScript 的執行機制導致它一定會有誤差,這個誤差時間會隨著頁面的忙碌程度而有所不同,再加上實在是效能太差(因為每毫秒都要檢查一次),所以最終宣告失敗。

總之,藉著這個機會發文來分享 JavaScript 的執行機制給大家,已經知道的朋友們可以複習一下,而不知道的朋友們則從中可以學到新知識,希望大家都能夠從中獲得些什麼。

順帶一提,此文發佈前已徵得該群友的同意,讓我可以將他在群中提問的問題分享給大家。

不過他說他很期待我的文,希望他看到此文不會太過於失望,因為我只是單純的經驗分享,因為我覺得參考連結的影片已經講得非常詳細了 ^^”

Reference

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×