javaScript之闭包

之前面试被问及神马是闭包,作为一只刚出生的菜鸟表示当时懵逼

(注:为了解释方便,以下代码来自MDN,不懂的直接去MDN:https://developer.mozilla.org/cn/docs/Web/JavaScript/Closures)

说到闭包不得不说说js变量作用域。js中变量的作用域属于链式的,就是说,任何函数都可以访问祖先(包括父亲、爷爷等等)函数中的变量。如:

1 function init() {
2   var name = "Mozilla";
3   function displayName() {
4     alert(name);
5   }
6   displayName();
7 }
8 init();

以上结果是 Mozilla。

子函数 displayName 访问的是父函数的变量name,那有人会问,那如果init函数还有个父函数,且该父函数也有个name变量,那displayName函数到底访问哪个?相信大家猜到答案了,既然是链式作用域,那凭感觉应该是访问最近的祖先函数定义的变量啦,不信可以用下面代码试试:

 1 function initFather(){
 2         var name = "father";
 3         function init() {
 4             var name = "Mozilla";
 5             function displayName() {
 6                 alert(name);
 7             }
 8             displayName();
 9         }
10         init();
11     }
12     initFather();

结果是 Mozilla。

 

讲完基础知识,那接下来开始磨刀霍霍了。

闭包,到底什么是闭包咧。

 1 function makeFunc() {
 2   var name = "Mozilla";
 3   function displayName() {
 4     alert(name);
 5   }
 6   return displayName;
 7 }
 8 
 9 var myFunc = makeFunc();
10 myFunc();

  以上代码还是能输出结果 Mozilla而不是undifined,为什么呢,因为,函数makeFunc中子函数displayName使用了父函数makeFunc的局部变量,但是头疼的是,这个子函数被作为返回值赋值给全局变量了。那么问题来了,一般情况下,函数中的局部变量在函数执行的时候存在,执行结束之后就销毁。可是在这种情况下,变量name是去还是留呢?

  由于以上情况在网页开发中经常出现且非常有用,所以name选择留下来。那,留下来总得有个理由吧,行!给你个理由,你存在的地方就是个闭包!也就是说定义你的函数就是一个闭包,但是别忘了你得有兄弟函数(在同样的环境中被定义)使用你。当闭包函数执行时,且你被以某种方式返回成为全局变量的一部分,你就拥有你的独立空间,不会随着父函数的结束而结束。

  更重要的是!每次调用这个闭包时这里的name都一人一个位置,纯属复制,但是复制之后各走各的,以后互不相干(科学的说法叫做:保存不同的环境)。相当于是克隆体,这个闭包就相当于是个造血干细胞(不喜勿喷)。来来来,不懂还有代码:

 1 function makeAdder(x) {
 2   return function(y) {
 3     return x + y;
 4   };
 5 }
 6 
 7 var add5 = makeAdder(5);
 8 var add10 = makeAdder(10);
 9 
10 console.log(add5(2));  // 7
11 console.log(add10(2)); // 12

氮素,这种小儿科的用法成就了闭包?负责任的告诉你,不可能。

现在看到这里,你有没有觉得闭包跟对象有点像啊,对啊,用对象构造器定义的对象也是个闭包。

网页开发里面经常遇到的代码类似这样:

  document.getElementById('myId').onclick = doSomething; 

如果这种情况遇到循环,很可能有人写成这样:

 1 <body>
 2 <p id="help">Helpful notes will appear here</p>
 3 <p>E-mail: <input type="text" id="email" name="email"></p>
 4 <p>Name: <input type="text" id="name" name="name"></p>
 5 <p>Age: <input type="text" id="age" name="age"></p>
 6 <script>
 7 function showHelp(help) {
 8   document.getElementById('help').innerHTML = help;
 9 }
10 
11 function setupHelp() {
12   var helpText = [
13       {'id': 'email', 'help': 'Your e-mail address'},
14       {'id': 'name', 'help': 'Your full name'},
15       {'id': 'age', 'help': 'Your age (you must be over 16)'}
16     ];
17 
18   for (var i = 0; i < helpText.length; i++) {
19     var item = helpText[i];
20     document.getElementById(item.id).onfocus = function() {
21       showHelp(item.help);
22     }
23   }
24 }
25 
26 setupHelp();
27 </script>
28 </body>

你去试试吧。告诉你为什么不对,有没有注意到,onfocus被赋值的是个匿名函数,它哪一点看起来像闭包了。所以,匿名函数里面的item是几个意思,item是匿名函数之外定义的。所以,既然不是闭包,那就是普通的函数,所有被赋值这个匿名函数的属性(onclick)在实际触发的时候使用的是同一个item对象,此时的item就是循环最后一轮的item。诶,这个时候item还在?为什么不该在?onclick是全局的吧,那,setupHelp函数是闭包吧。

所以,上面那个问题的解决方案很简单,给onfocus赋值一个闭包就行了,

function makeHelpCallback(help) {
  return function() {
      document.getElementById('help').innerHTML = help;
      };
}

function setupHelp() {
  var helpText = [
      {'id': 'email', 'help': 'Your e-mail address'},
      {'id': 'name', 'help': 'Your full name'},
      {'id': 'age', 'help': 'Your age (you must be over 16)'}
    ];

  for (var i = 0; i < helpText.length; i++) {
    var item = helpText[i];
    document.getElementById(item.id).onfocus = makeHelpCallback(item.help);
  }
}

setupHelp()

 

结尾再说点完全不是废话的话,刚刚说到,用构造器构造的对象如果同时构造了方法,那么这个对象就是个闭包。建议不要这样做,因为闭包的缘故,闭包中的方法跟闭包中的对象一样,在生成一个对象时是独立创建的,也就是说,每次生成一个对象函数就会被重新创建一次。但是,如果没有其它特殊用法,这样做只会浪费内存。

  所以解决办法就是,使用原型链prototype:

 1 function MyObject(name, message) {
 2   this.name = name.toString();
 3   this.message = message.toString();
 4 }
 5 MyObject.prototype.getName = function() {
 6   return this.name;
 7 };
 8 MyObject.prototype.getMessage = function() {
 9   return this.message;
10 };

废话讲完了...

 

你可能感兴趣的:(javaScript之闭包)