闭包作用域
创建函数
- 开辟一个堆内存
- 把函数体中的代码当做字符串存储进去
- 把堆内存的地址赋值给函数名/变量名
- 函数在哪创建的,那么它执行时所需要查找的上级作用域就是谁
函数执行
- 形成一个全新的私有作用域、执行上下文、私有栈内存(执行一次形成一个,同个函数多个之间也不会产生影响)
- 形参赋值 & 变量提升
- 代码执行(把所属堆内存中的代码字符串拿出来一行行执行)
- 遇到一个变量,首先看它是否为私有变量(形参和在私有作用域中声明的变量是私有变量),是私有的就操作自己的变量即可,不是私有的,则向上级作用域查找……一直找到全局作用域为止 ==>作用域链查找机制
- 私有变量和外界的变量没有必然关系,可以理解为被私有栈内存保护起来了,这种机制其实就是闭包的保护机制
- 关于堆栈内存释放问题
函数执行就会形成栈内存(从内存中分配的一块空间),如果内存都不销毁释放,很容易就会导致内存溢出。堆栈内存的释放问题是学习JS的核心之一。
堆内存释放问题
// =>创建一个引用类型值,就会产生一个堆内存
// 如果当前创建的堆内存不被其他东西所占用了(浏览器会在空闲的时候,查找每一个内存的引用状况,不被占用的都会给回收释放掉),则会释放。
let obj = {
name : 'test'
};
let oop = obj;
// 此时obj和oop都占用着对象的堆内存,如果要释放堆内存,需要手动解除变量和值的关联
obj = null;
oop = 1;
栈内存释放
// => 打开浏览器形成的全局作用域是栈内存
// => 手动执行函数形成的私有作用域是栈内存
// => 基于ES5中的let/const形成的块作用域也是栈内存
// => ...
/*
* 全局栈内存:关掉页面的时候才会销毁
* 私有栈内存:
* 1.一般情况下,函数执行完成,形成的私有栈内存就会被销毁掉(排除出现无限级递归、出现死循环的模式)
* 2.但是一旦栈内存中的某个东西(一般都是堆地址)被私有作用域以外的事物给占用了,则当前栈内存不能立即被释放销毁(特点:私有作用域中的私有变量等信息也保留下来了) =>市面上认为的闭包:函数执行形成不能被释放的私有栈内存,这样的才是闭包
*
*/
function fn() {
// ....
}
fn(); // => 函数执行形成栈内存,执行完成栈内存销毁
function X() {
return function(){
// ....
}
}
let f = X(); // =>f栈用了x执行形成的栈内存中的一个东西(返回小函数对应的堆),则X执行形成的栈内存不能被释放了
- 闭包的两大作用
从性能角度讲,我们真实项目中应该减少对闭包的使用(因为闭包会产生不释放的栈内存,过多使用容易导致内存溢出或者降低性能)
- 保护(私有变量和外界没有必然联系)
- 保存(形成不销毁的内存,里面的私有变量等信息保存下来了)
JQuery提供了大量的方法供开发人员使用,如果这些方法不保护起来,用户编写的方法很容易和JQuery方法名字相同产生冲突,即全局变量污染。为了防止全局变量污染,JQuery中的方法和变量需要用闭包保护起来。
(function(global, factory){
//...
// typeof window !== 'undefined' ? window : this,验证当前所处环境的全局对象是window还是global等
factory(global); // =>lichao(window);
})(typeof window !== 'undefined' ? window : this, function lichao(window, noGlobal){
//...
var jQuery=function(selector, context) {
// ...
};
// =>通过给全局对象增加属性:jQuery和$,把私有的jQuery方法暴露到全局作用域下,供外面使用(等价于 return jQuery)。
// 外界需要使用函数中私有变量,可以基于window.xxx和return xxx两种方式实现这个需求
window.jQuery = window.$ = jQuery;
});
在真实项目中,我们一般都要把自己写的内容放到一个闭包中,这样可以有效方式自己代码和别人的代码产生冲突(全局变量污染:真实项目中,要尽可能减少对全局变量的使用);如果需要把自己的东西给别人用,基于return和window.xxx等方式暴露给别人即可。
//.....A写的代码
var xxx = (function() {
return XXX;
})();
//......B写的代码
(function(){
window.xxx=xxx;
})();
//=>JQuery
$(function(){
//...这样写在某些角度上也是为了减少全局的变量
});