闭包

作者: _咻咻咻咻咻 | 来源:发表于2020-09-28 17:29 被阅读0次

一 概念

闭包是指有权访问另一个函数作用域中的变量的函数。
在JavaScript中,就是在一个函数内部创建另一个函数,内部函数为闭包。内部函数可以访问外部函数环境内的变量。

function f1(){
  var n=999;
  function f2(){
    alert(n);
  }
  return f2;
  }
var result=f1();
result(); // 999

以上f2函数为闭包。 f2从f1中被返回后,它的作用域链被初始化为包含f1函数的活动对象和全局变量对象,而且,f1执行完后它的活动对象也不会被销毁,因为f2的作用域链还在引用这个活动对象(f1的作用域链被销毁,但是活动对象还在内存中),直到f2被销毁,f1的活动对象才会被销毁。
f1 = null; // 释放内存

function f1() {
  var n = 999;
  nAdd = function () { n += 1 }
  function f2() {
    alert(n);
  }
  return f2;
}
var result = f1();
result(); // 999
nAdd();
result(); // 1000

n没有被销毁,所以再次调用result()会增加。
另nAdd定义时没有用var或者let和const,因此是全局变量,也是一个闭包,可以在外部调用。

function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}
var add5 = makeAdder(5);
var add10 = makeAdder(10);
console.log(add5(2));  // 7
console.log(add10(2)); // 12

二 用途

1. 使用闭包解决递归调用问题

function factorial(num) {
  if (num <= 1) {
    return 1;
  } else {
    return num * factorial(num - 1)
  }
}
var anotherFactorial = factorial
factorial = null
anotherFactorial(4)   // 报错 ,因为最好是return num* arguments.callee(num-1),arguments.callee指向当前执行函数,但是在严格模式下不能使用该属性也会报错,所以借助闭包来实现

//arguments.callee是一个指向正在执行函数的指针。可以解决以上问题,但是在严格模式下会报错。
function factorial(num) {
    if(num<=1){
        return 1;
    }else{
        return num * arguments.callee(num-1)
    }
}

// 使用闭包实现递归
var newFactorial = (function f(num) {
  if (num < 1) { return 1 }
  else {
    return num * f(num - 1)
  }
}) //这样就没有问题了,实际上起作用的是闭包函数f,而不是外面的函数newFactorial

2. 模仿块级作用域

(function () {
  // 这里是块级作用域
})()

现在多用es6的let和const

3. 访问私有变量

在函数中定义的变量为私有变量,闭包可以创建用于访问私有变量的公有方法,这种方法叫特权方法
私有方法不仅仅有利于限制对代码的访问:还提供了管理全局命名空间的强大能力,避免非核心的方法弄乱了代码的公共接口部分。

3.1 使用构造函数/原型模式实现自定义类型的特权方法

构造函数:私有变量name在Person的每一个实例中都不同, 因此每次调用构造函数都会重新创建getName和setName。构造函数模式的缺点是针对每个实例都会创建同样一组方法,而使用静态私有变量来实现特权方法就可以避免这个问题。

function Person(name) {
    this.getName = function () {
        return name;
    }
    this.setName = function (vlaue) {
        name = value;
    }
}
var person = new Person("aaa")
console.log(person.getName()); //aaa
person.setName('bbb')
console.log(person.getName()); //bbb

原型模式:私有变量和函数是由实例共享的。变量name变成了静态的,由所有实例共享的属性。

(function () {
    var name = "";
    Person = function (value) {
        name = value
    }
    Person.prototype.getName = function () {
        return name;
    }
    Person.prototype.setName = function (value) {
        name = value;
    }
})();
var person1 = new Person("aaa");
console.log(person1.getName()); //aaa
person1.setName('bbb')
console.log(person1.getName()); //bbb

var person2 = new Person('ccc')
console.log(person1.getName()); //ccc
console.log(person2.getName()); //ccc
3.2 使用模块模式/增强的模块模式实现单例的特权方法

单例:只有一个实例的对象。

模块模式:

var makeCounter = function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }  
};

var Counter1 = makeCounter();
var Counter2 = makeCounter();
console.log(Counter1.value()); /* logs 0 */
Counter1.increment();
Counter1.increment();
console.log(Counter1.value()); /* logs 2 */
Counter1.decrement();
console.log(Counter1.value()); /* logs 1 */
console.log(Counter2.value()); /* logs 0 */

增强的模块模式: 适合那种实例必须是某种类型的实例,同时还必须添加某些属性或方法对其加以增强的情况。

var singleton = function () {
    //私有变量和私有属性
    var privateVatiable = 10;
    function privateFunction() {
        return false;
    }
    //创建对象
    var object = new CustomType();
    //添加特权/公有属性和方法
    object.publicProperty = true;
    object.publicMethod = function () {
        privateVatiable++;
        return privateFunction();
    }
    return object
}

三 使用闭包需要注意

1. 闭包只能取得外部函数中任何变量的最后一个值
function outer() {
  var result = [];
  for (var i = 0; i < 5; i++) {
    result[i] = function () {
      return i;
    }
  }
  return result
}
var a = outer();
a[0]() //5
a[1]() //5
a[2]() //5
a[3]() //5
a[4]() //5

