jquery判断滚动条-不能使用高阶函数? 教你JS中高阶函数最实用的用法

不废话了,我们先看看哪些是高阶函数

高阶函数 函数作为参数传递

让我们看一个简单的演示:

说实话,只是一个简单的,但是越写越激动,就成了一个小demo了。 也可以抄下来加油加醋(各种版本写的),玩得开心,PS:因为代码太多,占了文章的位置,把css样式去掉,实现自己喜欢的样式。

  1. <body>

  2. <div id="box" class="clearfix"></div>

  3. <script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>

  4. <script src="./index.js"></script>

  5. </body>

js部分

  1. // index.js

  2. // 回调函数

  3. // 异步请求

  4. let getInfo = function (keywords, callback) {

  5. $.ajax({

  6. url: 'http://musicapi.leanapp.cn/search', // 以网易云音乐为例

  7. data: {

  8. keywords

  9. },

  10. success: function (res) {

  11. callback && callback(res.result.songs);

  12. }

  13. })

  14. };


  15. $('#btn').on('click', function() {

  16. let keywords = $(this).prev().val();

  17. $('#loading').show();

  18. getInfo(keywords, getData);

  19. });

  20. // 加入回车

  21. $("#search_inp").on('keyup', function(e){

  22. if (e.keyCode === 13) {

  23. $('#loading').show();

  24. getInfo(this.value, getData);

  25. }

  26. });


  27. function getData(data) {

  28. if (data && data.length) {

  29. let html = render(data);

  30. // 初始化Dom结构

  31. initDom(html, function(wrap) {

  32. play(wrap);

  33. });

  34. }

  35. }

  36. // 格式化时间戳

  37. function formatDuration(duration) {

  38. duration = parseInt(duration / 1000); // 转换成秒

  39. let hour = Math.floor(duration / 60 / 60),

  40. min = Math.floor((duration % 3600) / 60),

  41. sec = duration % 60,

  42. result = '';


  43. result += `${fillIn(min)}:${fillIn(sec)}`;

  44. return result;

  45. }


  46. function fillIn(n) {

  47. return n < 10 ? '0' + n : '' + n;

  48. }


  49. let initDom = function (tmp, callback) {

  50. $('.item').remove();

  51. $('#loading').hide();

  52. $('#box').append(tmp);

  53. // 这里因为不知道dom合适才会被完全插入到页面中

  54. // 所以用callback当参数,等dom插入后再执行callback

  55. callback && callback(box);

  56. };


  57. let render = function (data) {

  58. let template = '';

  59. let set = new Set(data);

  60. data = [...set]; // 可以利用Set去做下简单的去重,可忽略这步

  61. for (let i = 0; i < 8; i++) {

  62. let item = data[i];

  63. let name = item.name;

  64. let singer = item.artists[0].name;

  65. let pic = item.album.picUrl;

  66. let time = formatDuration(item.duration);


  67. template += `

  68. <div class="item">

  69. <div class="pic" data-time="${time}">

  70. <span></span>

  71. <img src="${pic}" />

  72. </div>

  73. <h4>${name}</h4>

  74. <p>${singer}</p>

  75. <audio src="http://music.163.com/song/media/outer/url?id=${item.id}.mp3"></audio>

  76. </div>`;

  77. }

  78. return template;

  79. };


  80. let play = function(wrap) {

  81. wrap = $(wrap);

  82. wrap.on('click', '.item', function() {

  83. let self = $(this),

  84. $audio = self.find('audio'),

  85. $allAudio = wrap.find('audio');


  86. for (let i = 0; i < $allAudio.length; i++) {

  87. $allAudio[i].pause();

  88. }

  89. $audio[0].play();

  90. self.addClass('play').siblings('.item').removeClass('play');

  91. });

  92. };

根据前面的代码,会得到如下的效果,我们来看看

不过还是感谢网易云音乐提供的API接口,让我们聆听美妙的音乐

函数输出作为返回值

各位朋友,函数作为返回值输出的应用场景太多了,这也凸显了函数式编程的思想。其实我们从闭包的例子中就已经听说过高阶函数了,哈哈

还记得我们判断数据类型的时候都是通过Object.prototype.toString来估计的。只有'[object XXX]'在各个数据类型之间是不同的

