身為 JS 開發者,你應該要知道的記憶體管理機制

更新於 2023/08/25閱讀時間約 23 分鐘
raw-image

如果你是寫 C/C++ 的開發者,應該對記憶體管理並不陌生,如果你是後端開發者,應該會常常注意伺服器有沒有發生 Memory Leak 與 Memory 使用量的狀況。然而在前端開發中,因為瀏覽器可以迅速啟動與關閉的特性,再加上 JavaScript 的 Garbage Collection 垃圾回收機制,常常讓前端開發者忽略了 JavaScript 的記憶體管理機制與 Memory Leak 帶來的危險性,有時應用的效能瓶頸可能就因此產生了。

今天想與各位讀者分享 JavaScript 的記憶體管理機制,知道不同的資料結構在 JS 中是如何儲存的。接著會看看 JavaScript 的 Garbage Collection 機制與它的限制,希望在經過今天的內容後,我們除了能夠知道 JavaScript 的記憶體管理機制以外,也能盡量避免寫出會造成記憶體用量大增甚至造成 Memory Leak 的程式碼,進而避免網站的效能產生瓶頸。

(今天的內容會以 Chrome 與 Node.js 使用的 JavaScript 引擎 V8 為例,不同的 JavaScript Engine 可能機制上會有些許不同。)

在 JavaScript 中,資料是如何儲存的?

記憶體的生命週期

首先要先來談談記憶體的生命週期,這個觀念無論使用的是哪種程式語言概念都是差不多的。

1. 分配程式需要用到的記憶體空間
2. 使用分配到的記憶體空間(讀寫操作)
3. 當不會再使用時要釋放被配置的記憶體空間

raw-image

Stack & Heap

JS 引擎又會將記憶體分為兩個區塊

  • 程式碼空間
  • Stack & Heap (數據空間)

我們知道 JavaScript 主要有 7 種資料型態:

  • string
  • number
  • boolean
  • null
  • undefined
  • symbol
  • object

這些數據資料會儲存在 Stack & Heap 之中,而程式碼空間則會儲存一些非數據的資料,舉例來說

const it_home = 'ironman';

“ironman” 這個 string 會被儲存到 Heap & Stack 的記憶體裡,而變數 it_home 則會被存放到程式碼空間的記憶體裡。

而今天主要要介紹的是「Heap & Stack」的部分。

數據空間又可以分為 stack 記憶體與 heap 記憶體,要注意這裡的 stack 與 heap 並不是指資料結構的 stack & heap,而是指記憶體的空間。熟悉 JS 的讀者應該知道數據又分成兩種類型,Primitive Type 與 Reference Type,比較簡單類型的 Primitive Type 會被放在 stack 裡,比較複雜類型的 Reference Type 則會把資料存在 heap 中,再把資料在 heap 的記憶體位址記錄到 stack 裡,為了快速理解,我們直接看一段 code。

function ironman(){
let one = "鐵人賽";
let two = one;
let three = { author: "Kyle Mo"};
let four = three;
}ironman();

當執行一段 JavaScript 的程式碼時需要先經過編譯,並創建所謂的執行環境(Execution Context), 接著再按照順序執行程式碼。

當 ironman function 執行完最後一行即將離開 function 時,記憶體的狀況會是這樣

raw-image

可以發現 Object 類型的數據實際上是存在 Heap 裡,Stack 中存的只是物件在 Heap 中的記憶體位置而已,而變數 four = three 這段 code 實際上是把 Three 指向的物件在 Heap 中的記憶體位置指派給 Four 變數,所以它們實際上指向的是同一個物件,這也是身為 JS 開發者應該十分熟悉的一個特性。

為什麼不把所有數據存到 Stack 裡就好?

原因是 JS Engine 是透過 stack 來維護 Execution Context 的切換狀態,如果 Stack 太過肥大,會影響 Context Switch 的執行效率,連帶影響到整個程式執行的效率。以上面的例子來說,當 ironman 這個 function 執行完畢後,JS Engine 會執行環境切換,將指針移到下一層的 Execution Context,也就是 Global Execution Context,然後回收 ironman function 的 執行環境與 stack memory。

