亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網(wǎng)為廣大站長提供免費收錄網(wǎng)站服務(wù),提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.430618.com 】, 免友鏈快審服務(wù)(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

高階函數(shù)不會用?教你JS中最實用的幾個高階函數(shù)用法

 

作者:chenhongdong

來源:https://juejin.im/post/5ad6b34a6fb9a028cc61bfb3

不廢話,先來看下什么是高階函數(shù)

高階函數(shù)

  • 函數(shù)可以作為參數(shù)傳遞
  • 函數(shù)可以作為返回值輸出

函數(shù)作為參數(shù)傳遞

  • 回調(diào)函數(shù)
    • 在ajax異步請求的過程中,回調(diào)函數(shù)使用的非常頻繁
    • 在不確定請求返回的時間時,將callback回調(diào)函數(shù)當(dāng)成參數(shù)傳入
    • 待請求完成后執(zhí)行callback函數(shù)

下面看個簡單的demo

說實在的本來只是個簡單的,不過越寫越興奮,就弄成了個小demo了,大家也可以copy下去自己添油加醋一下(寫成各種版本),樂呵一下吧,PS:由于代碼過多占用文章,將css樣式去掉了,樣式的實現(xiàn)大家隨意發(fā)揮就好了

  • html結(jié)構(gòu)
<body>
  <div id="box" class="clearfix"></div>
<script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>
<script src="./index.js"></script>
</body>

js部分

// index.js
// 回調(diào)函數(shù)
// 異步請求
let getInfo = function (keywords, callback) {
  $.ajax({
    url: 'http://musicapi.leanApp.cn/search',  // 以網(wǎng)易云音樂為例
    data: {
      keywords
    },
    success: function (res) {
      callback && callback(res.result.songs);
    }
  })
};
$('#btn').on('click', function() {
  let keywords = $(this).prev().val();
  $('#loading').show();
  getInfo(keywords, getData);});
// 加入回車
$("#search_inp").on('keyup', function(e){
  if (e.keyCode === 13) {
    $('#loading').show();
    getInfo(this.value, getData);
  }
});
function getData(data) {
  if (data && data.length) {
    let html = render(data);
    // 初始化Dom結(jié)構(gòu)
    initDom(html, function(wrap) {
      play(wrap);
    });
  }
}
// 格式化時間戳
function formatDuration(duration) {
  duration = parseInt(duration / 1000);     // 轉(zhuǎn)換成秒
  let hour = Math.floor(duration / 60 / 60),
      min = Math.floor((duration % 3600) / 60), 
      sec = duration % 60,        result = '';
  result += `${fillIn(min)}:${fillIn(sec)}`;
  return result;}function fillIn(n) {
    return n < 10 ? '0' + n : '' + n;
  }
let initDom = function (tmp, callback) {
  $('.item').remove();
  $('#loading').hide();
  $('#box').append(tmp);
  // 這里因為不知道dom合適才會被完全插入到頁面中
  // 所以用callback當(dāng)參數(shù),等dom插入后再執(zhí)行callback
  callback && callback(box);
};
let render = function (data) {
  let template = '';
  let set = new Set(data);
  data = [...set];    // 可以利用Set去做下簡單的去重,可忽略這步
  for (let i = 0; i < 8; i++) {
    let item = data[i];
    let name = item.name;
    let singer = item.artists[0].name;
    let pic = item.album.picUrl;
    let time = formatDuration(item.duration);
    template += `
<div class="item">
<div class="pic" data-time="${time}">
<span></span>
<img src="${pic}" />
</div>
<h4>${name}</h4>
<p>${singer}</p>
<audio src="http://music.163.com/song/media/outer/url?id=${item.id}.mp3"></audio>
</div>`;
  }
  return template;
};
let play = function(wrap) {
  wrap = $(wrap);
  wrap.on('click', '.item', function() {
    let self = $(this),
        $audio = self.find('audio'),
        $allAudio = wrap.find('audio');
    for (let i = 0; i < $allAudio.length; i++) {
      $allAudio[i].pause();
    }
    $audio[0].play(); 
    self.addClass('play').siblings('.item').removeClass('play');
  });
};

按照上面的代碼啪啪啪,就會得到下面這樣的效果,一起來看下吧

高階函數(shù)不會用?教你JS中最實用的幾個高階函數(shù)用法

 

不過依然感謝網(wǎng)易云音樂提供的API接口,讓我們聆聽美妙好音樂

  • 好了回歸主旋律,前面的戲份有點過了,不知不覺居然寫了個小demo,確實有點過分了
  • 本來是說一下函數(shù)作為參數(shù)傳遞的應(yīng)用,寫的太多了,趕緊調(diào)轉(zhuǎn)船頭繼續(xù)講吧

函數(shù)作為返回值輸出

親們,函數(shù)作為返回值輸出的應(yīng)用場景那就太多了,這也體現(xiàn)了函數(shù)式編程的思想。其實從閉包的例子中我們就已經(jīng)看到了關(guān)于高階函數(shù)的相關(guān)內(nèi)容了,哈哈

還記得在我們?nèi)ヅ袛鄶?shù)據(jù)類型的時候,我們都是通過Object.prototype.toString來計算的。每個數(shù)據(jù)類型之間只是'[object XXX]'不一樣罷了

