一、區域性細節控制
遇到某些需求,我們怎麼樣設計方法、功能呢?實現功能可能涉及一些邊界條件,複雜的邏輯,那麼如何解決呢?首先,我們先了解一下函數語言程式設計吧。1.1 逐漸消失的方塊
實現上面的需求,我們可能會這樣寫:
<style>
#block {
float: left;
color: white;
text-align: center;
width: 150px;
height: 150px;
line-height: 150px;
background-color: #37f;
transition: opacity 2s;
}
#block.hide{
opacity: 0;
}
</style>
<div id="block" class="large">Click Me</div>
<p>
文字內容文字內容文字內容文字內容文
字內容文字內容文字內容文字內容文字內
容文字內容文字內容文字內容文字內容文字
內容文字內容文字內容文字內容文字內容文
字內容文字內容文字內容文字內容文
字內容文字內容文字內容文字內容文字內
容文字內容文字內容文字內容文字內容文字
內容文字內容文字內容文字內容文字內容文
字內容文字內容文字內容文字內容文
字內容文字內容文字內容文字內容文字內
容文字內容文字內容文字內容文字內容文字
內容文字內容文字內容文字內容文字內容
</p>
<script>
block.onclick = function(evt){
console.log('hide');
evt.target.className = 'hide';
setTimeout(function(){
document.body.removeChild(block);
}, 2000);
};
</script>
複製程式碼
但其實這樣寫的程式碼是由bug的,當我們連續多次點選時,會多次執行removeChild方法,即使消失會有一個動畫,但元素只能remove掉一次,所以會報錯。
1.2 如何解決這個問題呢?
- 比較土一點的方法就是點選完一次把點選事件取消掉
block.onclick = function(evt){
block.onclick = null;
console.log('hide');
evt.target.className = 'hide';
setTimeout(function(){
document.body.removeChild(block);
}, 2000);
};
複製程式碼
1.3 討論:如何處理 “只能執行一次”
首先我們要談談一個概念:過程抽象 當我們寫程式碼的時候,我們都習慣對資料進行抽象,把一些現實中的物件抽象成結構化的資料,但除了資料抽象還有一種叫過程抽象,我們不抽象這些資料,而是去抽象行為。
1.4 舉個栗子?:
以開門舉例,因為門與人是兩個物件,我們可以進行他們資料抽象,但開門這個動作,我們可以對他們進行過程抽象。
我們如何進行過程抽象呢?來看一下下面的程式碼。
1.5 once
function once(fn){
return function(...args){
if(fn){
let ret = fn.apply(this, args);
fn = null;
return ret;
}
}
}
function foo(idx){
console.log(`I'm called:${idx}`);
}
foo(0);
foo(1);
foo(2);
foo = once(foo);
foo(3);
foo(4);
foo(5);
複製程式碼
once這個函式有一個引數,這個引數是一個function,它又return另外一個function,這種情況下,返回函式的函式,我們叫它高階函式。once返回的函式拿到fn這個引數,判斷一下這個引數,如果存在,我們讓這個函式等於null,這樣就只能呼叫一次了。
列印結果:
0,1,2 正常列印出來,3之後只能執行一次,所以4,5不會列印出來。
once可以傳入任何一個function,任何函式需要實現只呼叫一次這個功能時,就呼叫到once這個函式裡就可以了。
1.6 所以最後這樣寫就不會有bug了。
function once(fn){
return function(...args){
if(fn){
let ret = fn.apply(this, args);
fn = null;
return ret;
}
}
}
block.onclick = once(function(evt){
console.log('hide');
evt.target.className = 'hide';
setTimeout(function(){
document.body.removeChild(block);
}, 2000);
});
複製程式碼
1.7 節流
function throttle(fn, time = 500){
let timer;
return function(...args){
if(timer == null){
fn.apply(this, args);
timer = setTimeout(() => {
timer = null;
}, time)
}
}
}
複製程式碼
應用場景:
- 比如我們想改變視窗大小時,等到移動完滑鼠後再去重新繪製頁面。
- 當滑鼠移動結束時,再去繪製頁面。
- 點選付款按鈕
1.8 防抖
比如300毫秒之後觸發。
function debounce(fn){
let timer = null
return function(...args){
if(timer != null) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn.apply(this, args)
timer = null
}, 300)
}
}
複製程式碼
二、指令式與宣告式程式設計
- 指令式程式設計傾向於怎麼做?
- 宣告式程式設計傾向於做什麼?
2.1 指令式程式設計正規化:How to do?
let list = [1, 2, 3, 4];
let mapArr = [];
for(let i = 0; i < list.length; i++) {
mapArr.push(list[i] * 2);
}
複製程式碼
2.2 宣告式程式設計正規化:what to do?
let list = [1, 2, 3, 4];
const double = x => x * 2;
list.map(double);
複製程式碼
2.3 reduce
比如實現一個加法,減法。
版本1
function add(x, y){
return x + y;
}
function sub(x, y){
return x - y;
}
console.log(add(add(add(1,2),3),4)); // 不好!!
console.log([1, 2, 3, 4].reduce(add));
console.log([1, 2, 3, 4].reduce(sub));
複製程式碼
版本2
基於上面,我們可以封裝兩個函式,可以傳入多個引數,實現累加/減
function add(x, y){
return x + y;
}
function sub(x, y){
return x - y;
}
function addMany(...args){
return args.reduce(add);
}
function subMany(...args){
return args.reduce(sub);
}
console.log(addMany(1,2,3,4));
console.log(subMany(1,2,3,4));
複製程式碼
還需要改進的地方:對於這兩個方法,都是呼叫了reduce,只不過傳的引數不同;每實現一個功能就得封裝一個*many的方法。
版本3
function iterative(fn){
return function(...args){
return args.reduce(fn.bind(this));
}
}
const add = iterative((x, y) => x + y);
const sub = iterative((x, y) => x - y);
console.log(add(1,2,3,4));
console.log(sub(1,2,3,4));
複製程式碼
這個方法是一個高階的函式,它返回一個function,然後arguments呼叫reduce方法。 這樣就可以實現多個數的相加和相減,還有就是如果增加其他功能,只需要給iterative函式傳運算規則即可。
三、高階函式(High-ordered functions)
自身輸入函式或返回函式,都被稱為高階函式
舉個栗子?
3.1 實現toggle
版本1
switcher.onclick = function(evt){
if(evt.target.className === 'on'){
evt.target.className = 'off';
}else{
evt.target.className = 'on';
}
}
複製程式碼
版本2
實現一個函式toggle來實現切換狀態這個行為,通過return一個函式,使用shift()和push()來實現切換狀態.
這樣優化的好處:可以實現任意個數的狀態切換,比如把兩個狀態擴充套件成三態切換或者多型切換,直接給toggle裡新增狀態即可,擴充套件性強。
function toggle(...actions){
return function(...args){
let action = actions.shift();
actions.push(action);
return action.apply(this, args);
}
}
switcher.onclick = toggle(
evt => evt.target.className = 'warn',
evt => evt.target.className = 'off',
evt => evt.target.className = 'on'
);
複製程式碼
版本3
我們實現toggle這個函式的時候,還是通過push()和shift()這種指令式的方法實現的,所以我們還可以改進。
使用迭代器loop,來迴圈list陣列,通過迭代器的next()方法實現切換狀態。
function * loop(list, max = Infinity){
let i = 0;
//noprotect
while(i < max){
yield list[i++ % list.length];
}
}
function toggle(...actions){
let action = loop(actions);
return function(...args){
return action.next().value.apply(this, args);
}
}
switcher.onclick = toggle(
evt => evt.target.className = 'warn',
evt => evt.target.className = 'off',
evt => evt.target.className = 'on'
);
複製程式碼
四、升級API
Write less,do more!
學習了以上程式設計的技巧,我們可以通過函數語言程式設計來改良我們的api設計,實現一個小型JavaScript的庫。