raw-image

/* 2021.10.01 更新 */

社群上有大大指出字串型別與一些數字在 compile 的時候沒有辦法知道確切大小為何,所以應該不會是存在 stack 中。

關於這點我查詢了一些文章,發現這似乎不是一個很單純是或否的二選一問題,實際上可能得考慮 compiler 的實作方式,例如這篇文章所提及的。

又例如這篇文章它的續集透過觀察 Bytecode 而得出一些字串與數字會有「 constant pool」的概念,可以共用同一個記憶體位置。

所以目前結論是 JS 在 V8 引擎中:

  • string: 存在 Heap 裡,且 V8 會 maintain 一個字串的 hashmap,如果是相同字串,就會引用相同的記憶體位置。
  • number: 某些類型例如 smallint 會存在 Stack 中,其他類型則存在 Heap 裡。

詳細內容可以參考這篇文章

雖然我在某篇 Stack Overflow 的討論串中看到一句話「 For the JS programmer, worrying about stacks and heaps is somewhere between meaningless and distracting. It’s more important to understand the behavior of various types of values.」

但為了避免傳遞錯誤觀念給讀者,未來如果有新的結論,會再更新在文章中🙏 最後感謝社群大大的指正!

JavaScript 的垃圾回收機制

當數據不會再被程式使用時,就會變成所謂的垃圾數據(好像在罵人😂),而記憶體的空間是有限的,所以理想上必須針對這些垃圾數據進行回收,挪出記憶體空間以供未來儲存數據使用。

如果曾經寫過 C/C++ 的讀者應該寫過一些需要自己管理記憶體的分配與回收的程式碼,例如

char* ironman =  (char*)malloc(1024);free(ironman)
ironman = NULL

不過 JavaScript 這門程式語言有一個叫做 「Garbage Collector」的系統,Garbage Collector (簡稱 GC)的工作是「追蹤記憶體分配的使用情況,以便自動釋放一些不再使用的記憶體空間」。這個 GC 的機制方便歸方便,卻讓許多 JavaScript 開發者產生「寫 JS 時可以不須理會記憶體管理」的錯誤認知。

根據 MDN 文件,有 GC 機制的存在仍然不能不管記憶體管理的原因在於 GC 只是「儘量」做到自動釋放記憶體空間,因為判斷記憶體空間是否要繼續使用,這件事是「不可判定(undecidable)」的,也就是不能單純透過演算法來解決。

所以我認為了解 GC 基本的運作方式是很重要的,有了基本的觀念才能避免 memory leak 的發生,讓應用的效能不會因為記憶體空間不足而出現瓶頸甚至崩潰。

GC 的工作流程

首先需要先釐清一下,剛剛有提到在執行執行環境被回收時,該執行環境的 Stack 空間也會被回收(Stack 空間由 OS 管理,背後的實作機制我們先不討論),那各位讀者可能會發現一個問題,如果是物件的話,Stack 中存的是 Heap 空間的 address,所以就算 Stack 被回收,存在 Heap 空間的數據依然存在,這時就需要靠 GC 來判斷 Heap 空間中哪些數據是用不到且需要被回收的,接下來就一起來看看 Chrome 的 V8 引擎的垃圾回收機制是如何運作的。

其實 Garbage Collection 的演算法有非常多種,但目前還沒有出現所謂完美的 GC 演算法,依據不同的執行環境、語言,只能盡量找出「最適合」的 GC 演算法,以盡量達到最好的回收效果。

在 V8 引擎中,heap 又被分為兩個區域 — New SpaceOld Space

