闭包

  • 闭包:是指有权拜访另一个函数作用域中的变量的函数。创立闭包的常见形式,就是在一个函数外部创立另一个函数。
  • 示例
    function createFunction() {      var result = new Array()      for (var i = 0; i < 10; i++) {        result[i] = function() {          return i;        }      }      return result;    } // 每个函数返回都是 10    //增加闭包    function createFunction() {      var result = new Array()      for (var i = 0; i < 10; i++) {        result[i] = function(num) {          return function() {            return num          }        }(i)      }      return result    }

this对象

  • 在全局函数中,this等于window(非严格模式), 或者undefined(严格模式)
  • 当函数作为某个对象的办法调用时,this等于那个对象,不过,匿名函数的执行环境具备全局性,因而其this对象通过指向window。
  • 作为结构函数调用,this 指代new 的实例对象
  • 通过apply() 或 call()扭转函数执行环境的状况下,this就会指向其余对象。
  • Node模块或ES6模块中,this返回的是以后模块
  • 示例
  var name = "The Window"  var object = {    name: "My Object",    getNameFunc: function() {      return function() {        return this.name      }    }  }  console.log(object.getNameFunc()())  // "The Window"(非严格模式)  undefined(严格模式)  /*匿名函数的执行环境具备全局性,因而其this通过指向window*/  var name = "The Winodw"  var object = {    name: "My Object",    getName: function() {      return this.name;    }  }  object.getName() // "My Object"  (object.getName)() //"My Object"  (object.getName = object.getName)() // "The Window",在非严格格局下

闭包的作用

  • 应用闭包能够在JavaScript中模拟块级作用域,要点如下:

    • 创立并立刻调用一个函数,这样既能够执行其中的代码,又不会在内存中留下对该函数的援用。
    • 后果就是函数外部的所有变量都会被立刻销毁——除非将某些变量赋值给了蕴含作用域(即内部作用域)中的变量。
  • 闭包还能够用于在对象中创立公有变量,相干概念和要点如下:

    • 即便JavaScript中没有正式的公有对象属性的概念,但能够应用闭包来实现私有办法,而通过私有办法能够拜访在蕴含作用域中定义的变量。
    • 有权拜访公有变量的私有办法叫做特权办法。
    • 能够应用构造函数模式、原型模式来实现自定义类型的特权办法,也能够应用模块模式、加强的模块模式来实现单例的特权办法。

模拟块级作用域

  • javascript没有块级作用域的概念,这意味着在块级语句中定义的变量,实际上是在蕴含函数中而非语句中创立的。匿名函数能够用来模拟块级作用域并防止这个问题。用作块级作用域(通常称为公有作用域)的匿名函数的语法如下:
