Giter VIP home page Giter VIP logo

Comments (125)

jawil avatar jawil commented on May 2, 2024 310

这是隐式转换的玄学。

我们先看看ECMAScript规范对一元运算符的规范:

一元+ 运算符
 一元+运算符将其操作数转换为Number类型并反转其正负。注意负的+0产生-0,负的-0产生+0。

 产生式 UnaryExpression : - UnaryExpression 按照下面的过程执行 :

  1. 令 expr 为解释执行 UnaryExpression 的结果 .
  2. 令 oldValue 为 ToNumber(GetValue(expr)).
  3. 如果 oldValue is NaN ,return NaN.
  4. 返回 oldValue 取负(即,算出一个数字相同但是符号相反的值)的结果。

+new Date()相当于 ToNumber(new Date())

我们再来看看ECMAScript规范对ToNumber的定义:

我们知道new Date()是个对象,满足上面的ToPrimitive(),所以进而成了ToPrimitive(new Date())

接着我们再来看看ECMAScript规范对ToPrimitive的定义,一层一层来,抽丝剥茧。

这个ToPrimitive可能不太好懂,我给你解释一下吧:

ToPrimitive(obj,preferredType)

JS引擎内部转换为原始值ToPrimitive(obj,preferredType)函数接受两个参数,第一个obj为被转换的对象,第二个
preferredType为希望转换成的类型(默认为空,接受的值为Number或String)

在执行ToPrimitive(obj,preferredType)时如果第二个参数为空并且obj为Date的事例时,此时preferredType会
被设置为String,其他情况下preferredType都会被设置为Number如果preferredType为Number,ToPrimitive执
行过程如
下:
1. 如果obj为原始值,直接返回;
2. 否则调用 obj.valueOf(),如果执行结果是原始值,返回之;
3. 否则调用 obj.toString(),如果执行结果是原始值,返回之;
4. 否则抛异常。

如果preferredType为String,将上面的第2步和第3步调换,即:
1. 如果obj为原始值,直接返回;
2. 否则调用 obj.toString(),如果执行结果是原始值,返回之;
3. 否则调用 obj.valueOf(),如果执行结果是原始值,返回之;
4. 否则抛异常。

首先我们要明白 obj.valueOf()obj.toString() 还有原始值分别是什么意思,这是弄懂上面描述的前提之一:

toString用来返回对象的字符串表示。

var obj = {};
console.log(obj.toString());//[object Object]

var arr2 = [];
console.log(arr2.toString());//""空字符串
  
var date = new Date();
console.log(date.toString());//Sun Feb 28 2016 13:40:36 GMT+0800 (**标准时间)

valueOf方法返回对象的原始值,可能是字符串、数值或bool值等,看具体的对象。

var obj = {
  name: "obj"
};
console.log(obj.valueOf());//Object {name: "obj"}

var arr1 = [1];
console.log(arr1.valueOf());//[1]



var date = new Date();
console.log(date.valueOf());//1456638436303
如代码所示,三个不同的对象实例调用valueOf返回不同的数据

原始值指的是['Null','Undefined','String','Boolean','Number','Symbol']6种基本数据类型之一

最后分解一下其中的过程:
+new Date():

  1. 运算符new的优先级高于一元运算符+,所以过程可以分解为:
    var time=new Date();
    +time

2.根据上面提到的规则相当于:ToNumber(time)

3.time是个日期对象,根据ToNumber的转换规则,所以相当于:ToNumber(ToPrimitive(time))

4.根据ToPrimitive的转换规则:ToNumber(time.valueOf()),time.valueOf()就是 原始值 得到的是个时间戳,假设time.valueOf()=1503479124652

5.所以ToNumber(1503479124652)返回值是1503479124652这个数字。

6.分析完毕,从原理得出结果,而不是从浏览器输出的结果来解释结果。用结论解释结论,会忽略很多细节,装个逼,逃,233333

@lindazhang102 @WittyBob

from blog.

lindazhang102 avatar lindazhang102 commented on May 2, 2024 49

var now = +new Date(); 请问这里为什么要有个加号?

from blog.

proc07 avatar proc07 commented on May 2, 2024 37
// 第二版
function throttle(func, wait) {
    var timeout;
    var previous = 0;

    return function() {
        context = this;
        args = arguments;
        if (!timeout) {
            timeout = setTimeout(function(){
                timeout = null;
                // 删除  func.apply(context, args)
            }, wait)
           // 添加 
            func.apply(context, args)
        }

    }
}

这样就可以立刻执行了。

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024 30

@xue1234jun 防抖是虽然事件持续触发,但只有等事件停止触发后 n 秒才执行函数,节流是持续触发的时候,每 n 秒执行一次函数

from blog.

JJL-SH avatar JJL-SH commented on May 2, 2024 20

@lindazhang102 这个是转时间戳的方法

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024 20

@lindazhang102 正如 @jawil 所说,是利用隐式类型转换将时间对象转换为时间戳,类似的还有:

+'1' // 1 (转数字)
1 + '' // '1' (转字符串)
!!1 // true (转布尔值)

from blog.