New Space 中存放的是存活時間較短的物件,這裡垃圾回收的速度會比較快,不過空間卻比較小,大概只有 1–8 MB 左右,存在 New Space 中的物件也被稱作 Young Generation。Old Space 中存放的是存活時間較久的物件,這些物件是在 New Space 中經過幾次 GC Cycle 並成功存活後才被移到 Old Space,在 Old Space 做垃圾回收的效率比較差,因此它執行 GC 的頻率相較於 New Space 會比較低,而在 Old Space 中的物件也被稱作 Old Generation

在 V8 中,分別對 Young Generation 與 Old Generation 實作了不同的 GC 演算法

  • Young Generation: Scavenge collection
  • Old Generation: Mark-Sweep collection

Scavenge 演算法

Scavenge 演算法將 Young Generation 再分為「物件區域」與「空閒區域」。

raw-image

新存入記憶體的物件會被放到物件區域,當物件區域快要 overflow 時,就得執行一次 GC。要做 GC 時,得先標記出哪些物件是應該要被回收的垃圾,標記出垃圾後才會正式進入記憶體清理階段,Garbage Collector 會把「仍然存活的物件」Copy 到空閒區域中並且排序。如果有使用過電腦的「磁碟重組」功能,應該知道它的原理是把一些不再使用的空間清除,並將碎片化的空間連接在一起。上面 GC 這段 Copy & Sort 的操作其實就跟磁碟重組類似是一種整理記憶體空間的行為。

Copy & Sort 後垃圾回收器會再將物件區域與空閒區域的角色翻轉,這樣就順利完成了 Young Generation 垃圾回收的操作,並且這樣清除與翻轉角色的機制是可以一直重複執行下去的。

從 Scavenge 演算法我們可以得知兩件事:

  • 每次執行 Young Generation 的垃圾回收都需要執行 Copy & Sort 這些相當耗時的操作,因此為了效能,通常 Young Generation 的空間會分配的比較小,這是我們在先前就提過的。
  • 接續上面那點,因為 Young Generation 被分配的空間比較小,物件區域很容易被佔滿並必須執行垃圾回收,這對效能可能是個影響,因此 JS 通常會將經歷兩次 GC 仍然存活的物件移動到 Old Generation。

Mark-Sweep 演算法

在 Old Generation,主要會有兩種物件

  • 從 Young Generation 轉移過來的物件
  • 佔用記憶體空間較大的物件有機會直接被送到 Old Generation

所…所以呢?

因為 Old Generation 物件佔用記憶體的空間通常較大,執行 Scavenge 演算法的 Copy & Paste 是很沒有效率的,同時還得切分出一半的空間用來轉換作為,對於本身記憶體空間比較大的 Old Generation 來說浪費了更多的空間,種種原因影響之下,在 V8 引擎的 Old Generation 中通常會採用另外一種演算法 — 「Mark-Sweep」來進行垃圾回收。

Mark-Sweep GC 演算法分為「標記」與「清除」兩個步驟,標記就是紙從根元素開始遞迴的尋訪這組根元素,在這個過程中,能夠被造訪的元素就是仍然需要存活的物件,而沒有被造訪的元素則被判定為垃圾數據,應該要被 GC 給清除。

在標記(Mark)完成後下一個階段就是把標記為垃圾的物件給清除(Sweep)。

raw-image

動圖來源

Mark-Compact 演算法

上面的 Mark-Sweep 演算法有一個缺點,就是容易讓記憶體產生不連續且碎片化的空間,碎片過多會導致需要較大空間的物件沒辦法被分配到足夠的連續記憶體。為了解決這個問題,另外一種被稱作 Mark-Compact 的演算法誕生了。這個演算法在 Mark 階段與 Mark-Sweep 基本上一致,然而在清理過程會將存活的物件往記憶體的其中一端移動,整理出足夠的連續記憶體空間。

raw-image

Garbage Collection 可以 Stop The World !?

在前端的世界裡,Garbage Collection 也是由瀏覽器的 Main Thread 來負責的,不過 JavaScript 會受到 Single Thread 的限制,這意味著在做垃圾回收時 Main Thread 是不能夠做其他事的,必須等到回收任務完畢才能繼續執行 Script,這個特性也被稱作 「Stop The World」。