所以在我們寫類型判斷的時候,一般都是將參數(shù)傳入函數(shù)中,這里我簡單寫一下實現(xiàn),咱們先來看看

function isType(type) {
  return function(obj) {
    return Object.prototype.toString.call(obj) === `[object ${type}]
   }
}
const isArray = isType('Array');
const isString = isType('String');
console.log(isArray([1, 2, [3,4]]); // true
console.log(isString({});           // false

其實上面實現(xiàn)的isType函數(shù),也屬于偏函數(shù)的范疇,偏函數(shù)實際上是返回了一個包含預(yù)處理參數(shù)的新函數(shù),以便之后可以調(diào)用

另外還有一種叫做預(yù)置函數(shù),它的實現(xiàn)原理也很簡單,當(dāng)達到條件時再執(zhí)行回調(diào)函數(shù)

function after(time, cb) {
  return function() {
    if (--time === 0) {
      cb();
    }
  }
}
// 舉個栗子吧,吃飯的時候,我很能吃,吃了三碗才能吃飽
let eat = after(3, function() {
  console.log('吃飽了');
});
eat();
eat();
eat();

上面的eat函數(shù)只有執(zhí)行3次的時候才會輸出'吃飽了',還是比較形象的。

這種預(yù)置函數(shù)也是js中巧妙的裝飾者模式的實現(xiàn),裝飾者模式在實際開發(fā)中也非常有用,再以后的歲月里我也會好好研究之后分享給大家的

好了,不要停,不要停,再來看一個栗子

// 這里我們創(chuàng)建了一個單例模式
let single = function (fn) {
  let ret;
  return function () {
    console.log(ret);   // render一次undefined,render二次true,render三次true
    // 所以之后每次都執(zhí)行ret,就不會再次綁定了
    return ret || (ret = fn.apply(this, arguments));
  }
};
let bindEvent = single(function () {
  // 雖然下面的renders函數(shù)執(zhí)行3次,bindEvent也執(zhí)行了3次
  // 但是根據(jù)單例模式的特點,函數(shù)在被第一次調(diào)用后,之后就不再調(diào)用了
  document.getElementById('box').onclick = function () {
    alert('click');
  }
  return true;
}); 
let renders = function () {
  console.log('渲染'); 
  bindEvent();
}
renders();
renders();
renders();

這個高階函數(shù)的栗子,可以說一石二鳥啊,既把函數(shù)當(dāng)做參數(shù)傳遞了,又把函數(shù)當(dāng)返回值輸出了。

單例模式也是一種非常實用的設(shè)計模式,在以后的文章中也會針對這些設(shè)計模式去分析的,敬請期待,哈哈,下面再看看高階函數(shù)還有哪些用途

其他應(yīng)用

函數(shù)柯里化

柯里化又稱部分求值,柯里化函數(shù)會接收一些參數(shù),然后不會立即求值,而是繼續(xù)返回一個新函數(shù),將傳入的參數(shù)通過閉包的形式保存,等到被真正求值的時候,再一次性把所有傳入的參數(shù)進行求值

還能闡述的更簡單嗎?在一個函數(shù)中填充幾個參數(shù),然后再返回一個新函數(shù),最后進行求值,沒了,是不是說的簡單了

說的再簡單都不如幾行代碼演示的清楚明白

// 普通函數(shù)
function add(x,y){
  return x + y;
}
add(3,4);   // 7
// 實現(xiàn)了柯里化的函數(shù)
// 接收參數(shù),返回新函數(shù),把參數(shù)傳給新函數(shù)使用,最后求值
let add = function(x){
  return function(y){
    return x + y;
  }
};
add(3)(4);  // 7

以上代碼非常簡單,只是起個引導(dǎo)的作用。下面我們來寫一個通用的柯里化函數(shù)

function curry(fn) {
  let slice = Array.prototype.slice,  // 將slice緩存起來
      args = slice.call(arguments, 1);   // 這里將arguments轉(zhuǎn)成數(shù)組并保存
  return function() {
    // 將新舊的參數(shù)拼接起來
    let newArgs = args.concat(slice.call(arguments));
    return fn.apply(null, newArgs); // 返回執(zhí)行的fn并傳遞最新的參數(shù)
  }
}

實現(xiàn)了通用的柯里化函數(shù),了不起啊,各位很了不起啊。

不過這還不夠,我們還可以利用ES6再來實現(xiàn)一下,請看如下代碼:

// ES6版的柯里化函數(shù)function curry(fn) {
const g = (...allArgs) => allArgs.length >= fn.length ?
      fn(...allArgs) :
(...args) => g(...allArgs, ...args)
return g;
}
// 測試用例const foo = curry((a, b, c, d) => {
console.log(a, b, c, d);});foo(1)(2)(3)(4);    // 1 2 3 4
const f = foo(1)(2)(3);f(5);               // 1 2 3 5

兩種不同的實現(xiàn)思路相同,之后可以試著分析一下

不過大家有沒有發(fā)現(xiàn)我們在ES5中使用的bind方法,其實也利用了柯里化的思想,那么再來看一下下

let obj = {
  songs: '以父之名'
};
function fn() {
    console.log(this.songs);
}
let songs = fn.bind(obj);
songs();   // '以父之名'

為什么這么說?這也看不出什么頭緒啊,別捉急,再來看一下bind的實現(xiàn)原理

Function.prototype.bind = function(context) {
  let self = this,
      slice = Array.prototype.slice,
      args = slice.call(arguments);
  return function() {
        return self.apply(context, args.slice(1));
      }
};

是不是似曾相識,是不是,是不是,有種師出同門的趕腳了啊

反柯里化

啥?反柯里化,剛剛被柯里化弄的手舞足蹈的,現(xiàn)在又出現(xiàn)了個反柯里化,有木有搞錯啊!那么反柯里化是什么呢?簡而言之就是函數(shù)的借用,天下函數(shù)(方法)大家用

比如,一個對象未必只能使用它自身的方法,也可以去借用原本不屬于它的方法,要實現(xiàn)這點似乎就很簡單了,因為call和apply就可以完成這個任務(wù)

(function() {
  // arguments就借用了數(shù)組的push方法
  let result = Array.prototype.slice.call(arguments);
  console.log(result);     // [1, 2, 3, 'hi']
})(1, 2, 3, 'hi');
Math.max.apply(null, [1,5,10]);  // 數(shù)組借用了Math.max方法

從以上代碼中看出來了,大家都是相親相愛的一家人。利用call和apply改變了this指向,方法中用到的this再也不局限在原來指定的對象上了,加以泛化后得到更廣的適用性

反柯里化的話題是由我們親愛的js之父發(fā)表的,我們來從實際例子中去看一下它的作用

let slice = Array.prototype.slice.uncurrying();
(function() {
  let result = slice(arguments);  // 這里只需要調(diào)用slice函數(shù)即可
  console.log(result);    // [1, 2, 3]
})(1,2,3);

以上代碼通過反柯里化的方式,把Array.prototype.slice變成了一個通用的slice函數(shù),這樣就不會局限于僅對數(shù)組進行操作了,也從而將函數(shù)調(diào)用顯得更為簡潔清晰了

最后再來看一下它的實現(xiàn)方式吧,看代碼,更逼真

Function.prototype.uncurrying = function() {
  let self = this;    // self 此時就是下面的Array.prototype.push方法
  return function() {
    let obj = Array.prototype.shift.call(arguments);
    /*
    obj其實是這種樣子的
    obj = {
    'length': 1, 
    '0': 1
    }
    */
    return self.apply(obj, arguments); // 相當(dāng)于Array.prototype.push(obj, 110)
  }
};
let slice = Array.prototype.push.uncurrying();
let obj = {
  'length': 1,
  '0': 1
};
push(obj, 110);
console.log(obj);   // { '0': 1, '1': 110, length: 2 }

其實實現(xiàn)反柯里化的方式不只一種,下面再給大家分享一種,直接看代碼

Function.prototype.uncurrying = function() {
  let self = this;    return function() {
    return Function.prototype.call.apply(self, arguments);
  }
};

實現(xiàn)方式大致相同,大家也可以寫一下試試,動動手,活動一下筋骨

函數(shù)節(jié)流

下面再說一下函數(shù)節(jié)流,我們都知道在onresize、onscroll和mousemove,上傳文件這樣的場景下,函數(shù)會被頻繁的觸發(fā),這樣很消耗性能,瀏覽器也會吃不消的

于是大家開始研究一種高級的方法,那就是控制函數(shù)被觸發(fā)的頻率,也就是函數(shù)節(jié)流了。簡單說一下原理,利用setTimeout在一定的時間內(nèi),函數(shù)只觸發(fā)一次,這樣大大降低了頻率問題

函數(shù)節(jié)流的實現(xiàn)也多種多樣,這里我們實現(xiàn)大家常用的吧

function throttle (fn, wait) {
  let _fn = fn,       // 保存需要被延遲的函數(shù)引用
      timer,
      flags = true;   // 是否首次調(diào)用    return function() {
  let args = arguments,
      self = this;
  if (flags) {    // 如果是第一次調(diào)用不用延遲,直接執(zhí)行即可
    _fn.apply(self, args);
    flags = false;
    return flags;
  }
  // 如果定時器還在,說明上一次還沒執(zhí)行完,不往下執(zhí)行
  if (timer) return false;
  timer = setTimeout(function() { // 延遲執(zhí)行
    clearTimeout(timer);    // 清空上次的定時器
    timer = null;           // 銷毀變量
    _fn.apply(self, args);
  }, wait);
}
}
window.onscroll = throttle(function() {
  console.log('滾動');
}, 500);

給頁面上body設(shè)置一個高度出現(xiàn)滾動條后試試看,比每滾動一下就觸發(fā)來說,大大降低了性能的損耗,這就是函數(shù)節(jié)流的作用,起到了事半功倍的效果,開發(fā)中也比較常用的

分時函數(shù)

我們知道有一個典故叫做:羅馬不是一天建成的;更為通俗的來說,胖紙也不是一天吃成的

體現(xiàn)在程序里也是一樣,我們?nèi)绻淮潍@得了很多數(shù)據(jù)(比如有10W數(shù)據(jù)),然后在前端渲染的時候會卡到爆,瀏覽器那么溫柔的物種都會起來罵娘了

所以在處理這么多數(shù)據(jù)的時候,我們可以選擇分批進行,不用一次塞辣么多,嘴就辣么大

下面來看一下簡單的實現(xiàn)

function timeChunk(data, fn, count = 1, wait) {
  let obj, timer;
  function start() {
    let len = Math.min(count, data.length);
    for (let i = 0; i < len; i++) {
      val = data.shift();     // 每次取出一個數(shù)據(jù),傳給fn當(dāng)做值來用
      fn(val);
    }
  }
  return function() {
    timer = setInterval(function() {
      if (data.length === 0) {    // 如果數(shù)據(jù)為空了,就清空定時器
        return clearInterval(timer);
      }
      start();
    }, wait);   // 分批執(zhí)行的時間間隔 
  }
}
// 測試用例
let arr = [];
for (let i = 0; i < 100000; i++) {  // 這里跑了10萬數(shù)據(jù)
  arr.push(i);
}
let render = timeChunk(arr, function(n) {   // n為data.shift()取到的數(shù)據(jù)
  let div = document.createElement('div');
  div.innerHTML = n;
  document.body.appendChild(div);
}, 8, 20);
render();

惰性加載

兼容現(xiàn)代瀏覽器以及IE瀏覽器的事件添加方法就是一個很好的栗子

// 常規(guī)的是這樣寫的
et addEvent = function(ele, type, fn) {
  if (window.addEventListener) {
    return ele.addEventListener(type, fn, false);
  } else if (window.attachEvent) {
    return ele.attachEvent('on' + type, function() {
      fn.call(ele);
    });
  }};

這樣實現(xiàn)有一個缺點,就是在調(diào)用addEvent的時候都會執(zhí)行分支條件里,其實只需要判斷一次就行了,非要每次執(zhí)行都來一波

下面我們再來優(yōu)化一下addEvent,以規(guī)避上面的缺點,就是我們要實現(xiàn)的惰性加載函數(shù)了

let addEvent = function(ele, type, fn) {
  if (window.addEventListener) {
    addEvent = function(ele, type, fn) {
      ele.addEventListener(type, fn, false);
    }
  } else  if (window.attachEvent) {
    addEvent = function(ele, type, fn) {
      ele.attachEvent('on' + type, function() {
        fn.call(ele)
      }); 
    }
  } 
  addEvent(ele, type, fn);
};

上面的addEvent函數(shù)還是個普通函數(shù),還是有分支判斷。不過當(dāng)?shù)谝淮芜M入分支條件后,在內(nèi)部就會重寫了addEvent函數(shù)

下次再進入addEvent函數(shù)的時候,函數(shù)里就不存在條件判斷了

終點

節(jié)目不早,時間剛好,又到了該要說再見的時候了,來一個結(jié)束語吧

高階函數(shù)

  • 可以把函數(shù)當(dāng)做參數(shù)傳遞和返回值輸出
  • 函數(shù)柯里化
    • 接收參數(shù),返回新函數(shù),把參數(shù)傳給新函數(shù),最后求值
    • 定義
    • 作用
    • 參數(shù)復(fù)用 (add函數(shù)栗子)
    • 提前返回 (惰性加載)
    • 延遲計算 (bind)
  • 反柯里化
    • 統(tǒng)一方法,讓天下沒有不能用的方法
  • 函數(shù)節(jié)流
    • 將頻繁調(diào)用的函數(shù)設(shè)定在一個時間內(nèi)執(zhí)行,防止多次觸發(fā)
  • 分時函數(shù)
    • 一次性加載太多太多數(shù)據(jù),吃不消,可以像node中流一樣,慢慢來,別急
  • 惰性加載
    • 函數(shù)執(zhí)行的分支僅會發(fā)生一次

我勒個去,居然羅列了這么多東西,大家看的也很辛苦了,早睡早起,好好休息吧!

分享到:
標(biāo)簽:高階 函數(shù)
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨大挑戰(zhàn)2018-06-03

數(shù)獨一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運動步數(shù)有氧達人2018-06-03

記錄運動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績評定2018-06-03

通用課目體育訓(xùn)練成績評定