闭包
- 闭包:是指有权拜访另一个函数作用域中的变量的函数。创立闭包的常见形式,就是在一个函数外部创立另一个函数。
- 示例
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"
修复过期闭包:
- 解决过期闭包的第一种办法是找到捕捉最新变量的闭包。就是从最初一次调用 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