raw-image

這看起來不是那麼理想,因為在 Old Generation 的 GC 是比較緩慢的,萬一 GC 需要耗時幾百毫秒,也會對頁面效能造成重大的影響。

V8 實作了一種叫做 Incremental Marking 的演算法,透過交替執行 GC 與 Script 的方式來解決使用者感覺到頁面卡頓的問題。

raw-image

Prevent Memory Leak In JavaScript

Memory Leak 可以說是工程師的公敵,不管前端後端甚至系統工程師在開發時都會盡量避免 Memory Leak 的發生。

先來看看 Memory Leak 的定義

記憶體流失是在電腦科學中,由於疏忽或錯誤造成程式未能釋放已經不再使用的記憶體,從而造成了記憶體的浪費。嚴重的話會可能會導致程式效能變慢甚至 crash。

像是 C/C++ 這類的語言需要開發者自己手動管理記憶體的釋放,而 Java 或 JavaScript 這類有垃圾回收機制的語言則不用手動釋放記憶體,但是不要以為這樣就安全了,在開發時有些寫法會造成垃圾回收機制沒辦法正確判斷記憶體已經不再被使用了,而無法被自動會收,造成所謂的 Memory Leak。

在 JavaScript 中,遵守某些 Best Practices 或是避免一些寫法可以盡量避免 Memory Leak 的發生。

Event Listener

在前端開發中,Event Listener 是很常見的功能,前端開發者要特別注意事件監聽器是不是會重複產生新的監聽器還有當監聽器用不到的時候是不是有正確移除。

熟悉 React 的讀者一定看過這個寫法

raw-image

就是為了確保事件監聽器在不需要時可以被正確移除。

不當存取全域變數

例如說以下這段簡單的 Express Web Server 的程式

const express = require('express');
const app = express();

// 全域變數
const requestStatusCollection = [];

app.get('/ironman', (req, res) => {
requestStatusCollection.push(req.status);

return res.send({});
})

app.listen(3000,() => {
console.log('server listening on port 3000...');
})

requestStatusCollection 是一個全域的陣列變數,雖然每個 endpoint 被呼叫後就會離開 handler,但全域變數卻不會被移除,當成長到一定的量時伺服器很可能會因為記憶體使用量過多卻沒有正確釋放而影響效能。

當然也不是說都不能用全域變數,例如有些 Cache 就會使用 memory 的 data structure 實作,不過通常會搭配例如 LRU Cache 的資料結構來控制記憶體的用量。

另外因為 hoisting 的特性,JavaScript 有些寫法也會產生不預期的全域變數

// 假設以下 function 都是 global 的
// author 會被 hoist 成一個全域變數
function ironman() {
author = "Kyle Mo";
}

// 這種寫法在 non strict mode 下也會變成全域變數
function hello_it_home() {
this.author = "Kyle Mo";
}

// 這種情況下就算是 strict mode author 也會變成全域變數
const hello_ironman = () => {
this.author = "Kyle Mo";
}

Out of DOM references

在不使用前端框架的狀況下,有時候可能會把 DOM Node 存在像物件這樣的資料結構中

const elementsMap = {
button: document.getElementById('btn'),
image: document.getElementById('img'),
};

有時候會有 remove DOM element 的需求

function removeButton() {
document.body.removeChild(document.getElementById('btn'));
}

你可能會覺得在 removeChild 後這個 DOM Element 所佔用的記憶體空間已經被清除了,然而實際上因為 elementsMap 這個物件還存在對 btn 這個 element 的 reference,所以 GC 並不會清除它的記憶體空間。

Old Browser & Defective Browser Extension

一些比較舊的瀏覽器例如 IE 的 GC 演算法比較不精確,沒辦法解決像是 circular reference 等問題,因此比較容易造成 Memory Leak。此外一些有缺陷的瀏覽器擴充套件也是造成 Memory Leak 的可能原因之一。

Debug Memory Usage