(function(){     // 这里是块级作用域})()
  • 以一个例子阐明
    例1:
    function outputNumber() {      for (var i = 0; i < 10; i += 1) {}      console.log(i);    }    outputNumber();    // 此时会输入 10 

例2:

    function outputNumber() {      (function () {        for (var i = 0; i < 10; i += 1) {          console.log('i', i);        }      })();      console.log(i);    }    outputNumber();
增加一个匿名立刻执行函数,此时就会报错 Uncaught ReferenceError: i is not defined, 达到了块级作用域的成果

公有变量

  • 严格来讲,javascript中没有公有成员的概念,所有对象属性都是私有的。不过,倒是有一个公有变量的概念。任何在函数中定义的变量,都能够认为是公有变量,因为不能在函数的内部拜访这些变量。
  • 如果在这个函数外部创立一个闭包,那么闭包通过t本人的作用域链也能够拜访这些变量。而利用这一点,就能够创立用于说公有变量的私有办法。
  • 咱们把有权拜访公有变量和公有函数的私有办法称为特权办法。有两种在对象上创立特权办法的形式。

    1. 在构造函数中定义特权办法

  • 示例
    function myObject() {      // 公有变量和公有函数      var privateVariable = 10;      function privateFunction() {        return false;      }      // 特权办法      this.publicMethod = function () {        privateVariable++;        return privateFunction();      };    }
  • 在构造函数中定义特权办法的毛病:必须应用构造函数模式来达到目标,构造函数模式的毛病是针对每个实例都会创立同样一组新办法,而应用动态公有变量来实现特权办法就能够防止这个问题

    2. 动态公有变量

  • 通过在公有作用域中定义公有变量或函数,同样也能够创立特权办法。
  • 示例
    (function () {      // 公有变量和公有函数      var privateVariable = 10;      function privateFunction() {        return false;      }      // 构造函数      MyObject = function () {};      // 特权办法      MyObject.prototype.publicMethod = function () {        privateVariable++;        return privateFunction();      };    })();
  • 这个模式与在构造函数中定义特权办法的次要区别,就在于公有变量和函数是由实例共享的。因为特权办法是在原型上定义的,因而所有实例都应用同一个函数。而这个特权办法,作为一个闭包,总是保留着对蕴含作用域的援用。

    模块模式

  • 模块模式则是为单例创立公有变量和特权办法。所谓单例,指的就是只有一个实例的对象。javascript是以对象字面量的形式来创立单例对象的。
  • 示例
    var singleton = {      name:value,      method: function(){        // do something      }    }
  • 模块模式通过为单例增加公有变量和特权办法可能使其失去加强
    var singleton = (function () {      // 公有变量和公有函数      var privateVariable = 10;      function privateFunction() {        return false;      }      // 特权办法和属性      return {        publicProperty: true,        publicMethod: function () {          privateVariable++;          return privateFunction();        },      };    })();

减少的模块模式

  • 加强的模块模式适宜那些单例必须是某种类型的实例,同时还必须增加某些属性/办法对其加以加强的状况。
  • 示例
    var application = function () {      var components = new Array();      components.push(new BaseComponent());      var app = new BaseComponent();      app.getComponentCount = function () {        return components.length;      };      app.registerComponent = function (component) {        if (typeof component === 'object') {          components.push(component);        }      };      return app;    };// 限度application必须是BaseComponent的实例

过期的闭包

  • 问题示例
    function createIncrement(i) {      let value = 0;      function increment() {        value += i;        console.log(value);        const message = `Current value is ${value}`;        return function logValue() {          console.log(message);        };      }      return increment;    }    const inc = createIncrement(1);    const log = inc(); // 打印 1    inc(); // 打印 2    inc(); // 打印 3    // 无奈正确工作    log(); // 打印 "Current value is 1"

修复过期闭包:

  1. 解决过期闭包的第一种办法是找到捕捉最新变量的闭包。就是从最初一次调用 inc() 返回的闭包。
const inc = createIncrement(1);inc();  // 打印 1inc();  // 打印 2const latestLog = inc(); // 打印 3// 失常工作latestLog(); // 打印 "Current value is 3"

2.第二种办法是让logValue()间接应用 value

function createIncrement(i) {  let value = 0;  function increment() {    value += i;    console.log(value);    return function logValue() {      const message = `Current value is ${value}`;      console.log(message);    };  }  return increment;}const inc = createIncrement(1);const log = inc(); // 打印 1inc();             // 打印 2inc();             // 打印 3// 失常工作log();             // 打印 "Current value is 3"

React Hook

useEffect中过期闭包的问题及解决

  • 示例
 import React, { useEffect, useState } from 'react';const HomePage = () => {  const [count, setCount] = useState(0);  const [count1, setCount1] = useState(0);  const [count2, setCount2] = useState(0);  let tm;  useEffect(() => {    setInterval(() => {      setCount(count + 1);    }, 1000);  }, []);  useEffect(() => {    setInterval(() => {      setCount1((prev) => {        return prev + 1;      });    }, 1000);  }, []);  useEffect(() => {    tm = setInterval(() => {      setCount2(count2 + 1);    }, 1000);    return () => {      clearInterval(tm);      tm = null;    };  }, [count2]);  return (    <div>      {/* count 始终显示为 1 */}      <p>计数:{count}</p>      {/* 以下两种形式失常计数 */}      <p>计数-callback:{count1}</p>      <p>计数-依赖项:{count2}</p>    </div>  );};

防抖与节流

  • 节流:每隔一段时间触发一次事件
  // 形式1:  const throlttle3 = (fn, delay) => {    let flag = true;    return function () {      if (flag) {        flag = false;        setTimeout(() => {          fn.apply(this, arguments);          flag = true;        }, delay);      }    };  };  // 形式2 : 工夫戳  const throlttle = (fn, delay) => {    let prev = Date.now();    return function () {      const now = Date.now();      if (now - prev >= delay) {        fn.apply(this, arguments);        prev = Date.now();      }    };  }; // 形式3: 定时器  const throlttle2 = (fn, delay) => {    let tm = null;    return function () {      if (!tm) {        tm = setTimeout(() => {          fn.apply(this, arguments);        }, delay);      }    };  };  // 第一次立刻执行  const throlttle4 = (fn, delay) => {    let tm = null;    let lastTm = Date.now();    return function () {      let curTm = Date.now();      var rest = delay - (curTm - lastTm);      clearTimeout(tm);      if (rest <= 0) {        fn.apply(this, arguments);        lastTm = Date.now();      } else {        tm = setTimeout(() => {          fn.apply(this, arguments);        }, rest);      }    };  };
  • 防抖:每次触发事件时设置一个提早调用办法,并且勾销之前的延时调用办法。
  const debounce = (fn, delay) => {    ``;    let tm = null;    return function () {      if (tm) clearTimeout(tm);      tm = setTimeout(() => {        fn.apply(this, arguments);      }, delay);    };  };

柯里化

  • 是把承受多个参数的函数变换成承受一个繁多参数(最后函数的第一个参数)的函数,并且返回承受余下的参数而且返回后果的新函数的技术。
  • 示例
// 实现一个add办法,使计算结果可能满足如下预期:add(1)(2)(3) = 6;add(1, 2, 3)(4) = 10;add(1)(2)(3)(4)(5) = 15;function add() {    // 第一次执行时,定义一个数组专门用来存储所有的参数    var _args = Array.prototype.slice.call(arguments);    // 在外部申明一个函数,利用闭包的个性保留_args并收集所有的参数值    var _adder = function() {        _args.push(...arguments);        return _adder;    };    // 利用toString隐式转换的个性,当最初执行时隐式转换,并计算最终的值返回    _adder.toString = function () {        return _args.reduce(function (a, b) {            return a + b;        });    }    return _adder;}add(1)(2)(3)                // 6add(1, 2, 3)(4)             // 10add(1)(2)(3)(4)(5)          // 15add(2, 6)(1)                // 9