闭包的概念
闭包就是能读取其他函数内部变量的函数。
也可以简单的理解为:“定义在一个函数内部的函数”
闭包的形式
//在执行fn1时,其返回值是一个函数,即fn2,所以再一次执行的时候就是返回的a+b的值。
function fn1(){
var a = 5;
return function fn2(b){
return a + b
}
}
var result = fn1();
alert(result(2)); //7
ES6中的闭包(经典闭包面试题)
//闭包常见的面试题
for (var i = 1; i <= 5; i++) {
setTimeout( function timer() {
console.log(i);
}, 1000 );
} //连续5个6
for (var i = 1; i <= 5; i++) {
(function(i){
setTimeout( function timer() {
console.log(i);
}, 1000 );
})(i);
} //1,2,3,4,5
for (let i = 1; i <= 5; i++) {
setTimeout( function timer() {
console.log(i);
}, 1000 );
} //1,2,3,4,5
闭包的优缺点
优点:
- 保护函数内的变量安全,加强了封装性
- 在内存中维持一个变量(用的太多就变成了缺点,占内存)
缺点:
- 闭包有一个非常严重的问题,那就是内存浪费问题,这个内存浪费不仅仅因为它常驻内存,更重要的是,对闭包的使用不当会造成无效内存的产生
闭包的作用
看了闭包与ES6块级作用域的比较之后,我们会不会觉得既然有了块级作用域就没有闭包存在的必要性了呢?答案是否定的,闭包的作用远不止于此。
单例模式(封装变量,收敛权限)
- 什么是单例模式?
用一个变量来标志当前是否已经为某个类创建过对象,如果是,则在下一次获取该类的实例时,直接返回之前创建的对象
2. 写一个单例模式?
//单例模式的实现
var CreateDiv = function (html) {
this.html = html;
this.init();
};
CreateDiv.prototype.init = function () {
var div = document.createElement('div');
div.innerHTML = this.html;
document.body.appendChild(div);
};
var ProxySingletonCreateDiv = (function () {
var instance;
return function (html) {
if (!instance) {
instance = new CreateDiv(html);
}
return instance;
}
})();
var a = new ProxySingletonCreateDiv('sven1');
var b = new ProxySingletonCreateDiv('sven2');
alert(a === b); //true
-
核心?
//单例模式抽象,分离创建对象的函数和判断对象是否已经创建 var getSingle = function (fn) { var result; return function () { return result || ( result = fn.apply(this, arguments) ); } };
函数柯里化
- 什么是函数柯里化?
柯里化是指这样一个函数(假设叫做createCurry),他接收函数A作为参数,运行后能够返回一个新的函数。并且这个新的函数能够处理函数A的剩余参数。 - 与闭包的关系?
在前端面试中,你可能会遇到这样一个涉及到柯里化的题目。
// 实现一个add方法,使计算结果能够满足如下预期:
add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;
要补充的知识点是函数的隐式转换。当我们直接将函数参与其他的计算时,函数会默认调用toString方法,直接将函数体转换为字符串参与计算。
function fn() { return 20 }
console.log(fn + 10); // 输出结果 function fn() { return 20 }10
但是我们可以重写函数的toString方法,让函数参与计算时,输出我们想要的结果。
function fn() { return 20; }
fn.toString = function() { return 30 }
console.log(fn + 10); // 40
除此之外,当我们重写函数的valueOf方法也能够改变函数的隐式转换结果。
function fn() { return 20; }
fn.valueOf = function() { return 60 }
console.log(fn + 10); // 70
当我们同时重写函数的toString方法与valueOf方法时,最终的结果会取valueOf方法的返回结果。
function fn() { return 20; }
fn.valueOf = function() { return 50 }
fn.toString = function() { return 30 }
console.log(fn + 10); // 60
下面是具体的实现方法:
//实现参数不确定下的相加
function add() {
// 第一次执行时,定义一个数组专门用来存储所有的参数
var _args = [].slice.call(arguments);
// 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
var adder = function () {
var _adder = function() {
// [].push.apply(_args, [].slice.call(arguments));
_args.push(...arguments);
return _adder;
};
// 利用隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
_adder.toString = function () {
return _args.reduce(function (a, b) {
return a + b;
});
}
return _adder;
}
// return adder.apply(null, _args);
return adder(..._args);
}
var a = add(1)(2)(3)(4); // f 10
var b = add(1, 2, 3, 4); // f 10
var c = add(1, 2)(3, 4); // f 10
var d = add(1, 2, 3)(4); // f 10
// 可以利用隐式转换的特性参与计算
console.log(a + 10); // 20
console.log(b + 20); // 30
console.log(c + 30); // 40
console.log(d + 40); // 50
// 也可以继续传入参数,得到的结果再次利用隐式转换参与计算
console.log(a(10) + 100); // 120
console.log(b(10) + 100); // 120
console.log(c(10) + 100); // 120
console.log(d(10) + 100); // 120
// 其实上栗中的add方法,就是下面这个函数的柯里化函数,只不过我们并没有使用通用式来转化,而是自己封装
function add(...args) {
return args.reduce((a, b) => a + b);
}
经典的作用域问题
只要牵涉到函数作用域的问题我们一般都要想到闭包。他的牵涉范围甚广,一道题往往牵涉作用域,闭包,变量提升,自由变量,this指向等等。
在全局函数中this等于window
当函数被当做某个对象的方法调用时,this等于那个对象
匿名函数的执行环境具有全局性,this通常指向window
闭包在实际开发中的运用?
在实际开发中,闭包主要是用来封装变量,收敛权限。
//封装变量,收敛权限
function isFirstLoad(){
var list=[];
return function(option){
if(list.indexOf(option)>=0){ //检测是否存在于现有数组中,有则说明已存在
console.log('已存在')
}else{
list.push(option);
console.log('首次传入'); //没有则返回true,并把这次的数据录入进去
}
}
}
var ifl=isFirstLoad();
ifl("zhangsan");
ifl("lisi");
ifl("zhangsan");