「JavaScript面向对象编程指南」闭包

时间:2023-03-08 21:57:56

闭包

「JavaScript面向对象编程指南」闭包

JS只有函数作用域,函数外为全局变量,函数内为局部变量

绿圆是函数fn的作用域,在这范围内可访问局部变量b和全局变量a,橙圆是fn内部函数inner的作用域,此范围内可访问自身作用域内的变量c,也可访问父级作用域的变量b,这就形成了一条作用域链

全局空间(蓝圆)为0级作用域,绿圆是1级作用域,橙圆为2级作用域,依次类推

橙圈可访问绿圈或蓝圈内的数据,但蓝圈内访问不了绿圈,绿圈访问不了橙圈,也就是可以一路由内向外访问,但由外到内却不行

若把橙圈拿到绿圈外,蓝圈内的位置,就形成了闭包

「JavaScript面向对象编程指南」闭包

现在橙圈也就是函数inner和变量a一样,都是在全局作用域中,并且inner还记得它被定义时所设定的环境,因此它依旧可访问绿圈也就是函数fn的作用域并使用变量b

突破作用域链的途径,升级为全局变量传递(返回)给全局作用域即可

闭包#1

var a = 'global var';
var fn = function () {
var b = 'local var';
var inner = function () {
var c = 'inner';
return b;
}
return inner;
}
var f2 = fn(); //或者直接fn()();
f2();

函数fn中返回了inner,而inner内返回的变量b,现在inner和b都可通过作用域链进行访问

闭包#2

var f2; //函数占位符,这不是必须的,但最好写上
var fn = function () {
var b = 'local var';
var inner = function () {
var c = 'inner';
return b;
}
f2 = inner;
}
fn();
f2();

将函数inner在fn内赋值给全局变量f2,由于inner是在fn内定义的,所以即使该函数后来升级成了全局函数,也依然保留着对fn作用域的访问权

闭包#3

function fn(param) {
var inner = function () {
return param;
}
param++;
return inner;
}
var f2 = fn(123);
f2();

返回函数被调用时,param++已执行一次,所以f2()结果是124

由此可见,函数绑定的是作用域本身,而不是函数定义时该作用域中的变量或变量当前返回的值

闭包#4循环中的闭包

依次输出0,1,2

function fn() {
var arr = [],i;
for(i = 0; i < 3; i++){
arr[i] = function () {
return i;
}
}
return arr;
}
var arr2 = fn();
> arr2[0](); //3 , >表示在控制台输入
> arr2[1](); //3
> arr2[2](); //3

这里创建了三个闭包,都指向一个共同的局部变量i,但闭包不会记录它们的值,它们拥有的只是相关域在创建时的一个引用

对这三个函数任一个而言,当它去获取某个变量时,会从其所在的域开始逐级寻找那个距离最近的i值。由于循环结束时i为3,所以这三个函数都指向了这一共同值

若想输出的是0,1,2,则需要换种闭包形式

function fn() {
var arr = [],i;
for(i = 0; i < 3; i++){
arr[i] = (function (x) {
return function () {
return x;
};
}(i));
}
return arr;
}
var arr2 = fn();
> arr2[0](); //0
> arr2[1](); //1
> arr2[2](); //2

这里不再直接创建一个返回i的函数,而是将i传递给另一个自调用函数,在该函数中

i被赋值给了局部变量x,这样一来,每次迭代中i的值就能保存在局部变量x中

也可以使用函数声明的方式

function fn() {
var arr = [],i;
for(i = 0; i < 3; i++){
arr[i] = binder(i);
}
return arr;
function binder(x) {
return function () {
return x;
}
}
}
var arr2 = fn();
arr2[0](); //0
arr2[1](); //1
arr2[2](); //2

闭包#5

假设有个变量,表示的是某类特定值,不想将其暴露给外部,因为那样的话其他部分的代码就可能直接修改它,所以需要将其保护在相关函数内部,然后提供getter和setter函数用以访问和设置该变量的值

var getValue,setValue;
(function () {
var secret = 0;
getValue = function(){
return secret;
};
setValue = function(v){
if(typeof v === 'number'){ //假设这里有验证措施
secret = v;
}
};
}())
> getValue(); //0
> setValue(123);
> getValue(); //123

将getter和setter函数放在一个共同的函数中,并在该函数中定义secret变量,使这两函数能共享同一作用域

通过一个自调用函数,在其中定义了全局函数setValue和getValue函数,并以此确保局部变量secret的不可直接访问性

闭包#6迭代器

有时会面对复杂的数据结构,它们通常有着与数组截然不同的序列规则,此时就需要将一些“谁是下一个”的复杂逻辑封装成易于使用的next()函数,然后只需简单地调用next()就能实现相关的遍历操作

function setup(x) {
var i = 0;
return function () {
return x[i++];
}
}
var next = setup(['a','b','c']);
> next(); // “a”
> next(); // "b"

在setup()函数中定义一个私有指针i,该指针会始终指向数组的下一个元素