闭包
- 闭包:是指有权拜访另一个函数作用域中的变量的函数。创立闭包的常见形式,就是在一个函数外部创立另一个函数。
- 示例
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(); // 打印 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