JS-闭包

名词解释

  • 词法作用域:根据声明变量的位置来确定该变量可被访问的位置。嵌套函数可获取声明于外部作用域的变量和函数。
  • "链式作用域"结构(chain scope):子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。

定义

MDN: 函数对其状态即词法环境(lexical environment)的引用共同构成闭包(closure)。也就是说,闭包可以让你从内部函数访问外部函数作用域。在JavaScript,函数在创建时生成闭包。
闭包是由函数以及创建该函数的词法环境组合而成。这个环境包含了这个闭包创建时所能访问的所有局部变量和函数。

什么是闭包?
能够读取其他函数内部变量的函数。在Js中,只有函数内部的函数才能读取局部变量。所以可以说闭包一个定义在函数内部的函数。

示例:

function makeFunc() {
    var name = "Mozilla"; // name是makeFunc的局部变量
    
    // displayName()函数和它的词法环境,组成了一个闭包
    function displayName() { 
        alert(name); // 使用了父函数中声明的变量
    }
     return displayName;
}

//执行makeFunc,返回结果是函数displayName
var myFunc = makeFunc();

//执行displayName,此时displayName仍能访问makeFunc的局部变量name
myFunc();

displayName可以访问makeFunc的内部变量,外部无法访问makeFunc的内部变量,所以将displayName作为返回结果,在外部就可以通过displayName访问makeFunc的内部变量name了。

在一些编程语言中,函数中的局部变量仅在函数的执行期间可用,一旦函数执行完了,那么它的内部变量将不能被访问。 但是在js中,函数在执行完以后,它的内部变量还可以被它的内部函数访问。

为什么会这样呢?
原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后被垃圾回收机制(garbage collection)回收。

实用:用闭包模拟私有方法

私有方法不仅仅有利于限制对代码的访问:还提供了管理全局命名空间的强大能力,避免非核心的方法弄乱了代码的公共接口部分

下面的示例展现了如何使用闭包来定义公共函数,并令其可以访问私有函数和变量。这个方式也称为 模块模式(module pattern)

var makeCounter = function() {
  //私有变量
  var privateCounter = 0;
  
  //私有方法
  function changeBy(val) {
    privateCounter += val;
  }
  
  return {
  	//Counter.increment,Counter.decrement 和 Count共享一个词法环境,
  	//也就是说,外部函数的变量privateCounter 和函数changeBy以下三个函数是都可以访问的
    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 */

上面示例创建了一个词法环境和三个函数。

环境包含两个私有项:名为 privateCounter 的变量和名为 changeBy 的函数。
三个函数Counter.incrementCounter.decrementCounter.value
这三个函数是共享同一个环境的闭包。由于JavaScript 的词法作用域,它们都可以访问 privateCounter 变量和 changeBy 函数。

为什么上面Counter2的value并没有受到影响?

每个闭包都是引用自己词法作用域内的变量 privateCounter
每次调用其中一个计数器时,通过改变这个变量的值,会改变这个闭包的词法环境。然而在一个闭包内对变量的修改,不会影响到另外一个闭包中的变量

缺点

  • 闭包在处理速度和内存消耗方面对脚本性能具有负面影响。
    为什么?
    闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

  • 闭包会在父函数外部,改变父函数内部变量的值。
    如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
    在创建新的对象或者类时,方法通常应该关联于对象的原型,而不是定义到对象的构造器中。
    为什么?
    原因是这将导致每次构造器被调用时,方法都会被重新赋值一次。

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

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

//构造器MyObject被调用时,this.getName和this.getMessage方法都会被重新赋值一次
let obj = new MyObject('shang','123');


//改造
function MyObject2(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject2.prototype.getName = function() {
  return this.name;
};
MyObject2.prototype.getMessage = function() {
  return this.message;
};

//调用MyObject2,它属性中的两个方法并不会被影响
let obj = new MyObject2('shang','123');

思考题

  var name = "The Window";

 var object = {
    name : "My Object",

    getNameFunc : function(){
      return function(){
      	  //这里的this指向的是window
        console.log(this.name);
      };

    }

  };

  object.getNameFunc()();	//The Window
var name = "The Window";

  var object = {
    name : "My Object",

    getNameFunc : function(){
    	  //闭包中的this指向的是定义它的函数,即object 
      var that = this;
      return function(){
      	  //这里的name就是object.name
        console.log(that.name);
      };

    }

  };

  object.getNameFunc()();	//My Object

this在最终调用时才确定,而不是定义时确定。

思考题解释:
object.getNameFunc()()
getNameFunc是由object调用,所以getNameFuncthis指向object
第一个示例中,执行第二个方法时,由于this指向是不能像作用域一样存在链式的,所以执行第二个方法时其实是window在调用,所以输出是The Window
第二个示例中,我们将thisthis指向object)转存that,在第二个方法中输出that.name相当于输出object.name,所以输出My Object

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值