身為前端開發者,應該要學會好好利用瀏覽器 Devtool 提供的種種功能,以 Chrome 來說就有提供 memory tab 讓開發者可以觀測應用的記憶體使用量,礙於篇幅就不多做介紹,建議各位讀者可以去玩玩,也推薦閱讀這篇文章,看看實際在專案開發上是如何找出潛在 Memory Leak 的問題並嘗試解決。

不同的寫法,也許記憶體使用量會差很多

這裡有一個用 React 撰寫的簡易 Demo

raw-image

首先在 global 建立一個擁有一千萬個 items 的陣列,並分別實作兩個按鈕:cheap Loop 與 expensive loop。cheap loop 是利用迴圈更改 array item 的屬性值,expensive loop 則是每一次迴圈都重新指派一個新的物件給 array 中的 item,實際上最終這兩種方式跑出來的 array 應該要是長一樣的,但這兩種方式的效能卻有極大的不同。

raw-image

可以看到在點擊 cheap loop 的時候頁面基本上是平順的,但點擊 expensive loop 後頁面很明顯直接卡頓住了。

當然這跟 Single Thread 的特性有關,expensiveLoop 相較於 cheapLoop 也是一個較耗費 CPU 的操作,這點可以從 performance tab 觀察出來

raw-image

可以看到 CPU 在跑 expensiveLoop 後是爆量升高的,再來看看 heap 記憶體空間的 snapshot 對比

raw-image

snapshot1 是點擊任何按鈕前的 heap snapshot 狀況
snapshot2 則是點了 cheapLoop 按鈕後的 snapshot 狀況
snapshot3 則是點了 expensiveLoop 按鈕後的 snapshot 狀況

可以發現點了 expensiveLoop 後的 heap 記憶體用量變成了原本狀況的將近 5 倍左右!雖然後續有機會被 GC 回收,但因為 GC 是自己運作的,開發者沒有控制它的權力,因此我們也不能保證未來記憶體會被順利回收。

可見在開發時除了注意能不能完成需求以外,也要留意是不是一個好的寫法或是有沒有更好的解決方案,不管是 CPU 的消耗還是記憶體的使用量,如果能盡量避免就該避免!

Demo Source Code

(後續更新:其實 immutable 的寫法在 JS 中很常見,Immutable 的寫法的確是在記憶體新增一個物件,理論上會比較沒那麼有效率,但一般使用情景應該都沒什麼問題,變垃圾的物件自然會被 GC 清掉,上述範例是因為一次爆量(一千萬次迴圈)新增物件導致記憶體用量暴增,理論上未來 GC 也會做清理,但在JS 中開發者對 GC 沒有控制權,那個 snapshot 是馬上做完操作時紀錄的,所以會顯示記憶體爆量成長,平常開發正常使用 immutable 的寫法倒是不用太擔心喔!)

結語

了解記憶體管理的機制嚴格來說不是一種效能優化的技巧,而是一種「避免效能出現瓶頸」的一個重要觀念,今天的內容不深,卻是我認為前端開發者或是 JS 開發者一定要了解的記憶體管理機制,希望各位有所收穫!

(本篇文章由個人 Medium 部落格搬遷而來)

References

https://blog.risingstack.com/node-js-at-scale-node-js-garbage-collection/?source=post_page-----d9db2fd66f8--------------------------------

https://blog.poetries.top/browser-working-principle/guide/part3/lesson13.html?source=post_page-----d9db2fd66f8--------------------------------#%E5%89%AF%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E5%99%A8

https://linyencheng.github.io/2019/10/01/js-memory-leak/?source=post_page-----d9db2fd66f8--------------------------------


