对JS闭包的理解
1.什么是闭包
闭包,简单来说就是外部函数可以访问内部函数的变量,我们知道由于JS作用域的原因,内部函数通过作用域链依次来访问外层作用域所定义的变量,但是内部函数定义的变量,可以说是函数私有的变量,不允许外层函数访问,那要访问内部函数变量,就要通过闭包了
例如:
let a = "123";
function fun(){
let b = "234"
console.log(a,b) //123,234
}
console.log(a) //123
console.log(b) //b is not defined
上述代码中,变量a定义在fun函数外部,它的作用域为全局作用域,变量b定义在函数内部,作用域为fun,在fun函数中访问a,b都可以访问到,但在fun函数外部来访问b变量,控制台就会报错,因为b并非定义在全局环境中。
在某些情况下,我们需要去访问内部函数的变量,我们将上述函数做个变形:
let a = "123"
function fun(){
let b = "234";
function fn(){
console.log(a,b)
}
return fn
}
let result = fun();
result() // 123,234
在上述函数中,我们在函数fun内部定义了一个函数fn,对于fn来说,他的作用域就是fnn函数,所以它可以获取到fnn函数内部所有的变量,既然fn可以获取到函数内部的变量,那将fn函数作为返回值,那函数外部就可以获取到函数内部的变量了。所以说fn函数可以看作是一个闭包,它作为了连接fnn函数内部变量和外层函数的一个桥梁。所以,本质上闭包就是一个函数
2.关于闭包的使用
2.1.计数器
一般我们要实现一个计数可能会通过以下方式来实现:
let count = 0;
function add(){
return ++count;
}
console.log(add()) //1
console.log(add()) //2
通过以上方式我们是可以实现计数,但是count变量是定义在全局环境中的,这就意味着它随时可能被修改,在开发中我们往往不希望定义的变量修改。因此count应该是一个局部变量,定义在函数内部。
可通过以下方式来实现:
function add(){
let count = 0;
function fn(){
return ++count;
}
return fn;
}
var sum = add();
sum();
sum();
将计数的功能放在函数内部,将它作为add的返回值,外层函数在访问add函数时,也就可以获取到计数的变量,那么,fn函数就是一个闭包。
2.2.私有属性和方法
我们可以通过闭包来封装一个函数的私有属性和方法,例如:对于一个产品,我设置他的单价和数量,想知道这个产品的总价:
function Pruduct(name){
var price;
var total;
function setPrice(p){
price = p;
}
function setTotal(t){
total = t;
}
function getTotalPrice(){
return price*total;
}
return {
name:name,
setPrice:setPrice,
setTotal:setTotal,
getTotalPrice:getTotalPrice
}
}
let p = Pruduct();
p.setPrice(10);
p.setTotal(10);
console.log(p.getTotalPrice()); //100
p=null;
函数Product的变量price和total,通过setPrice,setTotal,getTotalPrice变成p对象的私有变量
2.3.循环调用
在开发中我们常见的一个问题就是循环调用的问题
例如:
function foo(){
var arr = [];
for(var i=0;i<10;i++){
arr[i] = function(){
return i
};
}
return arr
}
var result = foo();
console.log(result[0]()); //10
console.log(result[1]()); //10
console.log(result[2]()); //10
上述结果其实并不是我们想要的结果,由于for没有作用域,所以,相当于i变量在foo函数中定义,每次在执行完循环后,i会被改变,在调用函数的时候最终是获得的循环执行完成之后的变量
使用闭包实现循环调用:
function foo(){
var arr = [];
for(var i=0;i<10;i++){
arr[i] = (function(n){
return function(){
return n
}
})(i)
}
return arr
}
var result = foo();
console.log(result[0]()); //0
console.log(result[1]()); //1
console.log(result[2]()); //2
result = null
arr[i]赋值时形成独立的函数作用域,外部的i变化,对于函数内部的变量不影响
3.闭包的几种表现形式
3.1.返回值 (返回内部函数的一个变量)
function foo(){
let name = 'crystal';
return function(){
return name
}
}
var result = foo()
console.log(result())
3.2.函数赋值,将内部函数赋值给一个外部变量
function foo1(){
let name = 'crystal';
let a = function(){
console.log(name)
}
return a
}
let result1 = foo1();
console.log(result1)
3.3.函数参数,将一个函数作为另一个函数的参数
function foo2(){
let name = 'crystal';
let a = function(){
console.log(name)
}
fn(a)
}
function fn(f){
console.log(f)
}
let result2 = foo2();
console.log(result2)
3.4 IIEF(立即执行函数)
ps:下篇文章单独来记录立即执行函数
3.5 循环/迭代
3.6 私有属性(getter/setter)
3.7 区分首次调用
var firstLoad = (function(){
var list = []
return function(id){
if(list.indexOf(id) >= 0){
return false;
}else{
list.push(id);
return true;
}
}
})()
console.log(firstLoad()) //true
console.log(firstLoad()) //false
3.8 缓存机制
3.8.1.未使用缓存求和
function multi(){
var sum = 0;
for(var i = 0;i<arguments.length;i++){
sum+=arguments[i]
}
return sum;
}
console.log(multi(10,20,30,40))
console.log(multi(10,20,30,40))
在多次调用时,每次都会重新初始化sum的值,通过for循环来计算sum的和
3.8.1.使用缓存求和
var result = (function multi(){
var cache = {}
var calculate = function(){
console.log(1) //只输出一次
var sum = 0;
for(var i=0;i<arguments.length;i++){
sum+=arguments[i];
}
return sum;
}
return function(){
var args = Array.prototype.join.call(arguments,',');
if(args in cache){
return cache[args];
}else{
cache[args] = calculate.apply(null,arguments);
return cache[args];
}
}
}());
console.log(result(10,20,30,40))
console.log(result(10,20,30,40))
一般情况下,在多次调用函数时,如果函数参数不变,可以将结果缓存起来,在下次调用时,函数参数未发生变化,应该直接返回上次调用结果,而不是重新初始化在计算一次 。
实现思路:可以将上次计算结果存储在对象当中,将函数参数作为Key值,求和结果作为Value,当再次调用的时候,若对象中含有该Key,直接返回Value对象
3.9 img对象上传
var report = (function(src){
var imgs = [];
return function(){
var img = new Image();
imgs.push(img);
img.src = src;
}
}())
new Image() 低版本浏览器在进行数据上传时会丢失30%左右的数据,Image对象在使用完以后会立即被销毁,导致在图片上传的时候数据容易丢失,可以使用闭包将img 换存起来,始终存在内存当中
以上就是今天总结内容~