閉包是什麼能吃嗎?

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 – 520, substring()

題目:520. Detect Capital

Given a word, you need to judge whether the usage of capitals in it is right or not.

We define the usage of capitals in a word to be right when one of the following cases holds:

  1. All letters in this word are capitals, like “USA".
  2. All letters in this word are not capitals, like “leetcode".
  3. Only the first letter in this word is capital, like “Google".

Otherwise, we define that this word doesn’t use capitals in a right way.

 


var detectCapitalUse = function(word) {
if(word === word.toLowerCase() || word === word.toUpperCase()){
return true;
}else if(word[0] === word[0].toUpperCase() && word.substring(1) === word.substring(1).toLowerCase()){
return true;
}else{
return false;
}
};

view raw

leetCode520.js

hosted with ❤ by GitHub

JS中的字串可當作char[],直接操作[]

substring( startIndex, [endIndex])
The substring() method returns the part of the string between the start and end indexes, or to the end of the string.

參考:

[JavaScript] 手把手一起入門(二) – 變數 & 基本操作

Javascript刷LeetCode – 242

題目:242. Valid Anagram

Given two strings s and , write a function to determine if t is an anagram of s.

一開始寫,
卡在要怎麼完全比對兩個陣列,
所以查到使用JSON.stringify()。
後來查到,其他答案,利用陣列在串連回字串( join() )去比對,
感覺更直觀了!


var isAnagram = function(s, t) {
let s_ary = s.split('');
let t_ary = t.split('');
return JSON.stringify(s_ary.sort()) == JSON.stringify(t_ary.sort());
};
var isAnagram = function(s, t) {
if(s.length != t.length) return false;
let s_ary = s.split('').sort();
let t_ary = t.split('').sort();
return s_ary.join('') == t_ary.join('');
};

但是運行時間還是很慢,
因此繼續尋找更加解,

注意到以下解法:


var isAnagram = function(s, t) {
if(s.length != t.length) return false;
let tb = {};
for(let i = 0 ; i < s.length ; i++){
let letter = s[i];
if(tb[letter]){
tb[letter]++;
}else{
tb[letter] = 1;
}
}
for(let i = 0 ; i< t.length ; i++){
let letter = t[i];
if(tb[letter]){
tb[letter]–;
}else{
return false;
}
if(tb[letter] < 0 ){
return false;
}
}
return true;
};

覺得很不錯的思維,
根本不需要sort或是用物件記錄住兩方出現的次數,
直接利用一個物件以++記錄s的出現次數,再以–扣掉t出現的次數,
就可以得到解了!

Javascript刷LeetCode – 387, 雙指標遍歷: indexOf , lastIndexOf ; ES6新類型 map 與 set;for of

題目:387. First Unique Character in a String
Given a string, find the first non-repeating character in it and return it’s index. If it doesn’t exist, return -1.

解題思維:

先建立hashTable,再Seacrch
參考:詳細動畫可見:leetCode solution
一開始,我是使用object去紀錄每個字母出現的次數,但到後面要search時發現一個問題


var firstUniqChar = function(s) {
var ary = s.split('');
var counter = {};
for(var i = 0; i< ary.length; i++){
if(!(ary[i] in counter)){
counter[ary[i]] = 1;
}else{
counter[ary[i]] += 1;
}
}
console.log(counter);
Object.keys(counter).map(function(key,index){
if(counter[key] === 1){
return key;
}else{
return -1;
}
})
for(var j = 0 ; j < counter.lenght ; j++){
console.log(counter[j]);
}
};
var a = firstUniqChar("leetcode");
console.log(a);

[object Object] {
c
: 1,
d
: 1,
e
: 3,
l
: 1,
o
: 1,
t
: 1
}

原本儲存進去的key順序亂掉了!
經過一番survey後得知,
Object的key會根據不同瀏覽器被自動排序,是不可依靠的
要控制key的順序,建議使用Array

參考:
https://stackoverflow.com/questions/47727428/axios-stop-automatic-sorting-of-objects-based-on-keys

 

 

但是我覺得object很難直接用[]操作,很麻煩~~~
再繼續survey後,發現ES6出了新的資料型態:

映射的實作
Map v.s. Set

根據MDN的定義:
Map and Set objects contain elements which are iterable in the order of insertion.
key會根據插入的順序

Map object is a simple key/value map and can iterate its elements in insertion order.

Set objects are collections of values. You can iterate its elements in insertion order. A value in a Set may only occur once; it is unique in the Set‘s collection.
每個數值只會唯一不重複

參考:
http://www.jstips.co/zh_cn/javascript/map-to-the-rescue-adding-order-to-object-properties/

 

再搭配一個新的語法: for of 便可快速的遍歷map

根據MDN定義:
The for...of statement creates a loop iterating over iterable objects, including: built-in StringArrayArray-like objects (e.g., arguments or NodeList), TypedArrayMapSet, and user-defined iterables.

參考:
http://www.webhek.com/post/javascript-loop-foreach-for-in-for-of.html

 


