5 js面向对象基础 - 闭包的概念及应用
闭包的概念
注意:预解析,变量声明,词法作用域,作用域链等知识的理解,对闭包的彻底理解起重要的作用。没有基础的建议先看上一篇 4 js面向对象基础 - 预解析,词法作用域,作用域链
字面意义:
- 闭 : 关闭,封闭
包 : 包裹, 打包
闭包的含义就是一个被包裹的隔离的空间
在 js 中, 什么是闭包 ?
- 在 js 中函数是一个具有变量作用域隔离特性的一个内存结构, 函数的内部内容外部无法访问,是一个被封闭的内容,即为一个闭包。
学习闭包, 在 js 中到底要解决什么问题
在 js 中闭包要解决的问题就是间接的访问到这个被隔离的数据.
function foo () {
var num = 123;
return num; // return 只是进行了 拷贝,外界还是没有真正的访问 num
}
var num = foo();
在外界想访问到 num 中的数据. 怎么做? =》 我们可以使用闭包的间接访问
闭包的间接访问
使用 return 数据不能直接访问原来的数据, 那么可以考虑利用函数的返回访问原始数据
function foo() {
var num = Math.random(); // 建随机数
function fuc() {
return num; /* 原始数据 num */
}
return fuc; // 把函数返回
}
var fn = foo(); // 获取函数引用, 返回的函数功能是 获取 foo函数中 变量 num
var num1 = fn();
console.log( num1 ); // 0.4612285847198856
var num2 = fn();
console.log( num2 ); // 0.4612285847198856
fn 中存储的是 foo 里面定义的函数的 引用,可以使用 fn 来调用,根据词法作用域,返回了 foo 中的 num,间接的访问了闭包中的数据
闭包的应用
闭包的应用-利用闭包实现私有数据
function foo(){
var num1 = 123, num2 = 456;
return {
get_num1: function() {
return num1;
},
set_num1: function( value ) {
num1 = value;
},
get_num2: function() {
return num2;
}
};
}
var o = foo();
console.log( o.get_num1() ); // 获取 num1 123
o.set_num1(789); // 修改 num1
console.log( o.get_num1() ); // 再次获取 num1 789
console.log( o.get_num2() ); // 获取 num2 456
从上可以看到外部对 num1 可读可写,对 num2 可读不可写
函数允许返回一个对象, 那么该对象可以提供数据访问方法,但是数据存储在闭包中, 来达到私有的目的
function createPerson ( name, age, gender ) {
var hasChangeGender = false;
return {
get_Name: function () {
return name;
}, set_Name: function ( value ) {
name = value;
}, get_Age: function () {
return age;
}, get_Gender: function () {
return gender;
}, set_Gender: function ( value ) {
if ( hasChangeGender == false ) {
gender = value;
hasChangeGender = true;
} else {
throw new Error( '已经改变过一次性别了, 不能再修改了' );
}
}
};
}
var p1 = createPerson( '张三', 19, '男' );
console.log( 'p1.name = ' + p1.get_Name() ); // 张三
console.log( 'p1.age = ' + p1.get_Age() ); // 19
console.log( 'p1.gender = ' + p1.get_Gender() ); // 男
p1.set_Name( '王二' );
p1.set_Gender( '女' );
console.log( 'p1.name = ' + p1.get_Name() ); // 王二
console.log( 'p1.age = ' + p1.get_Age() ); // 19
console.log( 'p1.gender = ' + p1.get_Gender() ); // 女
p1.set_Name( '王三' );
p1.set_Gender( '男' ); // Uncaught Error: 已经改变过一次性别了, 不能再修改了
总结:闭包实现各种特性, 其根本的核心内容只有两个
- 带有私有数据的 函数
function foo () {
var num = 123;
return function () {
// 可以访问 num
}
}
var func = foo();
// 称 func 是一个 带有私有数据的 函数
// 称 func 带有缓存
**称 func 是一个 带有私有数据的 函数**
**称 func 带有缓存**
- 带有私有数据的 对象
闭包的应用-沙箱模式
沙箱就是一个隔离的执行环境。js 中沙箱就是一个自调用函数,写在这个函数中变量,不会被外面的内容所影响,这样的话,使得我们的数据更加的安全,代码也能正常使用和运行。
在 js 中 什么情况需要使用沙箱?
function Person() {....}
var p = Person. ...
Person.prototype = ...
...
定义变量越多, 会怎样? -> 出现冲突的可能性越大
而有时代码中 为了使得代码更加简洁, 会引入很多变量, 我们通过沙箱就可以解决这样的问题。
(function () {
// 沙箱模式
// 所有的代码写在这里
})();
闭包的应用-模拟onload事件的追加和移除
onload 事件的追加 addEvent
/* 新建 jepson 对象来接收 沙箱返回的对象 */
var jepson = ( function() {
/* 新建 私有变量 */
var arr = [];
/* window onload 以后,遍历执行 arr数组中的全部方法 */
window.onload = function() {
for( var i = 0; i < arr.length; i++ ) {
if( typeof arr[ i ] == "function" ) arr[ i ]();
}
};
/* 返回对象,对象中存放 addEvent方法,用以追加 onload执行事件 */
return {
addEvent: function( fn ) {
arr.push( fn );
}
}
})();
jepson.addEvent( function() {
console.log( " 我被追加载 onload 执行了 1 " );
});
onload 事件的移除 removeEvent
/* 新建 jepson 对象来接收 沙箱返回的对象 */
var jepson = ( function() {
/* 新建 私有变量 */
var arr = [];
window.onload = function() {
for( var i = 0; i < arr.length; i++ ) {
if( typeof arr[ i ] == "function" ) arr[ i ]();
}
};
return {
addEvent: function( fn ) { /* 追加 */
arr.push( fn );
},
removeEvent: function( fn ) { /* 移除 */
// 遍历 arr, 发现相同的就删除
for( var i = 0; i < arr.length; i++ ) {
if ( fn == arr[ i ] ) arr.splice( i, 1 )
}
}
}
})();
/* 要删除必须传引用,所以用变量存一下引用 */
var f1 = function() {
console.log( " 我被追加载 onload 执行了 1 " );
};
var f2 = function() {
console.log( " 我被追加载 onload 执行了 2 " );
};
var f3 = function() {
console.log( " 我被追加载 onload 执行了 3 " );
};
jepson.addEvent( f1 );
jepson.addEvent( f2 );
jepson.addEvent( f3 );
jepson.removeEvent( f1 ); // f1 被移除了
这里只会输出,f1已被移除
我被追加载 onload 执行了 2
我被追加载 onload 执行了 3
补充: setInterval函数调用时,也尽量用变量保存引用,不然每隔一秒,都会新建一个不同的函数来调用,大大的占用了内存,消耗了性能。
var f1 = function() { ... };
setInterval( f1, 1000 );
闭包的应用-模拟一个缓存结构
cache 对象, 可以使用 cache[ key ] = value 存储数据, cache[ key ] 获得数据
当 cache 里面的数据达到 1024 条, 将最早放进去的数据移除。
而模拟时, cache = {} 可以存取数据, 但是不能限定数据的长度
我们需要限定数据,就是在加入数据的时候判断,是否已经超过尺寸,来决定是否移除
可以将 cache 做成函数,那么添加数据时,使用 cache( key, value ),而且函数本身也是对象
function cache ( key, value ) {
// 可以在这里加上限定长度的代码
cache[ key ] = value;
}
cache( 'attr', function ( node, attr ) {
return node.getAttribute( 'name' ) == attr;
} );
cache( '__name__', '张三' );
由于需要记录键的数量. 并且需要记录添加数据的先后顺序. 所有首先考虑有序的数组.因此需要让 cache 函数带有缓存功能
搭建结构
var cache = (function () { var data = []; // 新建 data数组,用来保存 键 function cache ( key, value ) { // 准备做判断, 如果超出范围, 则, 将最开始加入的 移除 cache[ key ] = value; } return cache; })(); cache( 'age', 20 ); // 给 cache 添加 age 属性,值为 20
实现
var cache = (function () { var data = [], max = 3; function cache ( key, value ) { // 做判断, 如果超出范围, 则, 将最开始加入的 移除 if ( data.length >= 3 ) { // 需要先移除 var temp = data.shift(); // 移除第一项并获取 key delete cache[ temp ]; // 删除 cache对象中的 temp 属性 } data.push( key ); console.log( data ); cache[ key ] = value; } return cache; })(); cache( 'name1', '张三' ); cache( 'name2', '李四' ); cache( 'name3', '王五' ); cache( 'name4', '找钱' );