js中的闭包
首先,js闭包对于哪怕是有很多年前端开发经验的人,也是很晦涩难懂的东西.
所以,我不敢保证能把你说明白,但是如果你有兴趣可以接着看下去.
再说之前,咱们先探讨一个现象.就是闭包在前端开发面试过程中的出现率几乎是100%,很多同学一定会纳闷这是为啥.
要想明白这个,我们可以换个角度去思考,那就是如果你是一个公司的前端开发,此时你的上司交派给你一个任务,去面试即将到来的一大波前端面试者.你该如何去面?
此时的你,一定迫切的想要用最少的题目,去考量一个前端面试者尽可能多的技术能力.因为这样会节省很多时间和精力.还能达到考核目的.
那么什么是前端开发的核心呢?
javaScript
javaScript的核心又是什么呢?
函数
为什么我说javaScript的核心是函数呢?
我们都知道js是一门面向过程的语言,什么是面向过程呢?面向过程就是基于很多函数功能模块的执行来实现各种各样功能的概念
函数的核心又是什么?
1.作用域
2.作用域链
3.活动对象
4.执行环境
5.浏览器的垃圾回收机制
这5个东西并不是孤立存在的,他们之间有着很强的依赖关系.
先来说说作用域,提到作用域就必须提到另外一个东西,那就是变量,我前面的文章已经具体说了什么是变量,在这我只概括一下,作用域就是变量存储的规则,变量是以作用域为单位进行存储的.作用域可以相互嵌套,但是不可以重叠
作用域链,是变量被访问的规则,作用域链的访问是单向的,只能从下向上访问,不能从上向下访问.这也就是为什么外部作用域不能访问内部作用域的变量,内部作用域可以访问外部作用域的变量.
作用域链产生的前提条件是作用域,没有作用域就没有作用域链.
作用域链这个名字很形象,我们知道栓狗用的铁链吧,铁链是由一个个铁扣组成,一条铁链我们可以拆卸下一节,也可以增加一节,每一个铁扣就是我们即将要说到活动对象,每当函数执行前,都会产生一个活动对象,这个活动对象(铁扣)会与外部作用域的活动对象(铁扣)进行对接,此时的内部作用域就包含了2个活动对象,所以变量被访问实际上是依靠作用域链上的活动对象实现的,内部作用域当函数执行完毕就会销毁,同时销毁的还有此作用域生成的活动对象,这也是造成了外部作用域不能访问内部作用域变量的原因
活动对象(也称之为变量对象)是在函数执行之前函数初始化的过程中产生的一个对象,这个对象包含了函数中的所有变量,这个所有变量包括:
Ⅰ.用var声明的变量
Ⅱ.声明的函数
Ⅲ.arguments对象(也就是形参)
执行环境,就是当执行流(执行流就是代码执行的动作过程)进入到一个新的作用域(函数)时产生的一个新的环境,这个环境中同时会产生一个执行对象和一个this,this指向的就是新生成的执行对象,而不是当前执行环境,换句话说,执行环境只是一个概念,执行对象是这个概念的载体,js解释器需要知道当前的执行任务到底是属于谁的,因为它要确定这个函数到底是为谁服务.
this与执行对象是成对出现的
<script>
function fn(){
function sonFn(){
console.log(this)
}
var o ={
fn:function(){
console.log(this)
}
}
var g = o.fn;
console.log(this);
sonFn();
o.fn();
g();
}
fn()
</script>
注意:执行环境分为
Ⅰ.全局执行环境,它是基于Global对象的
Ⅱ.局部执行环境,他是基于函数的.
垃圾回收机制,在C语言中,垃圾回收是要由程序员自己来手动完成的,而例如java,jiavascript之流的语言,是不需要程序员来操心这些事的.
简单来说,垃圾回收就如同我们平时把果皮纸屑等这些对于你来说没有使用意义的东西丢进垃圾箱一样,而浏览器的垃圾回收机制相当于你在家里请了一个保姆,你只需要制造垃圾,而把垃圾扔到垃圾箱的事是由这个保姆来完成.
垃圾回收机制与变量也是密不可分的,在变量一章已经说过,这里不再阐述
巴拉巴拉说了这么多,完全是为闭包做铺垫
什么是闭包
很多人在看关于闭包网络文章很容易乱,原因就是解释的人其实在定义闭包的概念上就有分歧,而造成的这种分歧的主要原因就是各类JS权威书籍上对于闭包的解释不尽相同
两本流传的javascript神书
《JavaScript权威指南》对于闭包的解释是:
闭包是指在函数声明时的作用域以外的地方被调用的函数
<script>
function fn(){
var i = 0;
return function(){
console.log(++i)
}
}
var a = fn();
a();
</script>
概括为三点:
Ⅰ.访问所在作用域;
Ⅱ.函数嵌套;
Ⅲ.在所在作用域外被调用;
《JavaScript高级程序设计》对于闭包的解释是:
闭包是指有权访问另一个函数作用域中的变量的函数
<script>
function fn(){
var i = 0;
function fn2(){
console.log(++i)
}
fn2();
}
fn();
</script>
概括为二点:
Ⅰ.访问所在作用域;
Ⅱ.函数嵌套;
两本书之所以会定义有差异,其实还要从两本书的作者写书的出发点考虑
《JavaScript权威指南》与读起来枯燥有难度,需要有一定的编程思想,不太适合新手阅读,作者针对的人群是致力于深度钻研javascript的开发人员.
《JavaScript高级程序设计》只能说相比于《JavaScript权威指南》难度有所降低,比较亲民,适合新手阅读,作者针对的人群是没有什么编程基础或没有计算机基础的javascript的开发人员或小白.
在《JavaScript高级程序设计》的序言中,有这么一段话让我印象深刻,
与那些把大量篇幅花在讲解背景知识上的书,以及那些让人感觉好像是要使用 JavaScript 开发导弹制导系统的书相比,这本书让人感觉细致周到、亲切自然。
这段序言调侃的对象就包括了《JavaScript权威指南》,不好意思,我无耻的笑了
虽然我无比喜欢《JavaScript高级程序设计》,但是我们还要以《JavaScript权威指南》的闭包定义开展,因为这是面试官想要的,也是我们应该追求的.(前提是你不想转行又想拿高薪.)
我们系统阐述一下这段代码的整个过程:
<script>
function fn(){
var i = 0;
return function(){
var b = 55;
console.log(++i)
}
}
var a = fn();
a();
</script>
当浏览器中js解释器读到js代码的时候,他首先会创建一个全局的执行环境,这个全局执行环境包含了一个活动对象,这个活动对象包含了所有的全局变量(我们一定要记住函数本身是变量),然后在词法分析阶段,会为每一个全局函数导入这个全局活动对象,这也就造成了全局函数可以再整个全局环境的任何地方进行调用,全局执行环境的生命周期是在打开网页时到关闭网页时.
此时的fn函数包含了一个全局活动对象
在函数执行时,又会执行一遍此法语法分析,全局活动对象会与fn执行环境的活动对象进行对接,使得内部函数拥有两个活动对象,全局的,和自身的
此时的 return function(){console.log(++i)}拥有全局活动对象fn函数,和fn函数内部活动对象 i的访问权,也就是 a函数所拥有的的访问权限
在函数a执行时,他就拥有了三个活动对象
Ⅰ.全局活动对象;
Ⅱ.fn执行环境的活动对象;
Ⅲ.当前执行环境的活动对象;
这一实现阻断了垃圾回收机制,因为并没有明确的指向证明fn函数已经执行完毕,这就造成这三个活动对象一直保存在内存中而无法释放,而这也就造成了另外一个问题,内存泄漏
一般情况下,普通的开发不会涉及太多的闭包,如果你大量的使用了闭包,那么就需要手动清理一下,清理的方法也非常简单
var a = null;
闭包的应用
1.生成不重复的id
<script>
var a = (function(){
var a = 'a',b = 0;
return function(){
return a + b++;
}
})()
var b = a();
var c = a();
console.log(b,c)
</script>
2.生成排序规则函数的函数
<script>
var json = [{age:55},{age:14},{age:78},{age:66}];
function createComparisonFunction(propertyName) {
return function(object1, object2){
var value1 = object1[propertyName];
var value2 = object2[propertyName];
return value1 - value2;
};
}
json.sort(createComparisonFunction('age'));
console.log(json)
</script>
3.在循环中为dom绑定不同函数
<body>
<button>按钮1</button>
<button>按钮2</button>
<button>按钮3</button>
<script>
var list = document.getElementsByTagName('button');
for(var i =0;i<list.length;i++){
list[i].onclick = (function(i){
return function(){
console.log(i)
}
})(i)
}
</script>
</body>
当然也可以使用ES6的let来解决
for(let i =0;i<list.length;i++){
list[i].onclick = function(){
console.log(i)
}
}
4.创建单例模式
<script>
var Person = function(){
var obj = null;
return function(name, age){
if(obj !== null){
return obj
}
obj = this;
this.name = name;
this.age = age;
}
}();
var a = new Person('张三', 56); //{name: '张三',age: 56}
var b = new Person('李四', 25); //{name: '张三',age: 56}
</script>
这次明白面试官为什么要问闭包了吗?
如果你还是简单的回答:
"闭包就是外部作用域有权访问内部作用域的变量"的话,面试官会青睐于你吗?
此篇文章不定期更新