debounce 中遇到的小问题:<

889 阅读6分钟

文章建立在大家都已经了解过防抖的基础之上

为什么在防抖中加入return function()

<input id="ooclick" type="button" value="click" />
const btn = document.getElementById("btn");
function cc() {
  console.log("click me ");
}

function debounce(func, wait) {
  return function () {
    // =>为什么用 return
    func();
  };
}
// 下面2给方法等价
btn.addEventListener("click", cc);
btn.addEventListener("click", function (event) {
  cc(event);
});

btn.addEventListener

element.addEventListener(event, function, useCapture)

  1. 第一个参数是事件的类型(如'click'或'mousedown')

  2. 第二个参数是我们想要在事件发生时调用的函数。

    其实还可以传递一个 object 给 addEventListener 当作第二个参数,当事件被触发时,该 object 的 handleEvent 方法被调用。

    document.body.addEventListener(
      "click",
      {
        handleEvent: function () {
          alert("body clicked");
        },
      },
      false
    );
    

    使用 object 作为第二个参数有一个重要的特征:handleEvent 属性的函数值只会在事件触发的时候会被调用。这意味着我们改变 handleEvent 属性的函数值,那么事件触发时就会调用不同的函数,也就是延迟绑定。

    document.body.addEventListener("click", obj, false);
    obj.handleEvent = function () {
      alert("alpha");
    };
    
  3. 第三个参数是一个布尔值,指定是使用事件冒泡还是事件捕获(此参数是可选的,默认值为 false,事件冒泡)

    event.stopPropagation(); //阻止事件冒泡
    event.stopImmediatePropagation(); // 阻止事件捕获
    

用延迟绑定

btn.addEventListener("click", cc);
// 注意!!btn.addEventListener("click", cc()); 绑定时会直接执行 cc 结果
btn.addEventListener("click", debounce()); // debounce() 是 function()

这里的cc是一个表达式及obj.handleEvent = cc; 非方法立即执行的结果集; 而 obj.handleEvent = debounce();是返回执行防抖函数中暴露的方法(return 中的闭包方法) debounce() 打印出来也是 function debounce()()才是执行方法

注意点

function a() {
  console.log("a");
}
var b = a();
// b变成一个立即执行函数的返回结果集b; (b()报错)
var b = a;
// b变成可执行函数表达式 ;b(); (b;啥也没有,打印了 function)

设置和清除时间管理者

作用域和闭包

词法作用域

编译器的第一个工作阶段叫作分词,就是把由字符组成的字符串分解成词法单元。这个概念是理解词法作用域的基础

简单地说,词法作用域就是定义在词法阶段的作用域,是由写代码时将变量和块作用域写在哪里来决定的,因此当词法分析器处理代码时会保持作用域不变; 无论函数在哪里被调用,也无论它如何被调用,它的词法作用域都只由函数被声明时所处的位置决定

词法作用域

闭包(closure)是 Javascript 语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。(极具语言特性)

红宝书

有权访问另一个函数作用域中的变量的函数。

you don’t know JS

当函数可以记住并访问所在的词法作用域时, 就产生了闭包, 即使函数是在当前词法作用域之外执行

权威指南

函数对象可以通过作用域关联起来,函数体内的变量都可以保存在函数作用域内,这在计算机科学文献中称为“闭包”,所有的 javascirpt 函数都是闭包

timer

function debounce(func, wait) {
  /**
    要让闭包函数相互之间建立关联关系,所建立的桥梁通讯(作用域链)
    timer 定义到闭包体的外面让闭包可以有权访问,这样我们定义监听的同时也能定义 timer 变量

    btn.addEventListener("click", debounce());  时候这里 timer 只创建了一次唯一一次
    每一次的点击只会触发 debounce()中的 return 函数体 ,不会再构建 debounce ()
    相当于多个异步子集函数体, 同用一个双亲函数(类似堆中的树状图中的度)
    */
  let timer = null;
  return function () {
    /**
    在此设置 timer 每一次进入本体函数都会触发 
    那么 timer = settimout 所返回的 ID 就不再是唯一值
    此处的执行函数都是相互独立互不相交
    正因为没有关系所以无法进行清除
    */
    // let timer = null;
    clearTimeout(timer);
    timer = setTimeout(function () {
      func();
    }, wait);
  };
}

