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