閉包是什麼能吃嗎?

Photo by Valeria Boltneva on Pexels.com

結論懶人包先奉上~

閉包成立條件:

  1. 雙層函式 (雙層吉士堡
  2. 內部函示使用了外部函式的區域變數

結果:

此變數會被 cached 在內部函數,變成內部函數獨有的變數


那就開始囉~
在談閉包之前,讓我們先了解 JS 中的範疇 Scope

What ? 範疇是什麼

廣義的結論:
JS的程式碼片段都必須在執行前編譯好,範疇 ( Scope ) 就是在引擎 ( Enginee )編譯時,查找的規則依據

嚴謹來說:

定義良好的規則來將變數儲存在某些位置,以便在之後找回那些變數;
這組規則,用來規範引擎如何藉由一個變數的識別名稱 ( identifier name)來查找,我們便稱做是範疇

From You don’t know JS

一些規則如下:

巢狀範疇

如果在最接近的範疇中找不到變數,Engine 就會往上一層外層查找,直到找到、或是到達最外層(全域)範圍為止。

以下圖為例,將巢狀範疇想像成一棟大樓,目前執行的範疇是位在一樓的 Current Scope,當無法在該樓找到欲找的變數時,便會往二樓查找,以此類推,直到查找變數,或是查找到頂樓 Global Scope 為止。

視覺化巢狀範圍,圖片來自 You don’t know JS

Who ? 誰是範疇

  • 函式:
    Js 具有以函式為基礎的範疇,宣告的每一個函式都會為自身建立一個泡泡,將其中的資訊隱藏起來,不讓外層範疇存取。
  • 區塊 block-scoping:
    ex: for , if block 中
for(let i = 0; i ≤nums; i++){
block-scoping, i 只存在於這區塊間
}

靠近被使用的地方,再宣告變數,盡可能的本地化。

  • let
    宣告時,便接附 ( attaches to )上它所在的區域。
if(true) {
let bar = 'hi123'
console.log('inner bar',bar) //hi123}
console.log('outer bar',bar) //ReferenceError

*不會被拉升到現有區塊都能看見,因此程式未執行到宣告述句前,此變數不算存在。

{
 console.log('bar',bar) //ReferenceError
 let bar = 'woo'
}



回到正題,閉包是函式記得並存取語彙範疇的能力;對於語彙範疇一詞,感到陌生的,可以先去看以下文章,其中會提到JS 編譯過程。

語彙範疇( lexical scoping ):語意分析 (Parsing) 時期定義的範疇

[You don’t know JS]來談談 Hosting 拉升,是在拉什麼?

另外我也參考了,其他文章所描述的語彙範疇:

So, lexical or static scoping means the scope and value of a variable is determined from where it is defined. It doesn’t change.

reference from JavaScript Closure Tutorial — With JS Closure Example Code

也就是,範疇與變數的值在定義、創造時(編譯時期)便決定了,後續不會被改變。

函式即使在其編寫時期的語彙範疇之外被調用,閉包讓此函式仍能繼續存取它在編寫時期定義處的那個語彙範疇。所以用任何方式將內層函示運送到其語彙範疇外,它依仍會保留其原本宣告處的範疇參考(條件一)

function foo() {
var a = 2;
function bar(){
console.log(a)
}
return bar
}
var baz = foo()
baz()

bar() 被執行了,是在其宣告處的語彙範疇之外執行的。
照理說,foo()執行完後,看似不會再被使用,一般來說我們會預期整個內層範疇都會被釋放,但由於內層 bar() 函式仍還在使用此範疇,仍有一個參考(變數 a) 指向 foo() 範疇(條件二),因此此範疇得以仍存活。


最後再回顧一下,我們一開始提到的

閉包成立條件:

條件一:雙層函式

條件二:內部函示使用了外部函式的區域變數

ok,收工~


Learn More

  1. JavaScript Interpreted or Compiled?
  2. JavaScript Closure Tutorial — With JS Closure Example Code
    內文也有提供一些閉包的範例,可參考。

>> Medium 同步上架

Javascript 刷 LeetCode 學演算法 – 搜尋 Linear Search, Binary Search

Linear Search

時間複雜度:O(n)



744. Find Smallest Letter Greater Than Target


撞到語法不夠熟悉的問題,原本嘗試在 forEach 中 return 和 break,發現不適用
這種情況適合改用 for loop or every 等等其他方法,可參考以下文章

https://masteringjs.io/tutorials/fundamentals/foreach-break


Binary Search

時間複雜度: O log(n)

適用情境:在一個已排序的陣列中,找出其中特定的值

實作關鍵:遞迴or無窮迴圈,雙指標、中值
=> 設立雙指標、中值,並在查找的過程中不斷移動邊界

367. Valid Perfect Square


延伸閱讀:

取中值的公式

直觀來說會寫

int mid = (low + high)/2;

但這篇文章提到建議改寫

int mid = low + (high – low)/2;

原因是超過最大正整數時,會溢位,這時候取的中值就會是錯的

But if we calculate the middle index like this means our code is not 100% correct, it contains bugs.

That is, it fails for larger values of int variables low and high. Specifically, it fails if the sum of low and high is greater than the maximum positive int value(231 – 1 ).

The sum overflows to a negative value and the value stays negative when divided by 2.


Reference from geeksforgeeks

在VSCode 裝個漂亮的 Terminal 介面- zsh + powerlevel10k


reference by powerlevel10k

解決完連環 powerlevel9k 的設定問題後,赫然發現,竟然出新版本了,完全可以無腦安裝,照著介面操作,一分鐘就搞定!
完全不用經歷 debug 上一版本各種 icon 顯示不出來的問題,還不快繞過前人的坑,現賺數小時XD


  1. 未安裝 Brew
照著官網安裝 https://brew.sh/index_zh-tw

#安裝指令
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

#指定路徑
echo 'eval $(/opt/homebrew/bin/brew shellenv)' >> /Users/$USER/.zprofile

eval $(/opt/homebrew/bin/brew shellenv)

Reference:
https://stackoverflow.com/questions/36657321/after-installing-homebrew-i-get-zsh-command-not-found-brew

  1. 已安裝Brew ->安裝 iTerm2
#沒有用過 brew cask 的話需要先跑這行
brew tap homebrew/cask# 安裝 iTerm2
brew instal iterm2
  1. 安裝 iTerm2
#沒有用過 brew cask 的話需要先跑這行
brew tap homebrew/cask
# 安裝 iTerm2
brew instal iterm2


2. 安裝 zsh

brew install zsh

並把 zsh 設定為你的預設 shell:

sudo sh -c "echo $(which zsh) >> /etc/shells" 
chsh -s $(which zsh)

3. 安裝 oh-my-zsh

sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"

執行完以後如果沒有出現什麼錯誤訊息就代表成功了,同時會發現多了 oh-my-zsh 的資料夾 ~/.oh-my-zsh

4. 安裝 zsh theme: powerlevel10k

git clone --depth=1 https://github.com/romkatv/powerlevel10k.git ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k
#根據官網教學,安裝過 oh-my-zsh 可直接用上述指令安裝主題
# EX: ~/.oh-my-zsh
# 需要注意的是,由於 VsCode的 Terminal 主題設定會直接讀取 ~/.oh-my-zsh/themes 資料夾下中的主題,我們這邊就不另外放到客製化資料夾了

5. 切換內建的 theme

修改你的 ~/.zshrc,把原本 ZSH_THEME=”robbyrussell 改成你想要的:

#編輯 ~/.zshrc
vi ~/.zshrc
#找到這個參數,修改後面的值
ZSH_THEME="powerlevel10k/powerlevel10k"

任何的 zsh 設定修改過後,還要執行以下指令才會生效

exec $SHELL

6. 重啟 iTerm

7. 會出現 PowerLevel10k 的設定友善介面,照著做完就好囉!

你現在應該會看到類似的繽紛畫面~ 恭喜你,成功囉!

8. 開啟 VSCode,設定 terminal

Preference > Settings > Features > Terminal

點擊在 setting.json 內編輯 ( Edit in settings.json )

新增上這行

"terminal.integrated.defaultProfile.osx": "zsh"

也可以透過操作 command + 上箭頭 + P 的命令選取區,更換預設設定檔

然後也要確認一下設定檔中的 zsh 路徑是否正確

"terminal.integrated.profiles.osx":{ "zsh": {  "path": "/usr/local/bin/zsh", #正確路徑可在 iterm 下 which zsh 得到  "args": [  "-l"  ] }}

最後修改 font family

"terminal.integrated.fontFamily": "MesloLGS NF" #PowerlevelXk 系列的字體

9. 重開 VSCode ,就會看到漂亮的 Terminal 囉!

由於我是做了一系列 Powerlevel9k 的設定動作後,才改裝10k的,所以各位如果直接安裝 10k ,有少什麼流程,歡迎再留言提出!


Learn More

About Brew Part …

Q: what is taps ?
A: Taps (Third-Party Repositories)

Q: What cask ?
A: a CLI workflow

Reference 

超簡單!十分鐘打造漂亮又好用的 zsh command line 環境 https://medium.com/statementdog-engineering/prettify-your-zsh-command-line-prompt-3ca2acc967f

How to update visual studio code’s default terminal shell from bash to zsh
https://medium.com/fbdevclagos/updating-visual-studio-code-default-terminal-shell-from-bash-to-zsh-711c40d6f8dc

想要在 Medium 收藏這篇文章這邊走 ~

Event Loop 是什麼? 淺談 JS Engine – V8

Photo by Ryutaro Tsukata on Pexels.com

先講結論: Event Loop 就是一個永不停歇的調配 callback 事件佇列(Callback Queue)與目前該執行哪個程式(Call Stack)的橋樑

那我們開始囉~

🍘 先來聊聊,先備知識

JS 引擎是單一執行緒(thread)的程式語言,不可單獨運作,需在一個宿主環境中(hosting envirement)才能運行,像是典型的 web 瀏覽器、NodeJs,到各種內嵌裝置。

不論在哪個環境中,皆有一個共通的主軸 (thread),當每次 JS 被調用時,用來處理程式的多個組塊分布在各個時間的執行工作(?),特性為永遠都排程了事件,這個機制就叫 Event Loop。換句話說,JS 引擎本身是沒有時間概念的。

好的,上述是根據小黃書( You don’t know Js )的定義,可能是翻譯的關係,有一句話實在很饒口,完全不知道在說什麼XD
沒關係,作為講求實務的工程師,讓我們直接以範例來理解~

以下為 Web 瀏覽器 – V8 為例

reference : Understanding the JavaScript runtime environment

JS 引擎:

  1. Memory heap (The heap) : 底層記憶體,用來配置物件與變數
  2. Call Stack: 持續追蹤目前程式碼跑到哪裡,每執行一個 function,便疊加上去,function 執行完,便從疊加中移除,再執行下一個 function。為單一執行緒的後進先出 Stack 結構

瀏覽器環境:

  1. Web API: 瀏覽器環境所提 供的各種 API 接口,允許與 JS 做更多的互動,像是常用的 Dom, AJAX, setTimeout。


    「竟然都不存在於 JS 引擎中,在研究的過程中,這點也令我滿驚訝的XD」


  2. Callback Queue: 紀錄來自於 Web API callback 事件的觸發順序,同樣是先進先出的佇列結構 ( Queue )
  3. Event Loop: 持續調度 Call Stack 與 Callback Queue 之間狀態的機制,當 Call Stack 為空時,會抓取 Callback Queue 最上層任務,到 Call Stack 去執行。

「也就是說,Event loop 是 stack 和 queue 之間的橋樑,讓執行緒永遠有事做~(誤」


資源補充,強烈推薦觀看影片介紹,會有看完整的理解:

講者做的簡化機制的 demo : http://latentflip.com/loupe

好拉~ Event Loop 的淺談就差不多到這結束了,
以下為 JS 引擎內部實作的再進一步的討論,沒興趣的可就此打住~


JS 引擎進階討論:

Ignition: compiles JavaScript functions to a concise ( bytecode 將程式碼 compile 成 bytecode

TurbonFan: optimizing compilers ( 優化程式碼的 compiler

經由優化,優化成 machine code

AST: Abstract Syntax Tree

Reference:

https://v8.dev/blog/ignition-interpreter

https://v8.dev/blog/turbofan-jit

Call stack Reference:
JavaScript Event Loop And Call Stack Explained
內文有視覺化程式,可更幫助理解

The call stack is a mechanism that helps the JavaScript interpreter to keep track of the functions that a script calls.

Every time a script or function calls a function, it’s added to the top of the call stack. Every time the function exits, the interpreter removes it from the call stack.

A function either exits through a return statement or by reaching the end of the scope.

Javascript 刷 LeetCode 學演算法- 排序

在 Javascript 中遇到排序問題,通常可直接使用原生 function — sort()
因此很少自己實作排序的功能,此次透過刷題,嘗試用各種排序演算法,期待能更深入地對個演算法有記憶,並瞭解使用情境;相關的時間、空間複雜度,將會另撰寫文章

搭配相關的視覺化工具:VisualGoVISUALIZE CODE EXECUTION
我個人是圖像化學習較能吸收,尤其在針對演算法這種較抽象的概念,能夠直觀地看逐步的過程,可以有更深入的理解。

摘要

將會用到的排序:

插入排序 Insert Sort
快速排列 Quick Sort (use space and in place )
堆排序 Bucket Sort


尋找第 K 個元素問題

  • LeetCode 215: Find Kth Elements
    思路:排序後即可求得
    實作:
    1. 使用 native sort 去排序 (LeetCode 實測結果: 80 ms, 39.2 MB)
    2. 實作 quick sort use space 去排序 (LeetCode 實測結果: 1432 ms, 182.5 MB)
    3. 實作 insert sort (LeetCode 實測結果: 612 ms, 41.2 MB)

其中在理解插入排序時,對於要用兩個迴圈這件事,我卡了好一陣子,直到查到這部影片,很生動地呈現,才頓悟,推薦給大家。
原來雙迴圈是使用了「雙指標」的概念:一個指標往前遍尋,一個指標往後遍尋。

//Topic: find Kth elements
//quick sort use space
var qsort = function(nums,k) {
if(nums.length <= 1) {
return nums
}
let leftAry = [];
let rightAry = [];
let pivot = nums[0];
for(let i = 1; i < nums.length ; i++){
nums[i] > pivot ? rightAry.push(nums[i]) : leftAry.push(nums[i])
}
return qsort(leftAry).concat(pivot, qsort(rightAry));
}
//native sort
var nativeSort = (nums,k) => {
nums.sort((a,b) => a-b)
return nums[nums.length – k]
}
const swap = (nums,a,b) => {
[nums[a],nums[b]] = [nums[b],nums[a]]
}
// insert sort
var insertSort = (nums,k) => {
for(let i = 1; i < nums.length; i++) {
let position = i;
while(position >= 0 && nums[position – 1] > nums[position]) {
swap(nums,position,position-1);
position–;
}
}
return nums[nums.length – k]
}
var findKthLargest = function(nums, k , sort) {
return sort(nums,k);
};
const nums = [3,2,1,5,6,4];
const k = 2;
console.log('qsort result:', findKthLargest(nums,k,qsort)[nums.length – k]);
console.log('nativeSort result:', findKthLargest(nums,k,nativeSort))
console.log('insertSort result:', findKthLargest(nums,k,insertSort))
view raw leetCode215.js hosted with ❤ by GitHub

=>動手試試

  • LeetCode 75: Sort Colors
    實作:
    1. 使用 quick sort use space 實作 sort (僅實作,題目要求要 in place)
    2. 使用 quick sort in place (原地實作,不另外佔空間)實作 sort (LeetCode 實測結果:68 ms, 39.8 MB)
    3. 使用 native sort (LeetCode 實測結果:68 ms, 39.8 MB)

同樣的我們可以先來看一支舞 XD

//use space
var sortColors1 = function(nums) {
const pivot = nums[0];
let left = [];
let right = [];
let i = 0;
while(i < nums.length -1) {
nums[i] < pivot ? left.push(nums[i]) : right.push(nums[i])
i++;
}
return […left,pivot,…right]
};
//native sort in js used quick sort when less data
var sortColorsNativeSort = (nums) => {
nums.sort((a,b) => a – b)
}
//implement quick sort in place
var swap = (list,a,b) => [list[a],list[b]] = [list[b],list[a]]
var getPointer = (nums,start,end) => {
const v = nums[start];
let pointer = start;
for(let i = start + 1; i < end ; i++){
if(nums[i] < v){
pointer++;
swap(nums,pointer,i);
}
}
swap(nums,start,pointer)
return pointer
}
const sortColors = (nums,start = 0,end = nums.length) => {
if(start >= end) return
const p = getPointer(nums,start,end)
sortColors(nums, start, p)
sortColors(nums, p+1, end)
}
const nums = [2,1,4,3,7,1,8]
sortColors(nums)
console.log(nums)
view raw leetCode75.js hosted with ❤ by GitHub

=>動手試試

  • LeetCode 347: Top K Frequent Elements
    思路:先排序再做堆
    學到: 善用 JS Map 型別,可以讓 code 更漂亮,以及 Object 與 Map 的差異,論 iterator 特性
var topKFrequent = function(nums, k) {
let hash = {}
nums.forEach(row => {
if(hash[row] === undefined) {
hash[row] = 1;
}else {
hash[row] += 1;
}
})
let res = […hash].sort((a,b) => a[1]-b[1])
//object can't iterator directory
console.log(res)
};
var topKFrequent2 = function(nums, k) {
let hash = new Map();
nums.forEach(row => {
if(hash.has(row)) {
hash.set(row,hash.get(row)+1);
}else {
hash.set(row,1);
}
})
console.log('hash key sort:');
for (var key of hash.keys()) {
console.log(key);
}
let res = […hash].sort((a,b) => b[1]-a[1])
res = res.map(item => item[0])
return res.slice(0,k)
};
const nums = [1,1,1,2,2,4], k = 2
console.log(topKFrequent2(nums,k))
view raw leetCode347.js hosted with ❤ by GitHub

=> 動手試試


延伸閱讀:

  1. 图解快速排序及双路三路快速排序

2. 各個瀏覽器對於 sort 的實作方式不同探討

Chrome engine v8 對於 Javascript 的 sort 實作:
基礎是用 Quick Sort 實作, 若陣列長度小於 10 則改用 Insert Sort 實作

The sorting algorithm itself is rather straightforward: The basis is a Quicksort with an Insertion Sort fall-back for shorter arrays (length < 10). The Insertion Sort fall-back was also used when Quicksort recursion reached a sub-array length of 10. Insertion Sort is more efficient for smaller arrays.

Reference from ChromeDoc
Reference: 淺談 JS sort() 到背後排序方法
Reference: 從 Array 的 sort 方法,聊到各瀏覽器的實作,沒想到 Chrome 和FireFox 的排序如此不同

謝謝你的閱讀,

以上是我的讀書筆記整理,歡迎各方大大討論交流校正~

喜歡的話, Medium 同步更新,歡迎前往拍手:)

我們下次見~

[React] 來用 canvas 截圖影片吧!

技術摘要:

video-react load video
use canvas to capture
canvas.getContext(‘2d’)
drawImage

issue:

chrome v.76 bug 第一張截圖全黑
https://support.google.com/chrome/thread/12663566?hl=en

canvas dom

https://stackoverflow.com/questions/9152224/add-canvas-to-a-page-with-javascript


feature:

  1. 偵測寬高,做旋轉
  2. Hevc video support:

HVC type handle

mac 10.12 後才支援 hevc

edge 至今都不支援 hevc

詳細待補…

JS Array的各種常見操作分類- 來掃一遍mdn array fn吧


由於 wordpress 對於撰寫code 的編輯方式越來越難用了,後續會轉移到 medium為主,歡迎前往閱讀~
=> 前往Medium好讀版


Js 的 Array 有很多好用的function,這次複習著重在每個fn的 input , output ,期許增加熟悉度,能使用得更上手!

開始囉~


1.刪減新增元素

arr.filter

let modifiedArray = arr.filter(callback(currentValue[, index[, array]]) {
// return element for newArray, if true
}[, thisArg]);

arr.slice

let modifiedArray = arr.slice([start[, end]])

特性:shallow copy only,測試如下:https://sasacode.wordpress.com/media/a1acc397be19fbe00166725132d5b41a

arr.splice

let modifiedArray = array.splice(start[, deleteCount[, item1[, item2[, ...]]]])

arr.shift

arr.unshift

arr.pop

arr.push

詳細可參考 JS 20 ways to control Array


2. 遍歷元素

arr.map

let newArray = arr.map(callback(currentValue[, index[, array]]) {
// return every element for newArray, after executing something
}[, thisArg]);

arr.forEach

arr.reduce

let anything = arr.reduce(callback( accumulator, currentValue, [, index[, array]] ){
// return anything you want
}[, initialAccumulatorValue])

arr.reduceRight


3.查找元素

arr.find
arr.findIndex
arr.indexOf
arr.lastIndexOf
arr.includes

let boolean = arr.includes(valueToFind[, fromIndex])

4. 檢驗元素

arr.every
arr.some

5. initiate Array , combined Array

arr.fill

arr.concat


5. 類型轉換

array -> obj

let newObj = Object.fromEntries(iterable);

Some built-in types, such as Array or Map, have a default iteration behavior, while other types (such as Object) do not.

obj -> array

let newArray = array.entries(obj)

array->string

let newStr = arr.join([separator])
let newStr = arr.toString()

string -> array

let newArray = Array.from(arrayLike [, mapFn [, thisArg]])

延伸:What is arrayLike ? What’s different with Array?
 Array-like objects do not have any of Array’s functions, and for-in loops don’t even work!
see reference

let newArray = str.split([separator[, limit]])

進階使用技巧參考:上手使用 JavaScript 的 Map、Reduce 吧!