闭包的概念
闭包就是当一个函数即使是在它的词法作用域之外被调用时,也可以记住并访问它的词法作用域。
闭包是依赖于词法作用域编写代码而产生的结果
闭包和匿名函数的区别
【包含闭包的一些特性】
- 闭包是有权访问另一个函数作用域中变量的函数,常见的创建方式,就是在函数内部创建另一个函数;
- 立即执行函数是用一对圆括号包起来的函数,多用在模拟块级作用域,防止变量污染环境,或者是防止内存泄露,以为执行完之后,立即执行函数的执行环境都被销毁了。
- 立即执行函数的作用域链不会在外层函数之外保留,而闭包会。
- 只能取得外部函数的任何变量的最后一个有效值
- 相同点,执行环境具有全局性,this对象通常指向window,但是可以通过call、apply进行改变
// 闭包
// 此处将【var 改成let】 可以实现匿名函数的效果
function createFn() {
var res = new Array();
for(var i=0; i<5; i++) {
res[i] = function() {
console.log(this)
return i;
}
}
return res;
}
createFn()[0](); // 输出5
createFn()[1](); // 输出5
// 而用匿名函数的方式
function createFn() {
var res = new Array();
for(var i=0; i<5; i++) {
res[i] = (function(num){
return function() {
return num
}
})(i)
}
return res;
}
createFn()[0](); // 0
createFn()[1](); // 1
createFn()[2](); // 2
createFn()[3](); // 3
createFn()[4](); // 4
createFn()[5](); // undefined
闭包中的this对象
==this对象是基于函数的执行环境绑定的==,在全局函数中,通常指向window,而当函数被作为某个对象的方法调用时,会指向这个对象;然而匿名函数的this对象,通常指向window,这里的window函数包含闭包。
每个函数在调用时,都会自动获取两个特殊变量,arguments和this,内部函数在搜索这两个变量时,只搜索到内部函数自己的活动对象为止,因此永远不可能直接访问外部函数中的这两个变量。但是如果把外部作用域中的this保存在一个闭包能访问到的变量里,那么在闭包函数中,就可以访问外部函数的的this了。
所以闭包还有一个特性,就是可以访问外部函数的this和argument。
function fun(n,o) {
console.log(o)
return {
fun:function(m){
return fun(m,n);
}
};
}
var a = fun(0); a.fun(1); a.fun(2);a.fun(3); // 分别输出 undefined,0,0,0
var b = fun(0).fun(1).fun(2).fun(3); // 分别输出undefined,0,1,2,
var c = fun(0).fun(1); c.fun(2); c.fun(3); // 分别输出 undefined,0,1,1
关于this的指向
var name = 'Window';
var obj = {
name: 'wangxiaoer',
getName: function() {
return function() {
console.log(this.name);
}
}
}
console.log(obj.getName()()); // 输出'Window'
------------------------------------------------
var name = 'Window';
var obj = {
name: 'wangxiaoer',
getName: function() {
var _this = this;
console.log(this)
return function() {
console.log(_this.name);
}
}
}
console.log(obj.getName()()); // 输出'wangxiaoer',this指向obj,将this赋值给_this,然后在闭包中使用;
关于内存泄露
==当某个函数被调用时,会创建一个执行环境和响应作用域链,使用arguments和其他命名参数的值来初始化函数的活动对象。但在作用域链中,外部函数的活动对象始终位于第二位,外部函数的外部函数的活动对象处于第三位,知道作用域链的终点为全局执行环境。在执行环境中,为读取和写入变量的值,就需要在作用链中查找。==
==一般来说,当函数执行完毕时,局部活动对象就会被销毁,内存中仅保存全局执行环境中的活动对象,but, 闭包是个例外。因为闭包在被执行时,里面还保存着外层函数的活动对象,因此会比其他函数占用更多的内存。==
即外层函数已经销毁后,它的活动对象还存在与内存中,因为被闭包引用。
解决方法:内部函数解除对外部函数活动对象的引用
function outFn(args) {
return function() {
console.log(args);
return args
}
}
var createV = outFn(1);
createV();
createV = null;
p.s.函数的作用域链(理解函数的作用域对象对理解闭包有帮助)
- 1.创建函数outerFun()时,会创建一个预先包含全局变量对象的作用域链,保存在内部的[[Scope]]属性中。
- 2.调用函数outerFun()时,为此函数创建一个执行环境。
- 3.然后复制函数的[[Scope]]属性中的对象构建起执行环境的作用域链。
- 4.此后,创建一个活动对象,推入执行环境作用域链的前端([0]位置)。
- 此时执行环境的作用域链中包含两个变量对象:全局变量对象(第3步) 、 局部活动对象(第4步)。
- 函数中访问一个变量时,就会从作用域链中搜索具有相应名字的变量。
- 函数执行完后,局部活动对象被销毁,内存中仅保存全局作用域。
闭包的使用
实现函数节流
let reduce = (func, delay) => {
let timer = null;
return function(...args) {
if(timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
console.log(this);
func.apply(this, args);
}, delay)
}
}
let scrollFn = reduce(()=>{
console.log('节流啦啦啦')
// console.log(this);
}, 1000)
console.log(scrollFn);
document.addEventListener('scroll', scrollFn ,true);
实现单例模式
function CreateDiv(html) {
this.html = html;
this.init();
}
CreateDiv.prototype.init = function() {
let div = document.createElement('div');
div.innerHTML = this.html;
document.body.appendChild(div);
}
let singleton = (function() {
let instance;
return function(html) {
if(!instance) {
instance = new CreateDiv(html);
}
return instance;
}
})()
let s1 = new singleton('single1');
let s2 = new singleton('single2');
实现模块封装
var foo = (function () {
var something = 'cool';
var other = [1, 2, 3];
function getSomething() {
console.log(something);
}
function getOther() {
console.log(other)
}
return {
getSomething: getSomething,
getOther: getOther
}
})()
foo.getSomething(); // cool;
foo.getOther(); // 1,2,3
网友评论