留言0
查看全部
avatar-img
發表第一個留言支持創作者!
什麼是 Frontend Infrastructure (Infra) ? 提到前端網頁開發,可能很多人聯想到的都是 UI 畫面切版、動畫特效,甚至認為前端開發者的工作內容「離不開畫面」。但其實前端開發是一個非常廣的領域,同樣身為前端工程師,每個人專注開發的領域可能都不一樣,所打造出的技能樹...
什麼是 Frontend Infrastructure (Infra) ? 提到前端網頁開發,可能很多人聯想到的都是 UI 畫面切版、動畫特效,甚至認為前端開發者的工作內容「離不開畫面」。但其實前端開發是一個非常廣的領域,同樣身為前端工程師,每個人專注開發的領域可能都不一樣,所打造出的技能樹...
你可能也想看
Google News 追蹤
Thumbnail
*合作聲明與警語: 本文係由國泰世華銀行邀稿。 證券服務係由國泰世華銀行辦理共同行銷證券經紀開戶業務,定期定額(股)服務由國泰綜合證券提供。   剛出社會的時候,很常在各種 Podcast 或 YouTube 甚至是在朋友間聊天,都會聽到各種市場動態、理財話題,像是:聯準會降息或是近期哪些科
Thumbnail
當投射者遭遇非自己的時候,心中會有「Bitterness」的感覺。“Bitterness" 可以翻譯成「苦澀」或者「怨恨」,具體取決於上下文和使用情境。下面是兩個例子: 1. 苦澀(苦味):如果 "bitterness" 是指食物或飲品的苦味,那麼可以使用「苦澀」這個詞來翻譯。例如:「這杯手沖有一點
Thumbnail
身為科技人更應該寫文章做分享 你要先讓自己被世界看到 才有機會走向更廣闊的世界
Thumbnail
近年來社會新聞上鬧得沸沸揚揚的張淑晶案件,對房東、租屋族來說都是非常衝擊的,租屋遇到好房東、好房客非常講求緣分,沒有人希望因為租房子的事情引起糾紛,甚至弄到還要進到法院訴訟。所以在房東把房屋出租前應該注意什麼事項?租客承租房屋前應該注意什麼事項?一起來看看需要特別留意哪些相關規定...
Thumbnail
很多人說尊重,但實際上只是欺騙別人的同時也在欺騙自己
Thumbnail
互聯網產品基本上都會涉及到軟體的開發,而互聯網產品經理很重要的工作就是:定義軟體要開發哪些功能,並確保這些功能在要求的標準下順利發布,提供給目標用戶價值,進而帶來商業利益。而在管理整個產品週期的過程中,主要的工作流程會包含5個步驟……
Thumbnail
  綾辻老師推行的「新本格ミステリ(新本格推理)」也正好滿三十年。其實這和我的推理小說閱讀歷程很接近。差不多綾辻老師出道的年代,我開始迷上日式推理小說。從赤川次郎到西村京太郎,還有在當時早已是推理大師的橫溝正史、松本清張等,我也分不出甚麼"派",總之看就對了。於是這樣隨便看了三十多年的推理小說...
