解题
法1 - 数组遍历之map()方法
//解法1:
function makeClosures(arr, fn) {
return arr.map(function(item){
return function(){
return fn(item);
}
})
}
法2 - 数组遍历之forEach()方法
//解法2:
function makeClosures(arr, fn) {
var result = [];
arr.forEach(function(e){
result.push(function(num){
return function(){
return fn(num);
};
}(e));
});
return result;
}
一、知识点1 - js作用域(三大作用域)
- 全局作用域
- 函数作用域
- 块级作用域
- ES6新增,需配合const、let在 { } 中的作用域
- var 变量会挂载到window对象上,let、const不会
- 对象没有作用域!!!(但是有自己的this)
// 函数作用域
var a = "outter";
function fn(){
var a = "inner";
console.log(a);
}
fn(); //inner
// 对象没有作用域
var a = "outter";
var obj = {
a: "inner",
getA(){
console.log(a);
console.log(this.a)
}
}
obj.getA();
// outter(因为在同一作用域,先找到outter就停止了)
// inner(因为this绑定的是这个对象)
二、知识点2 - 闭包
闭包理解1:父函数中返回的子函数;
闭包理解2:一句话说,就是内层函数对外层函数变量的操作
闭包理解3:一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。
闭包理解4:在JS中,只有函数内部的子函数才能读取局部变量,闭包就是能够读取其他函数内部变量的函数。所以,闭包是将函数内部和函数外部连接起来的一座桥梁。
实例1
var a = "outer";
function fn(){
var a = "inner";
return function(){
console.log(a);
console.log(this);
console.log(this.a);
}
}
fn()();
//inner 此时在函数作用域中,a = inner
//window 匿名函数作为fn函数的返回值在全局作用域下执行,this是window
//outter
闭包使用场景
任何闭包的使用场景都离不开这两点
- 创建私有变量
- 延长变量的生命周期
一般函数的词法环境在函数返回后就被销毁,但是闭包会保存对创建时所在词法环境的引用,即便创建时所在的执行上下文被销毁,但创建时所在词法环境依然存在,以达到延长变量的生命周期的目的。
场景一 (函数复用 - 传参)
设计三个按钮,点击后分别对文字设置大小为10px、16px、20px
- 做法一:为三个按钮分别绑定一个onclick函数,三个函数里分别设置为10px、16px、20px
- 缺点:代码冗余,可以提炼一个函数,向内部传参
//省去部分html代码
var btn01 = document.getElementById("btn01");
var btn02 = document.getElementById("btn02");
var btn03 = document.getElementById("btn03");
btn01.onclick = function(){
var p = document.getElementById("content");
p.style.fontSize = 10 + "px";
}
btn02.onclick = function(){
var p = document.getElementById("content");
p.style.fontSize = 16 + "px";
}
btn03.onclick = function(){
var p = document.getElementById("content");
p.style.fontSize = 20 + "px";
}
- 做法二:对一进行修改,向同一个函数传参
- 问题:函数名加() 会立即执行,而传参必须带();
var btn01 = document.getElementById("btn01");
var btn02 = document.getElementById("btn02");
var btn03 = document.getElementById("btn03");
function sizeUp(fz) {
var p = document.getElementById("content");
p.style.fontSize = fz + "px";
console.log(fz);
}
btn01.onclick = sizeUp(10); //会立即执行
btn02.onclick = sizeUp(16); //会立即执行
btn03.onclick = sizeUp(20); //会立即执行
做法3:使用闭包
var btn01 = document.getElementById("btn01");
var btn02 = document.getElementById("btn02");
var btn03 = document.getElementById("btn03");
function sizeUp(fz) {
return function(){ //闭包
var p = document.getElementById("content");
p.style.fontSize = fz + "px";
}
}
btn01.onclick = sizeUp(10);
btn02.onclick = sizeUp(16);
btn03.onclick = sizeUp(20);
场景二 (参数复用 - 柯里化函数 )
柯里化的目的在于避免频繁调用具有相同参数,使参数复用
// 假设我们有一个求长方形面积的函数
function getArea(width, height) {
return width * height
}
// 如果我们碰到的长方形的宽老是10
const area1 = getArea(10, 20)
const area2 = getArea(10, 30)
// 我们可以使用闭包柯里化这个计算面积的函数
function getArea(width) {
return height => {
return width * height
}
}
const getTenWidthArea = getArea(10)
// 碰到宽度为10的长方形就可以这样计算面积
const area1 = getTenWidthArea(20)
const area2 = getTenWidthArea(30)
// 遇到宽度为20也可以轻松复用
const getTwentyWidthArea = getArea(20)
const area3 = getTwentyWidthArea(xx)
场景三(用闭包模拟私有方法)
在JS中,没有支持声明私有变量,但我们可以使用闭包来模拟私有方法
- 私有方法不仅仅有利于限制对代码的访问:还提供了管理全局命名空间的强大能力,避免非核心的方法弄乱了代码的公共接口部分。
- 下面的示例展现了如何使用闭包来定义公共函数,且其可以访问私有函数和变量。这个方式也称为 模块模式(module pattern):
var Counter = (function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
function increment() {
changeBy(1);
},
function decrement() {
changeBy(-1);
},
function value() {
return privateCounter;
}
}
})();
console.log(Counter.value()); /* logs 0 */
Counter.increment();
Counter.increment();
console.log(Counter.value()); /* logs 2 */
Counter.decrement();
console.log(Counter.value()); /* logs 1 */
这里有很多细节
- 在以往的示例中,每个闭包都有它自己的环境;而这次我们只创建了一个环境,为三个函数所共享:Counter.increment,Counter.decrement 和 Counter.value。
- 该共享环境创建于一个匿名函数体内,该函数一经定义立刻执行。环境中包含两个私有项:名为 privateCounter 的变量和名为 changeBy 的函数。 这两项都无法在匿名函数外部直接访问。必须通过匿名包装器返回的三个公共函数访问。
- 这三个公共函数是共享同一个环境的闭包。多亏 JavaScript 的词法范围的作用域,它们都可以访问 privateCounter 变量和 changeBy 函数。
您应该注意到了,我们定义了一个匿名函数用于创建计数器,然后直接调用该函数,并将返回值赋给 Counter 变量。也可以将这个函数保存到另一个变量中,以便创建多个计数器。
var makeCounter = function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
};
var Counter1 = makeCounter();
var Counter2 = makeCounter();
console.log(Counter1.value()); /* logs 0 */
Counter1.increment();
Counter1.increment();
console.log(Counter1.value()); /* logs 2 */
Counter1.decrement();
console.log(Counter1.value()); /* logs 1 */
console.log(Counter2.value()); /* logs 0 */
- 上述通过使用闭包来定义公共函数,并令其可以访问私有函数和变量,这种方式也叫模块方式
- 请注意两个计数器是如何维护它们各自的独立性的。每次调用 makeCounter() 函数期间,其环境是不同的,通过改变变量值,会改变这个闭包的词法环境,不会影响另一个闭包中的变量。每次调用中, privateCounter 中含有不同的实例。
- 这种形式的闭包提供了许多通常由面向对象编程所享有的益处,尤其是数据隐藏和封装。
其他场景(待补充)
例如计数器、延迟调用、回调等闭包的应用,其核心思想还是创建私有变量和延长变量的生命周期
注:如果不是某些特定任务需要使用闭包,在其它函数中创建函数是不明智的,因为闭包在处理速度和内存消耗方面对脚本性能具有负面影响
参考链接:
CSDN - 面试官:说说你对闭包的理解?闭包使用场景?
CSDN - JavaScript—用闭包模拟私有方法