这里闭包函数引用了变量i,但是并没有立即执行。变量i用var声明的,作用域在外部函数outer中,等到闭包函数执行的时候,i已经变成了5。所以result中每一项打印的都是5。

解决方案1: 多用一层闭包

function outer() {
  var result = [];
  for (var i = 0; i < 5; i++) {
    result[i] = (function (num) {
      return function(){
        return num;
      }
    })(i)
  }
  return result
}
var a = outer();
a[0]() //0
a[1]() //1
a[2]() //2
a[3]() //3
a[4]() //4

解决方案2:

function outer() {
  var result = [];
  for (var i = 0; i < 5; i++) {
    (function(){
      var item = i
      result[item] = function(){
        return item;
      }
    })()
  }
  return result
}
var a = outer();
a[0]() //0
a[1]() //1
a[2]() //2
a[3]() //3
a[4]() //4

解决方案3: i 用let来声明

function outer() {
  var result = [];
  for (let i = 0; i < 5; i++) {
    result[i] = function(){
      return i;
    }
  }
  return result
}
var a = outer();
a[0]() //0
a[1]() //1
a[2]() //2
a[3]() //3
a[4]() //4

解决方案4: 用forEach

function outer() {
  var result = [];
  var arr = [0,1,2,3,4]
  result = arr.forEach(function(i){
    return i;
  })
  return result
}
2. this指向问题

匿名函数的执行环境具有全局性,因此其this对象通常指向window。

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
3. 内存泄漏问题
function showId() {
  var el = document.getElementById("app")
  el.onclick = function () {
    alert(el.id)   // 这样会导致闭包引用外层的el,当执行完showId后,el无法释放
  }
}

// 改成下面
function showId() {
  var el = document.getElementById("app")
  var id = el.id
  el.onclick = function () {
    alert(id)   // 这样会导致闭包引用外层的el,当执行完showId后,el无法释放
  }
  el = null    // 主动释放el
}

四 性能

因为闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存,在处理速度和内存消耗方面对脚本性能具有负面影响,所以如果不是某些特定任务需要使用闭包,尽量避免闭包的使用。

例如,在创建新的对象或者类时,方法通常应该关联于对象的原型,而不是定义到对象的构造器中。原因是这将导致每次构造器被调用时,方法都会被重新赋值一次(也就是说,对于每个对象的创建,方法都会被重新赋值)。
eg:

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
  this.getName = function() {
    return this.name;
  };

  this.getMessage = function() {
    return this.message;
  };
}

在上面的代码中,我们并没有利用到闭包的好处,因此可以避免使用闭包。修改成如下:

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype = {
  getName: function() {
    return this.name;
  },
  getMessage: function() {
    return this.message;
  }
};

但我们不建议重新定义原型。可改成如下例子:

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype.getName = function() {
  return this.name;
};
MyObject.prototype.getMessage = function() {
  return this.message;
};

在前面的两个示例中,继承的原型可以为所有对象共享,不必在每一次创建对象时定义方法。

五 总结

当在函数内部定义了其他函数,就创建了闭包。
闭包的作用域包含它自己的作用域,包含函数的作用域和全局作用域。
使用闭包可以模仿块级作用域。
闭包可以用于在对象中创建私有变量。可以使用构造函数模式,原型模式实现自定义类型的特权方法,也可以使用模块模式,增强的模块模式实现单例的特权方法。

相关文章

  • swift-闭包

    闭包 闭包定义 闭包简化 - 尾随闭包 闭包参数 闭包返回值 闭包的循环引用

  • 闭包,闭包,闭包

    1、这家伙到底是什么? 网上关于这个的讨论的太多了太多了,有各种的举例子,但是大部分还在寻找这个答案的小伙伴对于变...

  • 闭包-Closures [swift 5.1]

    闭包的语法 尾随闭包 闭包逃离 自动闭包

  • Day7 闭包(Closures)

    本页包含内容:• 闭包表达式• 尾随闭包• 值捕获• 闭包是引用类型• 逃逸闭包• 自动闭包 1、闭包表达式 闭包...

  • Python闭包

    闭包 = 环境变量 + 函数 调用闭包内部的环境变量 闭包的经典误区 闭包与非闭包实现人类走路 非闭包 闭包

  • 闭包(closure)

    ● 闭包基础 ● 闭包作用 ● 闭包经典例子 ● 闭包应用 ● 闭包缺点 ● 参考资料 1、闭包基础 作用域和作...

  • swift- 闭包一

    /*• 闭包表达式• 尾随闭包• 值捕获• 闭包是引用类型• 逃逸闭包• 自动闭包*/

  • (9) python之闭包

    闭包闭包 = 函数 + 环境变量(函数定义的时候) 一个最简单的闭包 闭包不受外部变量影响 非闭包 闭包 闭包 只...

  • Swift-进阶 :闭包(二)逃逸闭包 & 非逃逸闭包

    本文主要分析逃逸闭包 、非逃逸闭包、自动闭包 逃逸闭包 & 非逃逸闭包 逃逸闭包定义 当闭包作为一个实际参数传递给...

  • javascript闭包详解

    跟我念 bi 闭 bao包 ,闭包的闭,闭包的包。。 闭包的简介 在计算机科学中,闭包(英语:Closure),又...

网友评论

      本文标题:闭包

      本文链接:https://www.haomeiwen.com/subject/fjgduktx.html