# 作用域闭包
# 什么是闭包?
红宝书(p178)上对于闭包的定义:闭包是指有权访问另外一个函数作用域中的变量的函数,
MDN 对闭包的定义为:闭包是指那些能够访问自由变量的函数。
由此,我们可以看出闭包共有两部分组成:
- 是一个函数
- 能访问另外一个函数作用域中的变量
对于闭包有下面三个特性:
1、闭包可以访问当前函数以外的变量
function getOuter(){
var date = '815';
function getDate(str){
console.log(str + date); //访问外部的date
}
return getDate('今天是:'); //"今天是:815"
}
getOuter();
2、即使外部函数已经返回,闭包仍能访问外部函数定义的变量
function getOuter(){
var date = '815';
function getDate(str){
console.log(str + date); //访问外部的date
}
return getDate; //外部函数返回
}
var today = getOuter();
today('今天是:'); //"今天是:815"
today('明天不是:'); //"明天不是:815"
3、闭包可以更新外部变量的值
function updateCount(){
var count = 0;
function getCount(val){
count = val;
console.log(count);
}
return getCount; //外部函数返回
}
var count = updateCount();
count(815); //815
count(816); //816
为什么闭包的应用都有关键词 return
,引用 JavaScript 秘密花园中的一段话:
闭包是 JavaScript 一个非常重要的特性,这意味着当前作用域总是能够访问外部作用域中的变量。 因为 函数 是 JavaScript 中唯一拥有自身作用域的结构,因此闭包的创建依赖于函数。
# 应用场景
具体应用场景你知道哪些??
- 保护函数内的变量安全:如迭代器、生成器。
- 在内存中维持变量:如缓存数据、柯里化。
# 私有属性
var foo = (function(){
var secret = 'secret'
// “闭包”内的函数可以访问 secret 变量,而 secret 变量对于外部却是隐藏的
return {
get_secret() {
return secret
},
new_secret(new_secret) {
secret = new_secret
}
}
})()
foo.secret // undefined
foo.get_secret() // 'secret'
foo.new_secret('哈哈哈') // 修改secret值
foo.get_secret() // '哈哈哈'
之所以可能通过这种方式在 JavaScript 种实现公有,私有,特权变量正是因为闭包,闭包是指在 JavaScript 中,内部函数总是可以访问其所在的外部函数中声明的参数和变量,即使在其外部函数被返回(寿命终结)了之后。
let sque = (function () {
let _width = Symbol();
class Squery {
constructor(s) {
this[_width] = s
}
foo() {
console.log(this[_width])
}
}
return Squery
})();
let ss = new sque(20);
ss.foo() // 20
console.log(ss[_width]) // ReferenceError: _width is not defined
# 单例模式
class Modal {
constructor(name) {
this.name = name
this.getName()
}
getName() {
return this.name
}
}
let ProxySing = (function(){
let instance;
return function(name) {
if (!instance) {
instance = new Modal(name)
}
return instance
}
})()
let a = new ProxySing('问题框');
let b = new ProxySing('回答框');
console.log(a === b); // true
console.log(a.getName()); // '问题框'
console.log(b.getName()); // '问题框'
# 函数防抖
const fn = () => console.log('fn')
window.onresize = debounce(fn, 1000)
function debounce(fn, interval) {
let timer = null;
return function (...args) {
if(timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, interval);
}
}
# 面试题
接下来,看这道刷题必刷,面试必考的加强版闭包题:
for (var i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
console.log(i)
// 5 5 5 5 5 5
答案是都是5,6个5,让我们分析一下原因:
由于作用域链机制的影响,闭包只能取得内部函数的最后一个值,这引起的一个副作用就是如果内部函数在一个循环中,那么变量的值始终为最后一个值。
如果要强制返回预期的结果(5,0,1,2,3,4),怎么办???
加个闭包
# 方法1:立即执行函数
把值传参给一个自执行的函数,函数具有块级作用域
for (var i = 0; i < 5; i++) {
((num) => {
setTimeout(() => {
console.log(num);
}, 1000);
})(i);
}
console.log(i)
# 方法2:setTimeout传参
setTimeout
被遗忘的第三个参数,定时器启动时候,第三个以后的参数是作为第一个func()的参数传进去。
for (var i = 0; i < 5; i++) {
setTimeout((j) => {
console.log(j);
}, 1000, i);
}
console.log(i)
# 方法3:使用ES6中的let
let
具有块级作用域,所以外面的会报错,未定义该变量,在这儿行不通
for (let i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
console.log(i) // i is not defined
# 方法4:函数调用
var output = function (i) {
setTimeout(function() {
console.log(i);
}, 1000);
};
for (var i = 0; i < 5; i++) {
output(i); // 这里传过去的 i 值被复制了
}
console.log(i)
# 闭包面试题再升级 - Promise
想要让它按顺序输出(0,1,2,3,4,5),并且要求代码块的循环和两处
console.log
不变
新的需求可以精确的描述为:代码执行时,立即输出 0,之后每隔 1 秒依次输出 1,2,3,4,循环结束后在大概第 5 秒的时候输出 5
怎么做:
下面,有请大神Promise
出场,掌声在哪里呢???
const tasks = [];
for (var i = 0; i < 5; i++) { // 这里 i 的声明不能改成 let,如果要改该怎么做?
((j) => {
tasks.push(new Promise((resolve) => {
setTimeout(() => {
console.log(new Date, j);
resolve(); // 这里一定要 resolve,否则代码不会按预期 work
}, 1000 * j); // 定时器的超时时间逐步增加
}));
})(i);
}
Promise.all(tasks).then(() => {
setTimeout(() => {
console.log(new Date, i);
}, 1000); // 注意这里只需要把超时设置为 1 秒
});
听说这是拥有加分项的模块化
const tasks = []; // 这里存放异步操作的 Promise
const output = (i) => new Promise((resolve) => {
setTimeout(() => {
console.log(new Date, i);
resolve();
}, 1000 * i);
});
// 生成全部的异步操作
for (var i = 0; i < 5; i++) {
tasks.push(output(i));
}
// 异步操作完成之后,输出最后的 i
Promise.all(tasks).then(() => {
setTimeout(() => {
console.log(new Date, i);
}, 1000);
});
# 闭包面试题再升级 - async/await
有大神Promise
能搞定的事情,那我async/await
也无所畏惧
// 模拟其他语言中的 sleep,实际上可以是任何异步操作
const sleep = (time) => new Promise((resolve) => {
setTimeout(resolve, time);
});
(async () => { // 声明即执行的 async 函数表达式
for (var i = 0; i < 5; i++) {
if (i > 0) {
await sleep(1000);
}
console.log(new Date, i);
}
await sleep(1000);
console.log(new Date, i);
})();