JJL-SH avatar JJL-SH commented on May 2, 2024 18

第三版
if (remaining <= 0 || remaining > wait) {
这里的remaining好像不会出现大于wait的情况吧
毕竟是基于wait去减的

from blog.

zhangruinian avatar zhangruinian commented on May 2, 2024 9

那为什么第二种置为空进行垃圾回收,时间戳的第一种就没有置为空呀,真心请教,疑惑

from blog.

xujinfengM avatar xujinfengM commented on May 2, 2024 6

唯一的疑问是,这个判断什么时候会进去?

if (timeout) {
  clearTimeout(timeout);
  timeout = null;
 }

@DFLovingWM
因为定时器并不是准确的时间,很可能你设置了2秒
但是他需要2.2秒才触发,这时候就会进入这个条件

from blog.

zjp6049 avatar zjp6049 commented on May 2, 2024 5

@dowinweb 肯定有意义啊。首先如果不在回调里设置(针对leading:false的情况)
previous = options.leading === false ? 0 : new Date().getTime();而是
直接previous = +new Date()
那么第一次以外的if (!previous && options.leading === false) previous = now;就不会执行,
这里的now是下次执行的new Date(),而回调里的是timeout执行时的new Date()。
也就是说按你的说话来执行的话,首先移动鼠标,wait秒后变为1没毛病,然后你别动鼠标,等待大于wait秒后再移动鼠标,这时候就会马上变为2而达不到禁用第一次执行的效果。
因为这时候的var remaining = wait - (now - previous);
now是现在执行的时间,previos还是你很久前timeout执行的时间,所以remaining应该是负数走了
触发时间到了的函数(立即执行,1变为2)

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024 4

@ishowman 可以,但是我个人认为 undefined 是一个值,而 null 表示无,所以赋值为 undefined 其实是将值改成一个非常小的占用内存的值,效果上跟赋值为 null 还是差了一点……

from blog.

jsyt avatar jsyt commented on May 2, 2024 3

双剑合璧第三版首次执行的时候有点问题吧?
首次执行 previous = 0; now = 一个很大的正数; remaining = wait - (now - previous) = wait - now = 一个很大的负数;首次执行timeout = undefined; 会进入 else if (!timeout) {timeout = setTimeout(later, remaining); }分支;第一次设置定时器,设置的延迟时间是负数啊???我这么理解有问题吗?

// 第三版
function throttle(func, wait) {
    var timeout, context, args, result;
    var previous = 0;

    var later = function() {
        previous = +new Date();
        timeout = null;
        func.apply(context, args)
    };

    var throttled = function() {
        var now = +new Date();
        //下次触发 func 剩余的时间
        var remaining = wait - (now - previous); //第一次执行 remaining为负数
        context = this;
        args = arguments;
         // 如果没有剩余的时间了或者你改了系统时间
        if (remaining <= 0 || remaining > wait) {
            if (timeout) {
                clearTimeout(timeout);
                timeout = null;
            }
            previous = now;
            func.apply(context, args);
        } else if (!timeout) { //第一次执行 !timeout = true; 会进入这个分支
            timeout = setTimeout(later, remaining); //第一次执行 setTimeout 延迟执行的时间是负数啊?????
        }
    };
    return throttled;
}

from blog.

bosens-China avatar bosens-China commented on May 2, 2024 3

有一个地方不是很懂,为什么是args = arguments,这样的话,args不也是类数组吗?为什么不用...args,有些困惑,希望大佬们能够解答

因为是es6的语法,需要经过转义,你可以看作者写的代码全部用的var声明,默认是es5的环境,他这样做就是想让代码全部兼容,除了这个你想怎么写都OK

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024 2

@MuYunyun 思路的话应该就是先看懂大致的实现,然后自己尝试着实现一个非常简单的,然后再向着最后的代码,一个功能点一个功能点的完善吧~ 技巧的话,有一个就是到相应仓库搜索与这段代码相关的 PR 或 isuue,也会有很多收获~

from blog.

lizhongzhen11 avatar lizhongzhen11 commented on May 2, 2024 2

防抖和节流第一次理解起来容易混淆。
我觉得防抖主要是阻止频繁触发一个事件的,虽然会有一个时间间隔,但是当在时间间隔内多次触发,那么会刷新剩余时间,比如时间间隔2s,过了1s,此时剩余时间1s,但是立马又操作了,那么剩余时间重新变成2s了;
而节流是不管操作多频繁,我始终固定时间触发一次。

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024 1

@zhangruinian 正是如此,使用定时器时置为空的主要目的并不是垃圾回收,主要是为了方便下次执行定时器

from blog.

JzHuangJian avatar JzHuangJian commented on May 2, 2024 1

节流,在规定时间内,保证执行一次该函数;防抖,当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始 延时。

from blog.

huangd-d avatar huangd-d commented on May 2, 2024 1

var now = +new Date(); 请问这里为什么要有个加号?

转为时间戳。 毫秒数

from blog.

lhmmhs avatar lhmmhs commented on May 2, 2024 1

请问
function throttle(func, wait) {
var context, args;
var previous = 0;

return function() {
    var now = +new Date();
    context = this;
    args = arguments;
    if (now - previous > wait) {
        func.apply(context, args);
        previous = now;
    }
}

}
可以不加return function直接执行吗,求大佬解释一下

不行,这个是闭包,主要保存这个previous 变量,移动鼠标每次执行是返回的函数,now会不断增加,但是previous 是不会改变的,而now -previous 会不断增大,当now -previous 增大到大于wait的时候,就进入判断,执行传入的回调,重置previous 。这个闭包最重要的用途就是,保存previous 在内存中。

from blog.

Lry0504 avatar Lry0504 commented on May 2, 2024 1

使用定时器实现throttle节流函数时,不需要再声明previous时间戳啦~

from blog.

yonghui666 avatar yonghui666 commented on May 2, 2024 1

这是隐式转换的玄学。

我们先看看ECMAScript规范对一元运算符的规范:

一元+ 运算符
 一元+运算符将其操作数转换为Number类型并反转其正负。注意负的+0产生-0,负的-0产生+0。

 产生式 UnaryExpression : - UnaryExpression 按照下面的过程执行 :

  1. 令 expr 为解释执行 UnaryExpression 的结果 .
  2. 令 oldValue 为 ToNumber(GetValue(expr)).
  3. 如果 oldValue is NaN ,return NaN.
  4. 返回 oldValue 取负(即,算出一个数字相同但是符号相反的值)的结果。

+new Date()相当于 ToNumber(new Date())

我们再来看看ECMAScript规范对ToNumber的定义:

我们知道new Date()是个对象,满足上面的ToPrimitive(),所以进而成了ToPrimitive(new Date())

接着我们再来看看ECMAScript规范对ToPrimitive的定义,一层一层来,抽丝剥茧。

这个ToPrimitive可能不太好懂,我给你解释一下吧:

ToPrimitive(obj,preferredType)

JS引擎内部转换为原始值ToPrimitive(obj,preferredType)函数接受两个参数,第一个obj为被转换的对象,第二个
preferredType为希望转换成的类型(默认为空,接受的值为Number或String)

在执行ToPrimitive(obj,preferredType)时如果第二个参数为空并且obj为Date的事例时,此时preferredType会
被设置为String,其他情况下preferredType都会被设置为Number如果preferredType为Number,ToPrimitive执
行过程如
下:
1. 如果obj为原始值,直接返回;
2. 否则调用 obj.valueOf(),如果执行结果是原始值,返回之;
3. 否则调用 obj.toString(),如果执行结果是原始值,返回之;
4. 否则抛异常。

如果preferredType为String,将上面的第2步和第3步调换,即:
1. 如果obj为原始值,直接返回;
2. 否则调用 obj.toString(),如果执行结果是原始值,返回之;
3. 否则调用 obj.valueOf(),如果执行结果是原始值,返回之;
4. 否则抛异常。

首先我们要明白 obj.valueOf()obj.toString() 还有原始值分别是什么意思,这是弄懂上面描述的前提之一:

toString用来返回对象的字符串表示。

var obj = {};
console.log(obj.toString());//[object Object]

var arr2 = [];
console.log(arr2.toString());//""空字符串
  
var date = new Date();
console.log(date.toString());//Sun Feb 28 2016 13:40:36 GMT+0800 (**标准时间)

valueOf方法返回对象的原始值,可能是字符串、数值或bool值等,看具体的对象。

var obj = {
  name: "obj"
};
console.log(obj.valueOf());//Object {name: "obj"}

var arr1 = [1];
console.log(arr1.valueOf());//[1]



var date = new Date();
console.log(date.valueOf());//1456638436303
如代码所示,三个不同的对象实例调用valueOf返回不同的数据

原始值指的是['Null','Undefined','String','Boolean','Number','Symbol']6种基本数据类型之一

最后分解一下其中的过程:
+new Date():

  1. 运算符new的优先级高于一元运算符+,所以过程可以分解为:
    var time=new Date();
    +time

2.根据上面提到的规则相当于:ToNumber(time)

3.time是个日期对象,根据ToNumber的转换规则,所以相当于:ToNumber(ToPrimitive(time))

4.根据ToPrimitive的转换规则:ToNumber(time.valueOf()),time.valueOf()就是 原始值 得到的是个时间戳,假设time.valueOf()=1503479124652

5.所以ToNumber(1503479124652)返回值是1503479124652这个数字。

6.分析完毕,从原理得出结果,而不是从浏览器输出的结果来解释结果。用结论解释结论,会忽略很多细节,装个逼,逃,233333

@lindazhang102 @WittyBob

全是*操作,明明可以写语义化的Number();

from blog.

thomas-void0 avatar thomas-void0 commented on May 2, 2024 1

image
我有一个问题,希望能得到各位大佬的解答。在第三版本中,当我的触发时间是在wait规定的周期以内的时候。就会进入到later中进行执行,但是later中又把timeout置为了null。那我下一次触发事件的时候,实际并不会进入到if(timeout){clearTimeout(timeout)}中去。那上一个定时器似乎并未得到回收?

from blog.

bosens-China avatar bosens-China commented on May 2, 2024 1
 var later = function() {
        previous = +new Date();
        timeout = null;
        func.apply(context, args)
  };

请问这里为什么要将previous = +new Date();呢?

给他转化为时间戳,你可以自己实现以下,这个是隐士转换

from blog.

liuxinqiong avatar liuxinqiong commented on May 2, 2024

沙发!!!

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@WittyBob 如果你修改了系统时间,就会产生 remaining > wait 的情况……

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@Awzp 置为空是为了 js 的垃圾回收,不过 later 函数中的 timeout 判断其实没有必要,估计是 underscore 在多次修改后忽略了这个问题~

from blog.

ClausClaus avatar ClausClaus commented on May 2, 2024

厉害了我的哥。。。

from blog.

dbfterrific avatar dbfterrific commented on May 2, 2024

you are really something my brother!

from blog.

ishowman avatar ishowman commented on May 2, 2024

timeout = null;是为了清楚闭包产生的变量常驻内存问题是吧?除了将变量重新赋值为null可以清楚使内存回收机制清楚变量占用的变量,赋值为undefined能清楚内存占用吗?

from blog.

zhangruinian avatar zhangruinian commented on May 2, 2024

所以timeout = null主要是为了使其为空然后下次好接着执行?

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@ClausClaus @dbfterrific 这应该是一个意思吧~ 😂

default

from blog.

xue1234jun avatar xue1234jun commented on May 2, 2024

节流和去抖如何区分

from blog.

xue1234jun avatar xue1234jun commented on May 2, 2024

@mqyqingfeng 非常感谢

from blog.

savoygu avatar savoygu commented on May 2, 2024

我们要注意 underscore 的实现中有这样一个问题:
那就是 leading:false 和 trailing: false 不能同时设置。
如果同时设置的话,比如当你将鼠标移出的时候,因为 trailing 设置为 false,停止触发的时候不会设置定时器,所以只要再过了设置的时间,再移入的话,就会立刻执行,就违反了 leading: false,bug 就出来了

其一: underscore 怎么没有考虑 leading = falsetrailing = false 同时为 false 的情况,这应该不难实现吧,关键就是 previous 置为初始值 0。

其二:

    var throttled = function() {
        var now = new Date().getTime();
        if (!previous && options.leading === false) previous = now;
        var remaining = wait - (now - previous);
        context = this;
        args = arguments;
        if (remaining <= 0 || remaining > wait) {
            if (timeout) {
                clearTimeout(timeout);
                timeout = null;
            }
            previous = now;
            func.apply(context, args);
            if (!timeout) context = args = null;
        } else if (!timeout && options.trailing !== false) {
            timeout = setTimeout(later, remaining);
        }
    };

remaining > wait 情况的发生是 now - previous 为负值的时候,也就是获取的当前时间小于先前获取的当前时间,在事件触发的过程中更改当前时间吗(这种概率是有多小)

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@savoygu 关于第一个问题,其实有人提过这个问题,这是当时的一个核心贡献者的回答:

default

这个开发者认为两者必须要有一个为 true。

除了这个 issue 外,如果要知道 underscore 是否考虑了 leading = false 和 trailing = false 同时为 false 的情况,其实看测试用例就可以了,这是地址 https://github.com/jashkenas/underscore/blob/08361d41590ff35be44ec6b757361ac37f6fa7c7/test/functions.js

搜索 leading ,其实没有两个都为 false 的测试用例。

关于是否容易实现这个效果,如果我们在已有的代码上进行修改,比如 previous 设置为 0,那什么时候设置这个 previous 呢?欢迎补充 demo 哈~

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@savoygu 关于第二个问题,让我们具体看看这个 PR

default

第一个人提出了这个需求,第二个人认为这个需求太小众,但是 underscore 作者接了这个需求~😂

from blog.

savoygu avatar savoygu commented on May 2, 2024

@mqyqingfeng 感谢

from blog.

zjp6049 avatar zjp6049 commented on May 2, 2024

要是有代码的思路就更好了~~~~~~~~~~~~~~~~~

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@zjp6049 嗯嗯,这个日后补充~

from blog.

dowinweb avatar dowinweb commented on May 2, 2024

感觉later函数里的这里判断没啥意义啊,求指教
previous = options.leading === false ? 0 : new Date().getTime();
直接previous = +new Date()不行吗

from blog.

dowinweb avatar dowinweb commented on May 2, 2024

@zjp6049 嗯嗯,明白啦,谢谢

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@jxZhangLi 嗯嗯,感谢补充,这样确实可以做到立即执行~

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@zjp6049 非常感谢回答哈~ ( ̄▽ ̄)~*

from blog.

meiminjun avatar meiminjun commented on May 2, 2024

@jawil 你那个ECMAScript规范能给个链接吗?

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@meiminjun jawil 现在很忙😂~ http://es5.github.io/#x9.3

from blog.

jawil avatar jawil commented on May 2, 2024

@meiminjun 中文版 建议看英文版,中文版翻译有些地方有坑。

@mqyqingfeng 不忘初心,方得始终,再忙也要补充一下。

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@jawil 哈哈,那还不赶紧把你博客里的问题回答一下?有些问题我不会,就不能帮你解答了😂

from blog.

jawil avatar jawil commented on May 2, 2024

那些问题可以写一篇文章了,我都记着呢,还有那个谷歌插件也要来一波更新,时间真的不够用。@mqyqingfeng

from blog.

MuYunyun avatar MuYunyun commented on May 2, 2024

已粉,博主的文章的思路由浅入深,质量和产量都让我敬佩,敢问博主是如何将 underscore 的源码抽丝剥茧成第一版、第二版..第N版的,这其中的思路和技巧能和我们分享下吗?

另外弄懂第三版,可能需要这个知识点:

console.log(1)
setTimeout(() => {console.log(2)}, 0)
console.log(3)
// 输出顺序为 => 1, 3 ,2

from blog.

MuYunyun avatar MuYunyun commented on May 2, 2024

非常感谢,学习了:heart:

from blog.

iiicon avatar iiicon commented on May 2, 2024
      `if (timeout) {
            clearTimeout(timeout);
            timeout = null;
        }
        previous = now;
        func.apply(context, args);
        if (!timeout) context = args = null;`

老哥,不是很理解这段,只有 wait 设置为 0 的时候才能触发这种情况,那是不是 timeout 为空啊?这里设置 context = args = null 倒是可以理解,没想明白哪种情况下 remaining 小于等于 0 的时候 timeout 还没清掉。

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@iiicon 有一种情况是更改了系统时间……

from blog.

xcgs123 avatar xcgs123 commented on May 2, 2024

第一版看上去应该也是等待wait时间后执行啊,为什么会立刻执行呢,求解

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@zuoyan188

// 第一版的代码
function throttle(func, wait) {
    var context, args;
    var previous = 0;

    return function() {
        var now = +new Date();
        context = this;
        args = arguments;
        if (now - previous > wait) {
            func.apply(context, args);
            previous = now;
        }
    }
}

第一次执行的时候,previous = 0, now - previous 肯定大于 wait 呀,所以立刻执行

from blog.

Fromzero828 avatar Fromzero828 commented on May 2, 2024

请教下大神,我把第四版改成这样是否可行呢,里面有没有存在错误

function debounce(func, wait) {
          var timeout;       
          var flag;
          return function () {
              var context = this;
              var args = arguments;
              clearTimeout(timeout)  
              if(!flag) {
                  func.apply(context, args)
                  flag = true;
              } else {
                  timeout = setTimeout(function() {
                      func.apply(context, args)
                  }, wait);
              }         
              
          }
      }

from blog.

Tan90Qian avatar Tan90Qian commented on May 2, 2024

@Fromzero828 你的这段代码里 flag这个变量只有从undefined变成true的过程,而没有复位的部分。那么你在首次执行后,flag永远是true。这使得你停下一段大于wait的时间后,再次触发时并不会出现“立即执行”的效果。
最基本的解决方法的话,可以在else分支 setTimeout要执行的函数体结尾加上一句flag=false,作用就跟博主代码中对于previoustimeout进行复位的代码一样。

from blog.

coconilu avatar coconilu commented on May 2, 2024

谢谢,对我很有帮助!

from blog.

jsyt avatar jsyt commented on May 2, 2024
/**
* @ fn 回调函数 
* @ wait 间隔时间 
*/
function throttle(fn, wait){
    let pre = 0,
        timer = null;
    return function(){
        let context = this,
            arg = arguments,
            now = +new Data(),
            remaining = wait - (now - pre);
        if(remaining < 0 || remaining >= wait){
            if(!timer){
                fn.apply(this, arguments);
                pre = now;
            }
            timer = setTimeout(function(){
                pre = now;
                fn.apply(context, arg);
            }, wait);
        }else{
            clearTimeout(timer);
            timer = setTimeout(function(){
                pre = now;
                fn.apply(context, arg);
            }, remaining);
        }
    }
}

这是我写的一个双剑合璧的版本,你看看有问题不?

from blog.

jinxin0112 avatar jinxin0112 commented on May 2, 2024

节流 不需要做返回值吗

from blog.

lulin1 avatar lulin1 commented on May 2, 2024

大神请问这句代码的作用是什么:
if (!timeout) context = args = null;
手动清空为了节省内存么?

from blog.

inter727 avatar inter727 commented on May 2, 2024

@mqyqingfeng
第一版代码中,每次触发事件都要执行 previous = 0
那不是每次都被清零了么?

from blog.

lulin1 avatar lulin1 commented on May 2, 2024

@inter727 container.onmousemove执行的是return后的function,所以每次执行完后 previous = now;

from blog.

Lyd9607 avatar Lyd9607 commented on May 2, 2024

我感觉previous初始化的时候为+new date()会不会更好点

from blog.

inottn avatar inottn commented on May 2, 2024

@jsyt

双剑合璧第三版首次执行的时候有点问题吧?
首次执行 previous = 0; now = 一个很大的正数; remaining = wait - (now - previous) = wait - now = 一个很大的负数;首次执行timeout = undefined; 会进入 else if (!timeout) {timeout = setTimeout(later, remaining); }分支;第一次设置定时器,设置的延迟时间是负数啊???我这么理解有问题吗?

remaining < 0 所以首次执行会进入这个 if (remaining < 0 || remaining >= wait) 分支。

from blog.

inottn avatar inottn commented on May 2, 2024

@jsyt

这段代码存在如下问题
首次执行时会走 if (remaining < 0 || remaining >= wait) 分支,如下图:

if (remaining < 0 || remaining >= wait) {
  if (!timer) {
    fn.apply(this, arguments);
    pre = now;
  }
  timer = setTimeout(function () {
    pre = now;
    fn.apply(context, arg);
  }, wait);
}

此时 timer === null ,所以会执行

if (!timer) {
  fn.apply(this, arguments);
  pre = now;
}

接着会在 wait 后触发下面的定时器

timer = setTimeout(function(){
  pre = now;
  fn.apply(context, arg);
}, wait);

简单来说,当你触发一次事件时(首次),会立即执行一次,并在 wait再执行一次

from blog.

inottn avatar inottn commented on May 2, 2024

@Lyd9607

我感觉previous初始化的时候为+new date()会不会更好点

不知道你指的是哪个版本,就假定是第三版好了。

previous初始化为0是有意义的,能确保首次执行时进入 if (remaining <= 0 || remaining > wait) 分支并触发 func.apply(context, args)

from blog.

hncsjxxxx avatar hncsjxxxx commented on May 2, 2024

请问
function throttle(func, wait) {
var context, args;
var previous = 0;

return function() {
    var now = +new Date();
    context = this;
    args = arguments;
    if (now - previous > wait) {
        func.apply(context, args);
        previous = now;
    }
}

}
可以不加return function直接执行吗,求大佬解释一下

from blog.

5201314999 avatar 5201314999 commented on May 2, 2024

感觉这个比jquery 的实现复杂多了。

from blog.

FinnWu avatar FinnWu commented on May 2, 2024

@jawil 你好,new Date的实例preferType被设置为String,不是应该先执行toString()方法吗,然后返回来一段字符串,是原始值,为什么是先执行valueOf,不来理解,可以解答下吗?

from blog.

5201314999 avatar 5201314999 commented on May 2, 2024

function throttle(fn, delay) {
let lastDate = 0, timeout;
let context = this;
let args = arguments;

        // console.log(context);
        return function () {
            let nowDate = + new Date();
            if (nowDate - lastDate >= delay) {
                lastDate = nowDate;
                console.log('1');
                fn.apply(context, arguments);
            }
            clearTimeout(timeout);
            timeout = setTimeout(() => {
                console.log('2');
                fn.apply(context, arguments);
            }, delay);
        }
    }

这样写节流有问题吗。

from blog.

BaoTao1997 avatar BaoTao1997 commented on May 2, 2024

双剑合璧第三版首次执行的时候有点问题吧?
首次执行 previous = 0; now = 一个很大的正数; remaining = wait - (now - previous) = wait - now = 一个很大的负数;首次执行timeout = undefined; 会进入 else if (!timeout) {timeout = setTimeout(later, remaining); }分支;第一次设置定时器,设置的延迟时间是负数啊???我这么理解有问题吗?

// 第三版
function throttle(func, wait) {
    var timeout, context, args, result;
    var previous = 0;

    var later = function() {
        previous = +new Date();
        timeout = null;
        func.apply(context, args)
    };

    var throttled = function() {
        var now = +new Date();
        //下次触发 func 剩余的时间
        var remaining = wait - (now - previous); //第一次执行 remaining为负数
        context = this;
        args = arguments;
         // 如果没有剩余的时间了或者你改了系统时间
        if (remaining <= 0 || remaining > wait) {
            if (timeout) {
                clearTimeout(timeout);
                timeout = null;
            }
            previous = now;
            func.apply(context, args);
        } else if (!timeout) { //第一次执行 !timeout = true; 会进入这个分支
            timeout = setTimeout(later, remaining); //第一次执行 setTimeout 延迟执行的时间是负数啊?????
        }
    };
    return throttled;
}

不会的,进入if分支就不会再进入else if了

from blog.

kashtian avatar kashtian commented on May 2, 2024

@jawil 你好,new Date的实例preferType被设置为String,不是应该先执行toString()方法吗,然后返回来一段字符串,是原始值,为什么是先执行valueOf,不来理解,可以解答下吗?

因为toNumber那里写了,暗示数值类型
附上截图:
image

from blog.

lisen6 avatar lisen6 commented on May 2, 2024

Wow. You are a genius!!

from blog.

world8023 avatar world8023 commented on May 2, 2024

这个是高级程序设计3,第615页的函数节流实现,我总是感觉这个更像是防抖,有没有大佬帮忙看一下,谢谢

 ` function throttle(method, context) {
        clearTimeout(method.tId);
       method.tId = setTimeout(function () {
            method.call(context);
        }, 100);
    }`

from blog.

Hellowor1d avatar Hellowor1d commented on May 2, 2024

这个是高级程序设计3,第615页的函数节流实现,我总是感觉这个更像是防抖,有没有大佬帮忙看一下,谢谢

 ` function throttle(method, context) {
        clearTimeout(method.tId);
       method.tId = setTimeout(function () {
            method.call(context);
        }, 100);
    }`

结论:这个确实是我们通常意义上讲的防抖动实现 debounce

from blog.

WeiShengv99 avatar WeiShengv99 commented on May 2, 2024

受益匪浅 感谢🙏

from blog.

shaw1121 avatar shaw1121 commented on May 2, 2024

great

from blog.

goSunadeod avatar goSunadeod commented on May 2, 2024

thanks

from blog.

yzfdjzwl avatar yzfdjzwl commented on May 2, 2024

思路很赞👍

from blog.

dendoink avatar dendoink commented on May 2, 2024
// 第一版
function throttle(func, wait) {
    var context, args;
    var previous = 0;

    return function() {
        var now = +new Date();
        //context =this; 这一行可以去掉,因为我们使用this 的地方并没有用到 settimeOut 所以 this 并未改变指向,可以直接用
        args = arguments;
        if (now - previous > wait) {
            func.apply(this, args);
            previous = now;
        }
    }
}

from blog.

dendoink avatar dendoink commented on May 2, 2024
// 第二版
function throttle(func, wait) {
    var timeout;
    //var previous = 0; 这里也多定义了一个无意义的变量... 根本没用时间差来判断

    return function() {
        context = this;
        args = arguments;
        if (!timeout) {
            timeout = setTimeout(function(){
                timeout = null;
                func.apply(context, args)
            }, wait)
        }

    }
}

from blog.

haaling avatar haaling commented on May 2, 2024

@jawil 你好,new Date的实例preferType被设置为String,不是应该先执行toString()方法吗,然后返回来一段字符串,是原始值,为什么是先执行valueOf,不来理解,可以解答下吗?

因为toNumber那里写了,暗示数值类型
附上截图:
image

image

如图 应该是toString() 吧?

from blog.

Jouryjc avatar Jouryjc commented on May 2, 2024

result 声明了没赋值,没返回

from blog.

gongph avatar gongph commented on May 2, 2024

这是隐式转换的玄学。

我们先看看ECMAScript规范对一元运算符的规范:

一元+ 运算符
 一元+运算符将其操作数转换为Number类型并反转其正负。注意负的+0产生-0,负的-0产生+0。

 产生式 UnaryExpression : - UnaryExpression 按照下面的过程执行 :

  1. 令 expr 为解释执行 UnaryExpression 的结果 .
  2. 令 oldValue 为 ToNumber(GetValue(expr)).
  3. 如果 oldValue is NaN ,return NaN.
  4. 返回 oldValue 取负(即,算出一个数字相同但是符号相反的值)的结果。

+new Date()相当于 ToNumber(new Date())

我们再来看看ECMAScript规范对ToNumber的定义:

我们知道new Date()是个对象,满足上面的ToPrimitive(),所以进而成了ToPrimitive(new Date())

接着我们再来看看ECMAScript规范对ToPrimitive的定义,一层一层来,抽丝剥茧。

这个ToPrimitive可能不太好懂,我给你解释一下吧:

ToPrimitive(obj,preferredType)

JS引擎内部转换为原始值ToPrimitive(obj,preferredType)函数接受两个参数,第一个obj为被转换的对象,第二个
preferredType为希望转换成的类型(默认为空,接受的值为Number或String)

在执行ToPrimitive(obj,preferredType)时如果第二个参数为空并且obj为Date的事例时,此时preferredType会
被设置为String,其他情况下preferredType都会被设置为Number如果preferredType为Number,ToPrimitive执
行过程如
下:
1. 如果obj为原始值,直接返回;
2. 否则调用 obj.valueOf(),如果执行结果是原始值,返回之;
3. 否则调用 obj.toString(),如果执行结果是原始值,返回之;
4. 否则抛异常。

如果preferredType为String,将上面的第2步和第3步调换,即:
1. 如果obj为原始值,直接返回;
2. 否则调用 obj.toString(),如果执行结果是原始值,返回之;
3. 否则调用 obj.valueOf(),如果执行结果是原始值,返回之;
4. 否则抛异常。

首先我们要明白 obj.valueOf()obj.toString() 还有原始值分别是什么意思,这是弄懂上面描述的前提之一:

toString用来返回对象的字符串表示。

var obj = {};
console.log(obj.toString());//[object Object]

var arr2 = [];
console.log(arr2.toString());//""空字符串
  
var date = new Date();
console.log(date.toString());//Sun Feb 28 2016 13:40:36 GMT+0800 (**标准时间)

valueOf方法返回对象的原始值,可能是字符串、数值或bool值等,看具体的对象。

var obj = {
  name: "obj"
};
console.log(obj.valueOf());//Object {name: "obj"}

var arr1 = [1];
console.log(arr1.valueOf());//[1]



var date = new Date();
console.log(date.valueOf());//1456638436303
如代码所示,三个不同的对象实例调用valueOf返回不同的数据

原始值指的是['Null','Undefined','String','Boolean','Number','Symbol']6种基本数据类型之一

最后分解一下其中的过程:
+new Date():

  1. 运算符new的优先级高于一元运算符+,所以过程可以分解为:
    var time=new Date();
    +time

2.根据上面提到的规则相当于:ToNumber(time)

3.time是个日期对象,根据ToNumber的转换规则,所以相当于:ToNumber(ToPrimitive(time))

4.根据ToPrimitive的转换规则:ToNumber(time.valueOf()),time.valueOf()就是 原始值 得到的是个时间戳,假设time.valueOf()=1503479124652

5.所以ToNumber(1503479124652)返回值是1503479124652这个数字。

6.分析完毕,从原理得出结果,而不是从浏览器输出的结果来解释结果。用结论解释结论,会忽略很多细节,装个逼,逃,233333

@lindazhang102 @WittyBob

Real B

from blog.

kakachake avatar kakachake commented on May 2, 2024

!previous && options.leading === false
这里!previous有什么用呢

from blog.

jiang2016tao avatar jiang2016tao commented on May 2, 2024

第三版
if (remaining <= 0 || remaining > wait) {
这里的remaining好像不会出现大于wait的情况吧
毕竟是基于wait去减的

我也这样认为。

from blog.

xpfxzxc avatar xpfxzxc commented on May 2, 2024

第三版

有人就说了:我想要一个有头有尾的!就是鼠标移入能立刻执行,停止触发的时候还能再执行一次!

我是这样理解上面需求:如果事件只触发一次,按道理应该执行两次。
但从代码实现看,如果事件只触发一次,只有事件刚触发的时候执行。
不知我是不是理解错需求了,还是看理解错代码实现了。

from blog.

bosens-China avatar bosens-China commented on May 2, 2024

!previous && options.leading === false
这里!previous有什么用呢

一个判断条件,你看他后面的代码有把previous 重置成了0

from blog.

pekonchan avatar pekonchan commented on May 2, 2024

虚心请教,既然节流可以由我们设置“有头有尾”“有头无尾”“无头有尾”,那么对于你之前提出的防抖方案,是否也可以做到这三种设置呢?

from blog.

pekonchan avatar pekonchan commented on May 2, 2024

不知道是否存在这个问题:

由于setTimeout中func.apply(context, args);contextargs都是之前设置timeout时的this和arguments,这样的话,最后延迟执行func函数,就不是真正最后停止操作最后一次的this和参数列表了

from blog.

xsfxtsxxr avatar xsfxtsxxr commented on May 2, 2024
// 第一版
function throttle(func, wait) {
    var previous = 0;

    return function() {
        var now = +new Date();
        if (now - previous > wait) {
            func.apply(this, arguments);
            previous = now;
        }
    }
}

from blog.

MrRENGE avatar MrRENGE commented on May 2, 2024

是不是把最后的 previous=now; 改为 previous=+new Date();更准确,因为经历了代码执行需要消耗掉部分时间。
image

from blog.

DFLovingWM avatar DFLovingWM commented on May 2, 2024

唯一的疑问是,这个判断什么时候会进去?

if (timeout) {
  clearTimeout(timeout);
  timeout = null;
 }

from blog.

qqis314860100 avatar qqis314860100 commented on May 2, 2024

唯一的疑问是,这个判断什么时候会进去?

if (timeout) {
  clearTimeout(timeout);
  timeout = null;
 }

我也好奇。。可能保险为了处理极端情况?

from blog.

thomas-void0 avatar thomas-void0 commented on May 2, 2024

在第四版中, if (!timeout) context = args = null; 的作用是什么,为什么把原本context指向的this编程了args?

from blog.

xiaolee55 avatar xiaolee55 commented on May 2, 2024

唯一的疑问是,这个判断什么时候会进去?

if (timeout) {
  clearTimeout(timeout);
  timeout = null;
 }

因为setTimeOut有误差,所以有可能到时间了定时器还没有执行,就会进入时间戳判断逻辑,所以要把定时器删掉

from blog.

sunnyBob avatar sunnyBob commented on May 2, 2024
    var later = function() {
        previous = options.leading === false ? 0 : new Date().getTime();
        timeout = null;
        func.apply(context, args);
        if (!timeout) context = args = null;
    };

前边给 timeout 赋值为 null 了,接着 if 判断 if (!timeout) context = args = null;,这个判断一定会执行吧?

from blog.

pumpkinduan avatar pumpkinduan commented on May 2, 2024
 var later = function() {
        previous = +new Date();
        timeout = null;
        func.apply(context, args)
  };

请问这里为什么要将previous = +new Date();呢?

from blog.

coderzym avatar coderzym commented on May 2, 2024

有一个地方不是很懂,为什么是args = arguments,这样的话,args不也是类数组吗?为什么不用...args,有些困惑,希望大佬们能够解答

from blog.

bosens-China avatar bosens-China commented on May 2, 2024
    var later = function() {
        previous = options.leading === false ? 0 : new Date().getTime();
        timeout = null;
        func.apply(context, args);
        if (!timeout) context = args = null;
    };

if (!timeout) context = args = null;这一句没必要加上if吧,上面已经设置为null

from blog.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.