Thumbnail
這樣快速科技變化使我們必須追求更高、更好的教育,以因應科技發展。我們雖然出生以及習慣生活在這樣科技化的時代中,但在科技進步速度如此急遽下,假使我們不增加自身能力來面對未來的變化,很快地,我們也會「過時」。因此,持續學習以及增進一些「必要的」技能,是刻不容緩的事,以下將會介紹三種重要的技能......
Thumbnail
我對小編的期許有幾個:一,是她要做為露比午茶家裡的”管家”,設想粉絲團或是社團就是一個會客室,露比午茶是一個大宅院,客人來拜訪,她傳遞內部消息過去,招呼各地的人們,了解客戶。所以他必須知道最新的官網檔期,她必須知道所有那些商品是熱賣,那些商品是主打。二,小編同時要建立自己以及內部Icone的角色,她
Thumbnail
在職場上,有些艾寶可能已經爬到小主管的位置了。當你在帶領下屬時,是不是常常在思考要給下屬多大的空間?給太多工作的話,下屬突然離職該怎麼辦?我身為主管的價值又在哪?
Thumbnail
工程師的專注力是非常寶貴的資源,和產出能力息息相關,如果因為儲存空間的不足導致必須時常費心去清除零碎檔案釋放空間,對工程師本人和產品本身都是極大的損失。所幸網路上找得到許多清除垃圾檔案的方法,在這邊我就重點擷取幾個對 iOS 工程師而言比較有感的方式。
Thumbnail
*合作聲明與警語: 本文係由國泰世華銀行邀稿。 證券服務係由國泰世華銀行辦理共同行銷證券經紀開戶業務,定期定額(股)服務由國泰綜合證券提供。   剛出社會的時候,很常在各種 Podcast 或 YouTube 甚至是在朋友間聊天,都會聽到各種市場動態、理財話題,像是:聯準會降息或是近期哪些科
Thumbnail
當投射者遭遇非自己的時候,心中會有「Bitterness」的感覺。“Bitterness" 可以翻譯成「苦澀」或者「怨恨」,具體取決於上下文和使用情境。下面是兩個例子: 1. 苦澀(苦味):如果 "bitterness" 是指食物或飲品的苦味,那麼可以使用「苦澀」這個詞來翻譯。例如:「這杯手沖有一點
Thumbnail
身為科技人更應該寫文章做分享 你要先讓自己被世界看到 才有機會走向更廣闊的世界
Thumbnail
近年來社會新聞上鬧得沸沸揚揚的張淑晶案件,對房東、租屋族來說都是非常衝擊的,租屋遇到好房東、好房客非常講求緣分,沒有人希望因為租房子的事情引起糾紛,甚至弄到還要進到法院訴訟。所以在房東把房屋出租前應該注意什麼事項?租客承租房屋前應該注意什麼事項?一起來看看需要特別留意哪些相關規定...
Thumbnail
很多人說尊重,但實際上只是欺騙別人的同時也在欺騙自己
Thumbnail
互聯網產品基本上都會涉及到軟體的開發,而互聯網產品經理很重要的工作就是:定義軟體要開發哪些功能,並確保這些功能在要求的標準下順利發布,提供給目標用戶價值,進而帶來商業利益。而在管理整個產品週期的過程中,主要的工作流程會包含5個步驟……
Thumbnail
  綾辻老師推行的「新本格ミステリ(新本格推理)」也正好滿三十年。其實這和我的推理小說閱讀歷程很接近。差不多綾辻老師出道的年代,我開始迷上日式推理小說。從赤川次郎到西村京太郎,還有在當時早已是推理大師的橫溝正史、松本清張等,我也分不出甚麼"派",總之看就對了。於是這樣隨便看了三十多年的推理小說...
Thumbnail
這樣快速科技變化使我們必須追求更高、更好的教育,以因應科技發展。我們雖然出生以及習慣生活在這樣科技化的時代中,但在科技進步速度如此急遽下,假使我們不增加自身能力來面對未來的變化,很快地,我們也會「過時」。因此,持續學習以及增進一些「必要的」技能,是刻不容緩的事,以下將會介紹三種重要的技能......
Thumbnail
我對小編的期許有幾個:一,是她要做為露比午茶家裡的”管家”,設想粉絲團或是社團就是一個會客室,露比午茶是一個大宅院,客人來拜訪,她傳遞內部消息過去,招呼各地的人們,了解客戶。所以他必須知道最新的官網檔期,她必須知道所有那些商品是熱賣,那些商品是主打。二,小編同時要建立自己以及內部Icone的角色,她
Thumbnail
在職場上,有些艾寶可能已經爬到小主管的位置了。當你在帶領下屬時,是不是常常在思考要給下屬多大的空間?給太多工作的話,下屬突然離職該怎麼辦?我身為主管的價值又在哪?
Thumbnail
工程師的專注力是非常寶貴的資源,和產出能力息息相關,如果因為儲存空間的不足導致必須時常費心去清除零碎檔案釋放空間,對工程師本人和產品本身都是極大的損失。所幸網路上找得到許多清除垃圾檔案的方法,在這邊我就重點擷取幾個對 iOS 工程師而言比較有感的方式。