乐趣区

关于javascript:笔记javascript闭包及其应用

闭包

  • 闭包:是指有权拜访另一个函数作用域中的变量的函数。创立闭包的常见形式,就是在一个函数外部创立另一个函数。
  • 示例
    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();  // 打印 1
inc();  // 打印 2
const 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(); // 打印 1
inc();             // 打印 2
inc();             // 打印 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)                // 6
add(1, 2, 3)(4)             // 10
add(1)(2)(3)(4)(5)          // 15
add(2, 6)(1)                // 9
退出移动版