360前端星學習筆記-如何學好JavaScript(2)

liusGG發表於2018-06-09

一、區域性細節控制

img
遇到某些需求,我們怎麼樣設計方法、功能呢?實現功能可能涉及一些邊界條件,複雜的邏輯,那麼如何解決呢?首先,我們先了解一下函數語言程式設計吧。

1.1 逐漸消失的方塊

img

實現上面的需求,我們可能會這樣寫:

<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 舉個栗子?:

img

以開門舉例,因為門與人是兩個物件,我們可以進行他們資料抽象,但開門這個動作,我們可以對他們進行過程抽象。

我們如何進行過程抽象呢?來看一下下面的程式碼。

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,這樣就只能呼叫一次了。

列印結果:

img

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)

自身輸入函式或返回函式,都被稱為高階函式

img

舉個栗子?

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的庫。

五、作業

img

相關文章