var firstUniqChar = function(s) {
var counter = new Map();
for(let word of s){
if(word === undefined)
break;
if(!(counter.has(word))){
counter.set(word,1);
}else{
counter.set(word,counter.get(word)+1);
}
}
for(let [key,value] of counter){
if(counter.get(key) === 1){
return s.search(key);
}
}
return -1;
};
var a = firstUniqChar("leetcode");

注意map元素操作的語法
map.get(element);
map.has(element);

String.search()
searches a string for a specified value, and returns the position of the match.
字串中尋找特定的字元,並返回其索引

 

上面的方法提交會過,但是總覺得太過攏長,
因此立刻去google尋找更佳解,
結果就找到了以下更漂亮簡短的解法!
參考:https://blog.vancel.in/leetcode-387-first-unique-character-in-a-string/


var firstUniqChar = function(s) {
for(let w of s){
if(s.indexOf(w) === s.lastIndexOf(w))
return s.indexOf(w);
}
return -1;
};

利用雙指標的思維

指標i由左邊開始確認,指標j由右邊開始確認,
分別i++ , j– , 直到相撞,
若相撞,表示兩邊出發都只有遇到同一個值,也就是唯一值。

左指標,利用indexOf()
The indexOf() method returns the index within the calling String object of the first occurrence of the specified value, starting the search at fromIndex. Returns -1 if the value is not found.
從左到右,直到找到第一個相符的字元

右指標,利用lastIndexOf()
The lastIndexOf() method returns the index within the calling String object of the last occurrence of the specified value, searching backwards from fromIndex. Returns -1 if the value is not found.
從左到右,直到找到最後一個相符的字元

參考:
https://blog.csdn.net/xiaobangsky/article/details/48313243

使用Javascript刷LeetCode – 657

657. Robot Return to Origin

題目:
There is a robot starting at position (0, 0), the origin, on a 2D plane. Given a sequence of its moves, judge if this robot ends up at (0, 0) after it completes its moves.

The move sequence is represented by a string, and the character moves[i] represents its ith move. Valid moves are R (right), L (left), U (up), and D (down). If the robot returns to the origin after it finishes all of its moves, return true. Otherwise, return false.

Note: The way that the robot is “facing" is irrelevant. “R" will always make the robot move to the right once, “L" will always make it move left, etc. Also, assume that the magnitude of the robot’s movement is the same for each move.
先上解法:


/**
* @param {string} moves
* @return {boolean}
*/
var judgeCircle = function(moves) {
var x = 0 , y = 0;
var ary = moves.split('');
for(var i = 0; i< ary.length ; i++){
switch(ary[i]){
case 'R':
x += 1;
break;
case 'L':
x -= 1;
break;
case 'U':
y -= 1;
break;
case 'D':
y += 1;
break;
}
}
return (x == 0 && x === y) ? 1 : 0;
};

view raw

leetCode657.js

hosted with ❤ by GitHub

Note:

原本看到題目,講到x,y軸就直覺地使用了陣列來記憶位置,但後來看了其他人的解法,發現用兩個變數記錄即可,想想也是,用陣列有點太大材小用了。

看到python解寫到,return x==y==0
感覺很直觀,因此想要照用,結果不小心掉入js的隱型強制轉型(Implicit convert)的陷阱,卡了一下。
x==y 為true , typeof: boolean
true == 0 , true被強制轉型為1, 因此答案會是 1 == 0 為 false

參考:
https://stackoverflow.com/questions/22980471/how-does-a-b-c-comparison-work-in-javascript

https://ithelp.ithome.com.tw/articles/10201512

Javscript刷LeetCode – 709 , 相關:ASCII , Unicode

簡解:使用內建函式


var toLowerCase = function(str) {
return str.toLowerCase();
};

複雜解:不使用內建函式,利用字母的Unicode碼


var toLowerCase = function(str) {
var ary = str.split('');
for(var i = 0 ; i < ary.length ; i++){ if(ary[i] >= 'A' && ary[i] <= 'Z'){
var code = ary[i].<strong>charCodeAt</strong>(0) + 32;
ary[i] = String.<strong>fromCharCode</strong>(code);
}
}
var newStr = ary.join('');
return newStr;
};

//大小寫英文的code號碼相差32

ASCII
ASCII(American Standard Code for Information Interchange,美國訊息交換標準代碼)是一套基於英文字母的字符編碼系統。
最後一次更新為1986年。

Unicode :
Unicode(中文:萬國碼、國際碼、統一碼、單一碼)是電腦科學領域裡的一項業界標準。它對世界上大部分的文字系統進行了整理、編碼,使得電腦可以用更為簡單的方式來呈現和處理文字。
目前最新的版本為2019年5月公布的12.1.0

Unicode字元列表:
https://zh.wikipedia.org/wiki/Unicode%E5%AD%97%E7%AC%A6%E5%88%97%E8%A1%A8#%E6%8B%89%E4%B8%81%E5%AD%97%E6%AF%8D
包含並沿用ASCII列表

參考:
http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html
前往 Medium.com 檢視