Comments (125)
这是隐式转换的玄学。
我们先看看ECMAScript规范对一元运算符的规范:
一元+ 运算符
一元+运算符将其操作数转换为Number类型并反转其正负。注意负的+0产生-0,负的-0产生+0。
产生式 UnaryExpression : - UnaryExpression 按照下面的过程执行 :
- 令 expr 为解释执行 UnaryExpression 的结果 .
- 令 oldValue 为 ToNumber(GetValue(expr)).
- 如果 oldValue is NaN ,return NaN.
- 返回 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():
- 运算符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.
var now = +new Date(); 请问这里为什么要有个加号?
from blog.
// 第二版
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.
@xue1234jun 防抖是虽然事件持续触发,但只有等事件停止触发后 n 秒才执行函数,节流是持续触发的时候,每 n 秒执行一次函数
from blog.
@lindazhang102 这个是转时间戳的方法
from blog.
@lindazhang102 正如 @jawil 所说,是利用隐式类型转换将时间对象转换为时间戳,类似的还有:
+'1' // 1 (转数字)
1 + '' // '1' (转字符串)
!!1 // true (转布尔值)
from blog.
第三版
if (remaining <= 0 || remaining > wait) {
这里的remaining好像不会出现大于wait的情况吧
毕竟是基于wait去减的
from blog.
那为什么第二种置为空进行垃圾回收,时间戳的第一种就没有置为空呀,真心请教,疑惑
from blog.
唯一的疑问是,这个判断什么时候会进去?
if (timeout) { clearTimeout(timeout); timeout = null; }
@DFLovingWM
因为定时器并不是准确的时间,很可能你设置了2秒
但是他需要2.2秒才触发,这时候就会进入这个条件
from blog.
@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.
@ishowman 可以,但是我个人认为 undefined 是一个值,而 null 表示无,所以赋值为 undefined 其实是将值改成一个非常小的占用内存的值,效果上跟赋值为 null 还是差了一点……
from blog.
双剑合璧第三版首次执行的时候有点问题吧?
首次执行 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.
有一个地方不是很懂,为什么是args = arguments,这样的话,args不也是类数组吗?为什么不用...args,有些困惑,希望大佬们能够解答
因为是es6的语法,需要经过转义,你可以看作者写的代码全部用的var声明,默认是es5的环境,他这样做就是想让代码全部兼容,除了这个你想怎么写都OK
from blog.
@MuYunyun 思路的话应该就是先看懂大致的实现,然后自己尝试着实现一个非常简单的,然后再向着最后的代码,一个功能点一个功能点的完善吧~ 技巧的话,有一个就是到相应仓库搜索与这段代码相关的 PR 或 isuue,也会有很多收获~
from blog.
防抖和节流第一次理解起来容易混淆。
我觉得防抖主要是阻止频繁触发一个事件的,虽然会有一个时间间隔,但是当在时间间隔内多次触发,那么会刷新剩余时间,比如时间间隔2s,过了1s,此时剩余时间1s,但是立马又操作了,那么剩余时间重新变成2s了;
而节流是不管操作多频繁,我始终固定时间触发一次。
from blog.
@zhangruinian 正是如此,使用定时器时置为空的主要目的并不是垃圾回收,主要是为了方便下次执行定时器
from blog.
节流,在规定时间内,保证执行一次该函数;防抖,当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始 延时。
from blog.
var now = +new Date(); 请问这里为什么要有个加号?
转为时间戳。 毫秒数
from blog.
请问
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.
使用定时器实现throttle节流函数时,不需要再声明previous时间戳啦~
from blog.
这是隐式转换的玄学。
我们先看看ECMAScript规范对一元运算符的规范:
一元+ 运算符
一元+运算符将其操作数转换为Number类型并反转其正负。注意负的+0产生-0,负的-0产生+0。产生式 UnaryExpression : - UnaryExpression 按照下面的过程执行 :
- 令 expr 为解释执行 UnaryExpression 的结果 .
- 令 oldValue 为 ToNumber(GetValue(expr)).
- 如果 oldValue is NaN ,return NaN.
- 返回 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():
- 运算符new的优先级高于一元运算符+,所以过程可以分解为:
var time=new Date();
+time2.根据上面提到的规则相当于: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.
我有一个问题,希望能得到各位大佬的解答。在第三版本中,当我的触发时间是在wait规定的周期以内的时候。就会进入到later中进行执行,但是later中又把timeout置为了null。那我下一次触发事件的时候,实际并不会进入到if(timeout){clearTimeout(timeout)}中去。那上一个定时器似乎并未得到回收?
from blog.
var later = function() { previous = +new Date(); timeout = null; func.apply(context, args) };请问这里为什么要将
previous = +new Date();
呢?
给他转化为时间戳,你可以自己实现以下,这个是隐士转换
from blog.
沙发!!!
from blog.
@WittyBob 如果你修改了系统时间,就会产生 remaining > wait 的情况……
from blog.
@Awzp 置为空是为了 js 的垃圾回收,不过 later 函数中的 timeout 判断其实没有必要,估计是 underscore 在多次修改后忽略了这个问题~
from blog.
厉害了我的哥。。。
from blog.
you are really something my brother!
from blog.
timeout = null;
是为了清楚闭包产生的变量常驻内存问题是吧?除了将变量重新赋值为null可以清楚使内存回收机制清楚变量占用的变量,赋值为undefined能清楚内存占用吗?
from blog.
所以timeout = null主要是为了使其为空然后下次好接着执行?
from blog.
@ClausClaus @dbfterrific 这应该是一个意思吧~ 😂
from blog.
节流和去抖如何区分
from blog.
@mqyqingfeng 非常感谢
from blog.
我们要注意 underscore 的实现中有这样一个问题:
那就是 leading:false 和 trailing: false 不能同时设置。
如果同时设置的话,比如当你将鼠标移出的时候,因为 trailing 设置为 false,停止触发的时候不会设置定时器,所以只要再过了设置的时间,再移入的话,就会立刻执行,就违反了 leading: false,bug 就出来了
其一: underscore 怎么没有考虑 leading = false
和 trailing = 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.
@savoygu 关于第一个问题,其实有人提过这个问题,这是当时的一个核心贡献者的回答:
这个开发者认为两者必须要有一个为 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.
第一个人提出了这个需求,第二个人认为这个需求太小众,但是 underscore 作者接了这个需求~😂
from blog.
@mqyqingfeng 感谢
from blog.
要是有代码的思路就更好了~~~~~~~~~~~~~~~~~
from blog.
@zjp6049 嗯嗯,这个日后补充~
from blog.
感觉later函数里的这里判断没啥意义啊,求指教
previous = options.leading === false ? 0 : new Date().getTime();
直接previous = +new Date()不行吗
from blog.
@zjp6049 嗯嗯,明白啦,谢谢
from blog.
@jxZhangLi 嗯嗯,感谢补充,这样确实可以做到立即执行~
from blog.
@zjp6049 非常感谢回答哈~ ( ̄▽ ̄)~*
from blog.
@jawil 你那个ECMAScript规范能给个链接吗?
from blog.
@meiminjun jawil 现在很忙😂~ http://es5.github.io/#x9.3
from blog.
@meiminjun 中文版 建议看英文版,中文版翻译有些地方有坑。
@mqyqingfeng 不忘初心,方得始终,再忙也要补充一下。
from blog.
@jawil 哈哈,那还不赶紧把你博客里的问题回答一下?有些问题我不会,就不能帮你解答了😂
from blog.
那些问题可以写一篇文章了,我都记着呢,还有那个谷歌插件也要来一波更新,时间真的不够用。@mqyqingfeng
from blog.
已粉,博主的文章的思路由浅入深,质量和产量都让我敬佩,敢问博主是如何将 underscore 的源码抽丝剥茧成第一版、第二版..第N版的,这其中的思路和技巧能和我们分享下吗?
另外弄懂第三版,可能需要这个知识点:
console.log(1)
setTimeout(() => {console.log(2)}, 0)
console.log(3)
// 输出顺序为 => 1, 3 ,2
from blog.
非常感谢,学习了:heart:
from blog.
`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.
@iiicon 有一种情况是更改了系统时间……
from blog.
第一版看上去应该也是等待wait时间后执行啊,为什么会立刻执行呢,求解
from blog.
@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.
请教下大神,我把第四版改成这样是否可行呢,里面有没有存在错误
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.
@Fromzero828 你的这段代码里 flag
这个变量只有从undefined
变成true
的过程,而没有复位的部分。那么你在首次执行后,flag永远是true。这使得你停下一段大于wait的时间后,再次触发时并不会出现“立即执行”的效果。
最基本的解决方法的话,可以在else分支 setTimeout要执行的函数体结尾加上一句flag=false
,作用就跟博主代码中对于previous
和timeout
进行复位的代码一样。
from blog.
谢谢,对我很有帮助!
from blog.
/**
* @ 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.
节流 不需要做返回值吗
from blog.
大神请问这句代码的作用是什么:
if (!timeout) context = args = null;
手动清空为了节省内存么?
from blog.
@mqyqingfeng
第一版代码中,每次触发事件都要执行 previous = 0
那不是每次都被清零了么?
from blog.
@inter727 container.onmousemove执行的是return后的function,所以每次执行完后 previous = now;
from blog.
我感觉previous初始化的时候为+new date()会不会更好点
from blog.
双剑合璧第三版首次执行的时候有点问题吧?
首次执行 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.
这段代码存在如下问题
首次执行时会走 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.
我感觉previous初始化的时候为+new date()会不会更好点
不知道你指的是哪个版本,就假定是第三版好了。
previous初始化为0是有意义的,能确保首次执行时进入 if (remaining <= 0 || remaining > wait)
分支并触发 func.apply(context, args)
。
from blog.
请问
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.
感觉这个比jquery 的实现复杂多了。
from blog.
@jawil 你好,new Date的实例preferType被设置为String,不是应该先执行toString()方法吗,然后返回来一段字符串,是原始值,为什么是先执行valueOf,不来理解,可以解答下吗?
from blog.
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.
双剑合璧第三版首次执行的时候有点问题吧?
首次执行 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.
@jawil 你好,new Date的实例preferType被设置为String,不是应该先执行toString()方法吗,然后返回来一段字符串,是原始值,为什么是先执行valueOf,不来理解,可以解答下吗?
from blog.
Wow. You are a genius!!
from blog.
这个是高级程序设计3,第615页的函数节流实现,我总是感觉这个更像是防抖,有没有大佬帮忙看一下,谢谢
` function throttle(method, context) {
clearTimeout(method.tId);
method.tId = setTimeout(function () {
method.call(context);
}, 100);
}`
from blog.
这个是高级程序设计3,第615页的函数节流实现,我总是感觉这个更像是防抖,有没有大佬帮忙看一下,谢谢
` function throttle(method, context) { clearTimeout(method.tId); method.tId = setTimeout(function () { method.call(context); }, 100); }`
结论:这个确实是我们通常意义上讲的防抖动实现 debounce
from blog.
受益匪浅 感谢🙏
from blog.
great
from blog.
thanks
from blog.
思路很赞👍
from blog.
// 第一版
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.
// 第二版
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.
@jawil 你好,new Date的实例preferType被设置为String,不是应该先执行toString()方法吗,然后返回来一段字符串,是原始值,为什么是先执行valueOf,不来理解,可以解答下吗?
如图 应该是toString() 吧?
from blog.
result
声明了没赋值,没返回
from blog.
这是隐式转换的玄学。
我们先看看ECMAScript规范对一元运算符的规范:
一元+ 运算符
一元+运算符将其操作数转换为Number类型并反转其正负。注意负的+0产生-0,负的-0产生+0。产生式 UnaryExpression : - UnaryExpression 按照下面的过程执行 :
- 令 expr 为解释执行 UnaryExpression 的结果 .
- 令 oldValue 为 ToNumber(GetValue(expr)).
- 如果 oldValue is NaN ,return NaN.
- 返回 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():
- 运算符new的优先级高于一元运算符+,所以过程可以分解为:
var time=new Date();
+time2.根据上面提到的规则相当于: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.
!previous && options.leading === false
这里!previous有什么用呢
from blog.
第三版
if (remaining <= 0 || remaining > wait) {
这里的remaining好像不会出现大于wait的情况吧
毕竟是基于wait去减的
我也这样认为。
from blog.
第三版
有人就说了:我想要一个有头有尾的!就是鼠标移入能立刻执行,停止触发的时候还能再执行一次!
我是这样理解上面需求:如果事件只触发一次,按道理应该执行两次。
但从代码实现看,如果事件只触发一次,只有事件刚触发的时候执行。
不知我是不是理解错需求了,还是看理解错代码实现了。
from blog.
!previous && options.leading === false
这里!previous有什么用呢
一个判断条件,你看他后面的代码有把previous 重置成了0
from blog.
虚心请教,既然节流可以由我们设置“有头有尾”“有头无尾”“无头有尾”,那么对于你之前提出的防抖方案,是否也可以做到这三种设置呢?
from blog.
不知道是否存在这个问题:
由于setTimeout中func.apply(context, args);
, context
和args
都是之前设置timeout时的this和arguments,这样的话,最后延迟执行func函数,就不是真正最后停止操作最后一次的this和参数列表了
from blog.
// 第一版
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.
是不是把最后的 previous=now;
改为 previous=+new Date();
更准确,因为经历了代码执行需要消耗掉部分时间。
from blog.
唯一的疑问是,这个判断什么时候会进去?
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
from blog.
唯一的疑问是,这个判断什么时候会进去?
if (timeout) { clearTimeout(timeout); timeout = null; }
我也好奇。。可能保险为了处理极端情况?
from blog.
在第四版中, if (!timeout) context = args = null; 的作用是什么,为什么把原本context指向的this编程了args?
from blog.
唯一的疑问是,这个判断什么时候会进去?
if (timeout) { clearTimeout(timeout); timeout = null; }
因为setTimeOut有误差,所以有可能到时间了定时器还没有执行,就会进入时间戳判断逻辑,所以要把定时器删掉
from blog.
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.
var later = function() {
previous = +new Date();
timeout = null;
func.apply(context, args)
};
请问这里为什么要将previous = +new Date();
呢?
from blog.
有一个地方不是很懂,为什么是args = arguments,这样的话,args不也是类数组吗?为什么不用...args,有些困惑,希望大佬们能够解答
from blog.
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)
- 冴羽答读者问:如何在工作中打造影响力,带动同事?
- 无
- 冴羽答读者问:如何学习更有计划性、提升更稳更快? HOT 4
- 冴羽答读者问:过程比结果重要吗? HOT 1
- 冴羽答读者问:冴羽,你为什么写起了鸡汤? HOT 3
- 聊聊 npm 的语义化版本(Semver) HOT 4
- How to create Backlinks 😤
- 思考题第2题
- 可以理解为原型是prototype,原型链是通过__proto__ 链接起来的吗
- React 之 createElement 源码解读 HOT 8
- React 之元素与组件的区别 HOT 1
- React 之 Refs 的使用和 forwardRef 的源码解读
- React 之 Context 的变迁与背后实现 HOT 1
- 第一段不报错啊,刚试过了,会打印1
- how can i HOT 1
- Hosting of Blog Issue
- 全局对象
- 为啥每次都要创建一个 Child函数来new 子类?现在不都是 const p1 = new Person(); const p2 = new Person()吗
- 文档内容中文件结构的错位 HOT 1
- 原型链继承
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from blog.