所以我们在写类型判断的时候,通常都会将参数传入到函数中。 这里我简单写一下实现。 我们先来看看。

  1. function isType(type) {

  2. return function(obj) {

  3. return Object.prototype.toString.call(obj) === `[object ${type}]

  4. }

  5. }


  6. const isArray = isType('Array');

  7. const isString = isType('String');

  8. console.log(isArray([1, 2, [3,4]]); // true

  9. console.log(isString({}); // false

事实上,其中实现的isType函数也属于偏函数的范畴。 偏函数实际上返回一个包含预处理参数的新函数,以便稍后调用

另外,还有一个叫做预设函数,它的实现原理也很简单,当条件满足时,执行回调函数

  1. function after(time, cb) {

  2. return function() {

  3. if (--time === 0) {

  4. cb();

  5. }

  6. }

  7. }

  8. // 举个栗子吧,吃饭的时候,我很能吃,吃了三碗才能吃饱

  9. let eat = after(3, function() {

  10. console.log('吃饱了');

  11. });

  12. eat();

  13. eat();

  14. eat();

上面的 eat 函数只有执行 3 次才能输出 'full',相当形象。

这种预设的功能也是巧妙的装饰器模式在js中的实现。 装饰器模式在实际开发中也很有用。 我会仔细研究一下,以后分享给大家。

好了,别停,别停,我们再看一个栗子

  1. // 这里我们创建了一个单例模式

  2. let single = function (fn) {

  3. let ret;

  4. return function () {

  5. console.log(ret); // render一次undefined,render二次true,render三次true

  6. // 所以之后每次都执行ret,就不会再次绑定了

  7. return ret || (ret = fn.apply(this, arguments));

  8. }

  9. };


  10. let bindEvent = single(function () {

  11. // 虽然下面的renders函数执行3次,bindEvent也执行了3次

  12. // 但是根据单例模式的特点,函数在被第一次调用后,之后就不再调用了

  13. document.getElementById('box').onclick = function () {

  14. alert('click');

  15. }

  16. return true;

  17. });


  18. let renders = function () {

  19. console.log('渲染');

  20. bindEvent();

  21. }


  22. renders();

  23. renders();

  24. renders();

这个高级函数的栗子可以说是一石二鸟。 它不仅将函数作为参数传递,还将函数作为返回值输出。

单例模式也是一种非常实用的设计模式,后面的文章会对其进行分析,敬请期待,哈哈,我们来看看高阶函数还有哪些用途

柯里化其他应用函数

柯里化也称为部分求值。 柯里化函数会接收一些参数,然后不会立即求值,而是继续返回一个新函数,传入的参数会保存在闭包中,直到真正求值为止。 value,一次性评估所有传入参数

还能简单点吗?在一个函数中填入几个参数,然后返回一个新函数,最后求值,就没有了,是不是很简单?

再简单,也不如几行代码演示那么清晰

  1. // 普通函数

  2. function add(x,y){

  3. return x + y;

  4. }


  5. add(3,4); // 7


  6. // 实现了柯里化的函数

  7. // 接收参数,返回新函数,把参数传给新函数使用,最后求值

  8. let add = function(x){

  9. return function(y){

  10. return x + y;

  11. }

  12. };


  13. add(3)(4); // 7

上面的代码很简单,仅供参考。我们来写一个通用的柯里化函数

  1. function curry(fn) {

  2. let slice = Array.prototype.slice, // 将slice缓存起来

  3. args = slice.call(arguments, 1); // 这里将arguments转成数组并保存


  4. return function() {

  5. // 将新旧的参数拼接起来

  6. let newArgs = args.concat(slice.call(arguments));

  7. return fn.apply(null, newArgs); // 返回执行的fn并传递最新的参数

  8. }

  9. }

实现了通用柯里化功能,太神奇了,大家都惊叹。

但这还不够,我们还可以使用ES6再次实现,请看下面的代码:

  1. // ES6版的柯里化函数

  2. function curry(fn) {

  3. const g = (...allArgs) => allArgs.length >= fn.length ?

  4. fn(...allArgs) :

  5. (...args) => g(...allArgs, ...args)


  6. return g;

  7. }


  8. // 测试用例

  9. const foo = curry((a, b, c, d) => {

  10. console.log(a, b, c, d);

  11. });

  12. foo(1)(2)(3)(4); // 1 2 3 4

  13. const f = foo(1)(2)(3);

  14. f(5); // 1 2 3 5

两种不同的实现思路是一样的,后面可以尝试分析

但是你有没有注意到,我们在ES5中使用的bind方法实际上使用了柯里化的思想,所以我们再看一下

  1. let obj = {

  2. songs: '以父之名'

  3. };


  4. function fn() {

  5. console.log(this.songs);

  6. }


  7. let songs = fn.bind(obj);

  8. songs(); // '以父之名'

为什么这么说呢?我这里看不到任何线索,别这么惨,我们来看看bind的实现原理

  1. Function.prototype.bind = function(context) {

  2. let self = this,

  3. slice = Array.prototype.slice,

  4. args = slice.call(arguments);


  5. return function() {

  6. return self.apply(context, args.slice(1));

  7. }

  8. };

是不是很熟悉,是不是,有一种老师是同一所学校的感觉?

反柯里化

什么? 反柯里化,刚才被柯里化跳舞了,现在又来了一个反柯里化,有没有搞错! 那么什么是反柯里化呢?简单来说就是函数的借用,全世界的人都用函数(方法)

jquery判断滚动条_如何判断鼠标是否滚动_jquery图片滚动

例如,一个对象可能不仅能够使用自己的方法,还可以借用原本不属于它的方法。 实现这个很简单,因为call和apply就可以完成这个任务

  1. (function() {

  2. // arguments就借用了数组的push方法

  3. let result = Array.prototype.slice.call(arguments);

  4. console.log(result); // [1, 2, 3, 'hi']

  5. })(1, 2, 3, 'hi');


  6. Math.max.apply(null, [1,5,10]); // 数组借用了Math.max方法

从上面的代码可以看出,大家都是相亲相爱的一家人。使用call和apply来改变this的方向,方法中使用的this不再局限于原来指定的对象,可以泛化以获得更广泛的适用性

反柯里化专题是我们亲爱的js之父发表的,我们从一个实际的例子来看看它的作用

  1. let slice = Array.prototype.slice.uncurrying();


  2. (function() {

  3. let result = slice(arguments); // 这里只需要调用slice函数即可

  4. console.log(result); // [1, 2, 3]

  5. })(1,2,3);

上面的代码通过反柯里化的方法,将Array.prototype.slice变成了通用的切片函数,这样就不会局限于只能对链表进行操作,从而使函数调用更加简洁明了

最后我们看一下它的实现方法,看代码,比较现实

  1. Function.prototype.uncurrying = function() {

  2. let self = this; // self 此时就是下面的Array.prototype.push方法

  3. return function() {

  4. let obj = Array.prototype.shift.call(arguments);

  5. /*

  6. obj其实是这种样子的

  7. obj = {

  8. 'length': 1,

  9. '0': 1

  10. }

  11. */

  12. return self.apply(obj, arguments); // 相当于Array.prototype.push(obj, 110)

  13. }

  14. };

  15. let slice = Array.prototype.push.uncurrying();


  16. let obj = {

  17. 'length': 1,

  18. '0': 1

  19. };

  20. push(obj, 110);

  21. console.log(obj); // { '0': 1, '1': 110, length: 2 }

其实实现反柯里化的方法不止一种,下面给大家分享一种,看代码即可

  1. Function.prototype.uncurrying = function() {

  2. let self = this;

  3. return function() {

  4. return Function.prototype.call.apply(self, arguments);

  5. }

  6. };

实现方法大致相同,大家也可以写下来试试,动动双手,动动筋骨

功能节流

接下来我们来说说函数节流。 我们都知道,在onresize、onscroll、mousemove、文件上传等场景中,会频繁触发函数,比较消耗性能,浏览器无法承受。

于是你开始研究一种中间方法,就是控制函数被触发的频率,也就是函数节流。简单说一下原理,在一定时间内使用setTimeout,函数只被触发一次,这大大增加了频率问题

函数节流的实现也有多种,这里我们实现一下大家常用的

  1. function throttle (fn, wait) {

  2. let _fn = fn, // 保存需要被延迟的函数引用

  3. timer,

  4. flags = true; // 是否首次调用


  5. return function() {

  6. let args = arguments,

  7. self = this;


  8. if (flags) { // 如果是第一次调用不用延迟,直接执行即可

  9. _fn.apply(self, args);

  10. flags = false;

  11. return flags;

  12. }

  13. // 如果定时器还在,说明上一次还没执行完,不往下执行

  14. if (timer) return false;


  15. timer = setTimeout(function() { // 延迟执行

  16. clearTimeout(timer); // 清空上次的定时器

  17. timer = null; // 销毁变量

  18. _fn.apply(self, args);

  19. }, wait);

  20. }

  21. }


  22. window.onscroll = throttle(function() {

  23. console.log('滚动');

  24. }, 500);

设置页面body的高度,等滚动条出现后试试。 与每次滚动都触发相比,大大增加了性能损失。 这就是函数节流的作用,具有事半功倍的效果,在开发中比较常用。

分时功能

我们知道有一个典故是这样说的:罗马不是三天建成的;罗马不是三天建成的。 更一般地说,胖纸不是三天建成的

节目中也体现了同样的情况。 如果我们一次性获取大量数据(比如10W数据),然后后端渲染的时候就会卡住,浏览器就是这么温柔的物种。

所以在处理这么多数据的时候,我们可以选择分批进行,而不是一次塞这么多,嘴就这么大

我们看一个简单的实现

  1. function timeChunk(data, fn, count = 1, wait) {

  2. let obj, timer;


  3. function start() {

  4. let len = Math.min(count, data.length);

  5. for (let i = 0; i < len; i++) {

  6. val = data.shift(); // 每次取出一个数据,传给fn当做值来用

  7. fn(val);

  8. }

  9. }


  10. return function() {

  11. timer = setInterval(function() {

  12. if (data.length === 0) { // 如果数据为空了,就清空定时器

  13. return clearInterval(timer);

  14. }

  15. start();

  16. }, wait); // 分批执行的时间间隔

  17. }

  18. }


  19. // 测试用例

  20. let arr = [];

  21. for (let i = 0; i < 100000; i++) { // 这里跑了10万数据

  22. arr.push(i);

  23. }

  24. let render = timeChunk(arr, function(n) { // n为data.shift()取到的数据

  25. let div = document.createElement('div');

  26. div.innerHTML = n;

  27. document.body.appendChild(div);

  28. }, 8, 20);


  29. render();

延迟加载

兼容现代浏览器和IE浏览器,加麻烦的方式是一个很好的栗子

  1. // 常规的是这样写的

  2. let addEvent = function(ele, type, fn) {

  3. if (window.addEventListener) {

  4. return ele.addEventListener(type, fn, false);

  5. } else if (window.attachEvent) {

  6. return ele.attachEvent('on' + type, function() {

  7. fn.call(ele);

  8. });

  9. }

  10. };

这种实现有一个缺点,就是调用addEvent时会执行分支条件。 其实只需要判断一次jquery判断滚动条,每次都要执行一波。

接下来我们再优化一下addEvent,避免它的缺点,这就是我们要实现的延迟加载功能

jquery图片滚动_jquery判断滚动条_如何判断鼠标是否滚动

  1. let addEvent = function(ele, type, fn) {

  2. if (window.addEventListener) {

  3. addEvent = function(ele, type, fn) {

  4. ele.addEventListener(type, fn, false);

  5. }

  6. } else if (window.attachEvent) {

  7. addEvent = function(ele, type, fn) {

  8. ele.attachEvent('on' + type, function() {

  9. fn.call(ele)

  10. });

  11. }

  12. }


  13. addEvent(ele, type, fn);

  14. };

上面的addEvent函数仍然是一个普通函数,它仍然具有分支判断。但是,当第一次进入分支条件时,addEvent函数内部会进行重绘

下次进入addEvent函数时,函数中就不会再进行条件判断了

结尾

演出时间不早,时间刚刚好,又到了告别的时候了,让我做个总结吧

高阶函数

反柯里化

功能节流

分时功能

延迟加载

卧槽,我罗列了这么多,大家辛苦看完了,早睡早起,好好休息!

读三件事

如果你觉得这篇内容对你很有启发,我想邀请你帮我三个小忙:

点击“在看”jquery判断滚动条,让更多人看到此内容(不管你喜不喜欢,都是流氓行为-_-)