一步一步重温 JavaScript 基础系列。
本章节将会理解JS的闭包、闭包为何会造成内存泄漏,以及闭包相关考题
闭包
概念
闭包,是指有权访问另一个函数作用域内的变量的函数。创建闭包的方式,常见如:在一个函数内部创建另外一个函数,并且将该函数作为返回值,同时该函数还引用了外部函数作用域内的变量。例如:
1 | function foo1 () { |
更广泛的含义
如果一个函数访问了此函数的父级或父级以上的作用域变量,该函数就可以称之为闭包。例如:
1 | var num = 1; |
闭包产生的原因
在函数作用域外无法读取函数作用域内的变量,但是借助JS作用域链,函数内部的函数可以访问外层函数的变量。根据这一特性,就可以在函数内部创建并返回一个函数,将函数内部和函数外部作用域连接起来,由此便产生了闭包。
闭包的用途
闭包可以让函数外部访问函数内局部作用域的变量,并且可以让该变量始终保存在内存中, 例如:1
2
3
4
5
6
7
8
9
10
11function fn () {
var num = 3;
function fn1 () {
alert(++num);
}
return fn1
}
var fn2 = fn();
fn2() // 4
fn2() // 5
根据这一特性,可以解决实际场景中遇到的以下问题:
引入的全局变量太多,极易产生全局变量命名冲突
例如:(Jquery)1
2
3;(function($){
// 内部实现
})(Jquery)在开发JS插件时,为了保护插件内部变量不被改变
例如二(防止私有变量被污染)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36const CHANGE_FONT_SIZE='CHANGE_FONT_SIZE';
// 设置一个闭包,把变量保护起来,通过返回值调用
function createStore() {
let appState={
fontSize: '26px',
color: 'red'
}
// 保护变量被修改,深克隆
let getter=()=>JSON.parse(JSON.stringify(appState));
// 改变变量的方法,action代表一个命令对象,就是一个普通的js对象,起码需要一个字段控制命令类型type
let setter=(action)=>{
switch(action.type){
case CHANGE_FONT_SIZE:
appState.fontSize = action.fontSize;
default:
return;
}
}
//返回出去的修改和取值的接口
return{
getter,
setter
}
}
let store=createStore();
// 取值函数
store.getter().fontSize;
// 修改函数
store.setter({type:CHANGE_FONT_SIZE, fontSize: '30px'});如果需要对重要参数防止被篡改,可使用闭包规定变量的getter和setter
以上示例参考,https://www.cnblogs.com/GoCircle/p/9804106.html例如三(利用闭包来完成对象的继承,做到函数的私有性和公开性):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46(function(){
// Person模块
var Person = {
// 数据部分
data: {
name: "",
age: ""
},
// 操作部分
opt:{
setName:function(name){
Person.data.name=name;
},
setAge:function(age){
Person.data.age=age;
},
getName:function(){
return Person.data.name;
},
getAge:function(){
return Person.data.age;
},
init:function(name,age){
Person.opt.setName(name);
Person.opt.setAge(age);
}
},
// 公开的API
openAPI:function(){
return {
init:Person.opt.init,
getName:Person.opt.getName,
getAge:Person.opt.getAge
};
}
};
window.Person = Person;
})()
var p = window.Person.openAPI();
p.init("wx", "20");
alert(p.getName());
alert(p.getAge());简单说一下jQuery的源码,其实整个jQuery其实就是一个自调函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28(function(window){
// jQuery是一个函数,一个对象,一个构造器函数
var jQuery = function( selector, context ) {
return new jQuery.fn.init( selector, context );
},
// 定义了jQuery的prototype的内容
// fn = prototype
// 加在jQuery的prototype上的方法可以通过选择器创建出来的对象调用
// 对象方法
jQuery.fn = jQuery.prototype = {
ready: function(){},
each: function(){}
},
// post方法和extend方法直接加在了jQuery对象上
// 全局方法
jQuery.post = function(){},
jQuery.extend = function(){}
// 程序员在用jQuery的时候,是window的一个属性
//jQuery = $
window.jQuery = window.$ = jQuery;
$.fn = jQuery.prototype = window.$.prototype = window.$.fn = window.jQuery.fn = window.jQuery.prototype
// 加在jQuery的prototype上的方法和加在jQuery对象上的方法称为jQuery的插件开发
})(window);
window.jQuery.fn=$.prototype=window.$.fn=jQuery.prototype=window.jQuery.prototype以上示例参考,https://blog.csdn.net/canot/article/details/50760804
闭包的副作用
- 内存泄漏
如上例fn,fn1作为fn的返回值,形成闭包。fn1被赋值给全局变量fn2,导致fn1始终存在内存中,而fn1引用了父函数fn内的变量num,因此num也会一直在内存中,无法被垃圾回收机制(GC)回收。
闭包相关考题
考点一:闭包函数调用时,this指向全局window
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26// 例①
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()()); // The Window
// 例②
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
alert(object.getNameFunc()()); // My Object题目引用自,http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html