clearTimeout() 方法可取消由 setTimeout() 方法设置的 timeout。 它的参由 setTimeout() 返回的 ID 值,该值标识要取消的延迟执行代码块。

this 你在指向哪里

this 你在哪里

const btn = document.getElementById("btn");
function cc() {
  console.log("click me ");
  console.log(this);
}
// 1
btn.addEventListener("click", cc);
// 2
btn.addEventListener("click", function () {
  console.log(this);
  cc();
});
  1. cc 中 this 指向 button 实例

  2. addEventListener 中 this 指向 button 实例 ; cc 中 this 指向 windows 实例

    需要改变 cc 中 this 的情况 cc.bind(this,..)();cc.applay(this,[]);cc.call(this,...)

加入 setTimeout

function debounce(func, wait) {
  let timer;
  return function () {
    let that = this; // 注意 this 指向
    timer && clearTimeout(timer);
    // 方法1 通过 that
    timer = setTimeout(function () {
      func.apply(that, args);
    }, wait);
    // 方法2 箭头函数
    timer = setTimeout(() => {
      func.apply(this, args);
    }, wait);
  };
}

我们应该去纠正 this 和 event 对象的指向问题?

因为如果不绑定 this,会出现典型的 this 丢失的情况(指向 windows)。 如果不修正 this 的指向,那么每一次引用会找不到具体实例,可能会导致意想不到的错误

VueComponent({
  data:{
    return{
      name:0
  }
})
debounce(function () {
  /**
    假设 vue 环境下未处理 this 丢失 this 没有指向 vue 实例对象
  */
  this.name === vueComponent.data.name // false
});

入参如何处理???

function debounce(func, wait) {
  let timer;
  return function () {
    let context = this; // 注意 this 指向
    timer && clearTimeout(timer);
    timer = setTimeout(() => {
      func.apply(this);
    }, wait);
  };
}

Arguments 对象

MDN 描述 在 javascript 中,函数是没有重载这一项的,所谓的重载;一个函数可以有多个,就是参数的个数和形式不同所以引用的功能不同,而 js 不存在函数重载,不管传不传参数,函数里面是否引用,关系都不大,一个函数对应一个功能,但是函数可以模拟函数重载,所以有一个 Arguments 对象。

javascript 函数体内,arguments 像数组(并不是真的数组,是一个 Arguments 对象,再次强调)一样,有 length 属性,可以代表传给函数的参数的个数。 javascript 中 Arguments 对象是函数的实际参数,arguments 对象的长度是由实参个数而不是形参个数决定的。

形参是函数内部重新开辟内存空间存储的变量,但是其与 arguments 对象内存空间并不重叠;

形参是函数内部重新开辟内存空间存储的变量,但是其与 arguments 对象内存空间并不重叠;

形参是函数内部重新开辟内存空间存储的变量,但是其与 arguments 对象内存空间并不重叠;

是一个对应于传递给函数的参数的类数组对象,对象长度是由实参个数而不是形参个数决定的。类数组(伪类数组):是数组的形式,有 length,但不具有数组的一切方法。

注意

es6 箭头函数的出现,arguments 对象相对来说少用了,因为箭头函数没有 arguments 对象。

arguments 对象是所有(非箭头,默认值)函数中都可用的局部变量;

arguments 对象是所有(非箭头,默认值)函数中都可用的局部变量;

arguments 对象是所有(非箭头,默认值)函数中都可用的局部变量;

你可以使用 arguments 对象在函数中引用函数的参数。此对象包含传递给函数的每个参数,第一个参数在索引 0 处。例如,如果一个函数传递了三个参数,你可以如下方式引用他们:

function a(aa = [], bb = 12, cc = "cc") {
  arguments[0]; // undefined
  arguments[1]; // null
  arguments[2]; // 0
  cc = "哇哈哈";
  arguments[2]; //0 和cc不相等
}
a(undefined, null, 0);

通过 apply call bind 进行传数据

function debounce(func, wait) {
  let timer;
  return function () {
    let context = this; // 注意 this 指向
    let args = arguments; // arguments中存着
    timer && clearTimeout(timer);
    timer = setTimeout(() => {
      func.apply(this, args);
      func.call(this, ...args);
      func.bind(this, ...args)();
    }, wait);
  };
}