考慮到文章過長,不便於閱讀,這裡分出第二篇,如有後續,每15個知識點分為一篇... #29 - 使用緩存的記憶讓遞歸函數加速運行波非那切數列(Fibonacci sequence)想必大家都不陌生(針對學霸而言,在這之前本獸完全不知道這是個什麼鬼,雖然經常會用到遞歸),我們可以在20秒內寫出以下的函
考慮到文章過長,不便於閱讀,這裡分出第二篇,如有後續,每15個知識點分為一篇...
#30 - 將true的/false的值轉換成boolean類型
你可以通過!!操作將為true/為false的值轉換為boolean類型。
!!"" // false !!0 // false !!null // false !!undefined // false !!NaN // false !!"hello" // true 字元串長度不為0時 !!1 // true !!{} // true !![] // true
### 2016-01-31 更新 ###
#29 - 使用緩存的記憶讓遞歸函數加速運行
波非那切數列(Fibonacci sequence)想必大家都不陌生(針對學霸而言,在這之前本獸完全不知道這是個什麼鬼,雖然經常會用到遞歸),我們可以在20秒內寫出以下的函數:
var fibonacci = function(n){ return n < 2 ? n : fibonacci(n-1) + fibonacci(n-2); }
它確實是運行了,但是效率並不高。它做了大量的重覆計算的工作,我們可以通過緩存先前計算結果的方法來加快速度。
var fibonacci = (function(){ var cache = { 0: 0, 1: 1 }; return function(n){ return n <= 1 ? cache[n] : (cache[n] = cache[n-1] + cache[n-2]); } })()
同時,我們可以定義一個高階函數,該高階函數將接收一個函數作為參數並且該參數函數會返回一個已記憶的緩存版本。
var memoize = function(func){ var cache = {}; return function(){ var key = Array.prototype.slice.call(arguments).toString(); return key in cache ? cache[key] : (cache[key] = func.apply(this,arguments)); } } fibonacci = memoize(fibonacci);
我們可以在很多情況下使用memoize()
GCD(最大公約數)
var gcd = memoize(function(a,b){ var t; if (a < b) t=b, b=a, a=t; while(b != 0) t=b, b = a%b, a=t; return a; }) gcd(27,183); //=> 3
階乘計算
var factorial = memoize(function(n) { return (n <= 1) ? 1 : n * factorial(n-1); }) factorial(5); //=> 120
#28 - 柯里化(局部套用)和局部應用
柯里化(局部套用)
柯里化將以下函數
f:X*Y_-R
轉換為以下形式的函數:
f':X_>(Y->R)
我們僅調用f'配合第一個參數,來代替配合兩個參數來調用f。返回的結果是一個我們配合第二個參數調用的函數並且返回結果值的函數。
因此,如果我們調用未柯里化的函數f:
f(3,5)
然後調用柯里化的函數f':
f(3)(5)
案例:
未柯里化的add()
function add(x, y) { return x + y; } add(3, 5); // _> 8
柯里化的add()
function addC(x) { return function (y) { return x + y; } } addC(3)(5); // _> 8
柯里化的運演算法則:
柯里化取一個二元函數,返回一個包含一元函數的一元函數。
柯里化:
(X × Y → R) → (X → (Y → R))
javascript 代碼:
function curry(f) { return function(x) { return function(y) { return f(x, y); } } }
局部應用:
局部應用將以下函數
f:X*Y_-R
給定一個固定的值,傳入一個參數,以此產生個新函數:
f':Y->R
f'和f不一樣,但是他只需要填入第二個參數,這就是為什麼f'比f的參數數量少一個。
案例:
綁定函數的第一個參數通過函數plus5進行與加5:
function plus5(y) { return 5 + y; } plus5(3); // _> 8
局部應用的運演算法則:
局部調用取一個二元函數和一個值,並且產生一個一元函數。
局部: ((X × Y → R) × X) → (Y → R)
javascript 代碼:
function partApply(f, x) { return function(y) { return f(x, y); } }
### 2016-01-30 更新 ###
#27 - Short-circuit evaluation(沒看懂是個什麼東東,於是暫且不翻譯它)
Short-circuit evaluation指出,僅當第一個參數的論據不足以確定表達式的值的時候才執行第二個參數:當第一個參數的AND(&&)函數返回false,整體value也將是false;當第一個參數的OR(||)為true,則整體的值也是true。
以下是test情況和isTrue函數和isFalse函數:
var test = true;
var isTrue = function(){
console.log('Test is true.');
};
var isFalse = function(){
console.log('Test is false.');
};
使用AND(&&)邏輯:
// 正常語句.
// 正常語句.
if(test){
isTrue(); // _> true
}
// 以上的功能使用 '&&' 完成
( test && isTrue() ); // _> true
使用OR(||)邏輯:
test = false;
if(!test){
isFalse(); // _> false.
}
( test || isFalse()); // _> false.
邏輯OR(||)語句同時可以用來給函數的參數設置預設值:
function theSameOldFoo(name){
name = name || 'Bar' ;
console.log("My best friend's name is " + name);
}
theSameOldFoo(); // My best friend's name is Bar
theSameOldFoo('Bhaskar'); // My best friend's name is Bhaskar
邏輯AND(&&)語句同時可以用來避免使用undefined的屬性的例外,案例:
var dog = {
bark: function(){
console.log('Woof Woof');
}
};
// 調用 dog.bark();
dog.bark(); // Woof Woof.
//但是如果bog是undefined, dog.bark() 會拋出個錯誤 "Cannot read property 'bark' of undefined."
// 我們可以使用 && 來避免這種情況.
dog&&dog.bark(); // 這樣的話,只有在bog存在的時候才會調用 dog.bark()
### 2016-01-28 更新 ###
#26 - 字元串列表的過濾和排序
你可能會有一個很大的字元串列表,你需要去對這個數組進行過濾和按字母排序。
在我們的例子中,我們將使用JavaScript來保存一系列的不同語言的關鍵字,但正如你所見,他們之間有重覆,也沒按字母順序進行排序。所以這是一個完美的字元串列表(數組)來測試這個JavaScript小技巧。
var keywords = ['do', 'if', 'in', 'for', 'new', 'try', 'var', 'case', 'else', 'enum', 'null', 'this', 'true', 'void', 'with', 'break', 'catch', 'class', 'const', 'false', 'super', 'throw', 'while', 'delete', 'export', 'import', 'return', 'switch', 'typeof', 'default', 'extends', 'finally', 'continue', 'debugger', 'function', 'do', 'if', 'in', 'for', 'int', 'new', 'try', 'var', 'byte', 'case', 'char', 'else', 'enum', 'goto', 'long', 'null', 'this', 'true', 'void', 'with', 'break', 'catch', 'class', 'const', 'false', 'final', 'float', 'short', 'super', 'throw', 'while', 'delete', 'double', 'export', 'import', 'native', 'public', 'return', 'static', 'switch', 'throws', 'typeof', 'boolean', 'default', 'extends', 'finally', 'package', 'private', 'abstract', 'continue', 'debugger', 'function', 'volatile', 'interface', 'protected', 'transient', 'implements', 'instanceof', 'synchronized', 'do', 'if', 'in', 'for', 'let', 'new', 'try', 'var', 'case', 'else', 'enum', 'eval', 'null', 'this', 'true', 'void', 'with', 'break', 'catch', 'class', 'const', 'false', 'super', 'throw', 'while', 'yield', 'delete', 'export', 'import', 'public', 'return', 'static', 'switch', 'typeof', 'default', 'extends', 'finally', 'package', 'private', 'continue', 'debugger', 'function', 'arguments', 'interface', 'protected', 'implements', 'instanceof', 'do', 'if', 'in', 'for', 'let', 'new', 'try', 'var', 'case', 'else', 'enum', 'eval', 'null', 'this', 'true', 'void', 'with', 'await', 'break', 'catch', 'class', 'const', 'false', 'super', 'throw', 'while', 'yield', 'delete', 'export', 'import', 'public', 'return', 'static', 'switch', 'typeof', 'default', 'extends', 'finally', 'package', 'private', 'continue', 'debugger', 'function', 'arguments', 'interface', 'protected', 'implements', 'instanceof'];
因為我們不打算改變原始列表,我們將使用一個名為filter的高階函數,它會基於我們傳遞給它的函數進行過濾並且返回一個經過過濾的新數組。判斷語句會將當前關鍵字的索引與新列表中的索引進行比較,如果索引能匹配上,則會將這項推到新的數組中。
最後,我們要使用排序功能以一個比較函數作為唯一的參數來對過濾後的列表進行排序,返回一個按字母順序排序的列表。
var filteredAndSortedKeywords = keywords
.filter(function (keyword, index) {
return keywords.indexOf(keyword) === index;
})
.sort(function (a, b) {
if (a < b) return -1;
else if (a > b) return 1;
return 0;
});
ES6(ES 2015)使用arrow函數將會看上去簡單些:
const filteredAndSortedKeywords = keywords
.filter((keyword, index) => keywords.indexOf(keyword) === index)
.sort((a, b) => {
if (a < b) return -1;
else if (a > b) return 1;
return 0;
});
這是進行過濾和排序後的關鍵字列表:
console.log(filteredAndSortedKeywords);
// ['abstract', 'arguments', 'await', 'boolean', 'break', 'byte', 'case', 'catch', 'char', 'class', 'const', 'continue', 'debugger', 'default', 'delete', 'do', 'double', 'else', 'enum', 'eval', 'export', 'extends', 'false', 'final', 'finally', 'float', 'for', 'function', 'goto', 'if', 'implements', 'import', 'in', 'instanceof', 'int', 'interface', 'let', 'long', 'native', 'new', 'null', 'package', 'private', 'protected', 'public', 'return', 'short', 'static', 'super', 'switch', 'synchronized', 'this', 'throw', 'throws', 'transient', 'true', 'try', 'typeof', 'var', 'void', 'volatile', 'while', 'with', 'yield']
### 2016-01-27 更新 ###
#25 - 使用立即執行函數表達式
被稱為"Iffy"(IIFE-立即執行函數表達式)是一個在JavaScript中挺有用的並且能夠立即調用的匿名函數。
(function() {
// Do something
})()
用括弧將匿名函數包裹起來,會將函數匿名函數轉化成一個函數表達式或者變數表達式。因此,我們用一個未命名的函數表達式代替全局作用域下(或其他任何地方)的簡單的匿名函數。
類似的,我們也可以為他創建一個名稱,立即執行函數:
(someNamedFunction = function(msg) {
console.log(msg || "Nothing for today !!")
}) (); // 輸出 -> Nothing for today !!
someNamedFunction("Javascript rocks !!"); // 輸出 -> Javascript rocks !!
someNamedFunction(); // 輸出 -> Nothing for today !!
更多的信息,點擊以下的鏈接 link1 link2
演示:jsPerf
### 2016-01-26 更新 ###
#24 - 使用 === 代替 ==
==(或者!=)做對比的時候會將進行對比的兩者轉換到同一類型再比較。===(或者!==)則不會,他會將進行對比的兩者做類型對比和值對比,相對於 == ,=== 的對比會更加嚴謹。
[10] == 10 // true [10] === 10 // false "10" == 10 // true "10" === 10 // false [] == 0 // true [] === 0 // false "" == false // true 但是 true == "a" 是false "" === false // false
#23 - 轉換數值的更加的方法
將字元串轉換為數字是非常常見的。最簡單和最快的(jspref)方式來實現,將使用+(加)演算法。
var one = '1';
var numberOne = +one; // Number 1
你也可以使用-(減號)演算法的轉換類型並且變成負數值。
var one = '1';
var negativeNumberOne = -one; // Number -1
#22 - 清空一個數組
你定義一個數組,並希望清空它的內容。通常,你會這樣做:
var list = [1, 2, 3, 4];
function empty() {
//清空數組
list = [];
}
empty();
但是還有一種更高性能的方法。
你可以使用這些代碼:
var list = [1, 2, 3, 4];
function empty() {
//清空數組
list.length = 0;
}
empty();
· list = [] 將一個變數指定個引用到那個數組,而其他引用都不受影響。這意味著,對於先前數組的內容的引用仍然保留在記憶體中,從而導致記憶體泄漏。
· list.length = 0 刪除數組內的所有東西,這不需要引用任何其他的東西
然而,如果你有一個copy的數組(A和copy-A),如果你使用list.length = 0 刪除其內容,副本也會失去它的內容。
var foo = [1,2,3];
var bar = [1,2,3];
var foo2 = foo;
var bar2 = bar;
foo = [];
bar.length = 0;
console.log(foo, bar, foo2, bar2);
//[] [] [1, 2, 3] []
StackOverflow上的更多詳情:difference-between-array-length-0-and-array
#21 - 對數組排序進行"洗牌"(隨機排序)
這段代碼在這裡使用Fisher Yates洗牌演算法給一個指定的數組進行洗牌(隨機排序)。
function shuffle(arr) {
var i,
j,
temp;
for (i = arr.length - 1; i > 0; i--) {
j = Math.floor(Math.random() * (i + 1));
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
return arr;
};
案例:
var a = [1, 2, 3, 4, 5, 6, 7, 8];
var b = shuffle(a);
console.log(b);
// [2, 7, 8, 6, 5, 3, 1, 4]
網友@幻天芒在評論中提供的隨機技巧:
arr.sort(function(){return Math.random() - 0.5;});
實測可用,感謝分享!(性能方面相差3-4倍之間,shuffle執行比較快,根據個人喜歡來使用)
#20 - 返回對象的函數能夠用於鏈式操作
當創建面向對象的JavaScript對象的function時,函數返回一個對象將能夠讓函數可鏈式的寫在一起來執行。
function Person(name) {
this.name = name;
this.sayName = function() {
console.log("Hello my name is: ", this.name);
return this;
};
this.changeName = function(name) {
this.name = name;
return this;
};
}
var person = new Person("John");
person.sayName().changeName("Timmy").sayName();
//Hello my name is: John
//Hello my name is: Timmy
#19 - 字元串安全連接
假設你有一些類型未知的變數,你想將它們連接起來。可以肯定的是,演算法操作不會在級聯時應用:
var one = 1;
var two = 2;
var three = '3';
var result = ''.concat(one, two, three); //"123"
這樣的連接不正是你所期望的。相反,一些串聯和相加可能會導致意想不到的結果:
var one = 1;
var two = 2;
var three = '3';
var result = one + two + three; //"33" 而不是 "123"
談到性能,對join和concat進行比較,他們的執行速度是幾乎一樣的。你可以在MDN瞭解更多與concat相關的知識
#18 - 更快的四捨五入
今天的技巧是關於性能。見到過雙波浪線"~~"操作符嗎?它有時也被稱為double NOT運算符。你可以更快的使用它來作為Math.floor()替代品。為什麼呢?
單位移~將32位轉換輸入-(輸入+1),因此雙位移將輸入轉換為-(-(輸入+1)),這是個趨於0的偉大的工具。對於輸入的數字,它將模仿Math.ceil()取負值和Math.floor()取正值。如果執行失敗,則返回0,這可能在用來代替Math.floor()失敗時返回一個NaN的時候發揮作用。
// 單位移 console.log(~1337) // -1338 // 雙位移 console.log(~~47.11) // -> 47 console.log(~~-12.88) // -> -12 console.log(~~1.9999) // -> 1 console.log(~~3) // -> 3 //失敗的情況 console.log(~~[]) // -> 0 console.log(~~NaN) // -> 0 console.log(~~null) // -> 0 //大於32位整數則失敗 console.log(~~(2147483647 + 1) === (2147483647 + 1)) // -> 0
雖然~~可能有更好的表現,為了可讀性,請使用Math.floor()。
#17 - Node.js:讓module在沒被require的時候運行
在node里,你可以根據代是運行了require('./something.js')還是node something.js,來告訴你的程式去做兩件不同的事情。如果你想與你的一個獨立的模塊進行交互,這是很有用的。
if (!module.parent) {
// 運行 `node something.js`
app.listen(8088, function() {
console.log('app listening on port 8088');
})
} else {
// 使用 `require('/.something.js')`
module.exports = app;
}
更多信息,請看the documentation for modules
#16 - 給回調函數傳遞參數
在預設情況下,你無法將參數傳給回調函數,如下:
function callback() {
console.log('Hi human');
}
document.getElementById('someelem').addEventListener('click', callback);
你可以採取JavaScript閉包的優點來給回調函數傳參,案例如下:
function callback(a, b) {
return function() {
console.log('sum = ', (a+b));
}
}
var x = 1, y = 2;
document.getElementById('someelem').addEventListener('click', callback(x, y));
什麼是閉包呢?閉包是指一個針對獨立的(自由)變數的函數。換句話說,閉包中定義的函數會記住它被創建的環境。瞭解更多請參閱MDN所以這種方式當被調用的時候,參數X/Y存在於回調函數的作用域內。
另一種方法是使用綁定方法。例如:
var alertText = function(text) {
alert(text);
};
document.getElementById('someelem').addEventListener('click', alertText.bind(this, 'hello'));
兩種方法在性能上有一些略微區別,詳情參閱jsperf
### 2016-01-25 更新 ###
上一篇地址:( 譯、持續更新 ) JavaScript 上分小技巧(一)