找回密码
 用户注册

QQ登录

只需一步,快速开始

查看: 3459|回复: 0

你必须知道的【闭包】陷阱和案例

[复制链接]
发表于 2012-2-27 17:40:12 | 显示全部楼层 |阅读模式
闭包
In some languages, a closure may occur when a function is defined within another function, and the inner function refers to local variables of the outer function.
At run-time, when the outer function executes, a closure is formed, consisting of the inner function’s code and references (the upvalues) to any variables of the outer function required by the closure.

翻译:
在一些编程语言当中,闭包发生  :在一个函数内部定义了另外一个函数,并且内部的函数引用了外部函数的本地变量。
在运行的时候,当外部函数执行,这个时候形成了一个闭包,由内部函数的代码和对外部函数任意变量引用组成,这写引用都依赖于此闭包。

  1. // ECMAScript
  2. var f, g;
  3. function foo() {
  4. var x = 0;
  5. f = function () { return ++x; };
  6. g = function () { return --x; };
  7. x = 1;
  8. alert('inside foo, call to f(): ' + f()); // "2"
  9. }
  10. //外部函数执行,这个时候形成了闭包
  11. foo();
  12. //因为有了闭包,所以才访问到了 foo中的x
  13. alert('call to g(): ' + g()); // "1"
  14. //因为有了闭包,所以才访问到了 foo中的x
  15. alert('call to f(): ' + f()); // "2"
复制代码



javascript闭包陷阱与案例

在很多时候,由于内部函数的变量覆盖了闭包的变量,我们如果需要引用外部函数同名的变量,需要通过执行匿名函数,不外部函数的变量作为参数传递进来。如下所示:
  1. (function(out_xxx){
  2. //这里面就可以使用out_xxx
  3. }(xxx))
复制代码
可以看得出来,使用这种方式最多的地方是在我们定义对象的时候:
  1. (function (window) {
  2. var MyObject = function () {
  3. this.initialize();
  4. }
  5. var p = MyObject.prototype;
  6. p.initialize = function () {
  7. }
  8. window.MyObject = MyObject;
  9. } (window));
复制代码
这样定义对象有两个好处:
1.避免污染外部变量
2.传递参数的形式减少作用域查找
javascript为我们埋了很多坑,在许多场景下,我们需要利用以上的形式去解决问题,下面依依列出。

场景1 :
如下所示,我需要在cc方法中调用到外面的name:

  1. var bb, cc;
  2. function aa() {
  3. var name = "当耐特";
  4. bb = function () {
  5. var name = "砖家";
  6. cc = function () {
  7. var name = "张磊";
  8. alert(name);
  9. }
  10. }
  11. }
  12. aa();
  13. bb();
  14. cc();//输出 “张磊”
复制代码
因为内部的函数定义的变量覆盖了外部函数的变量,所以结果输出“张磊”。
解决办法:
  1. var bb, cc;
  2. function aa() {
  3. var name = "当耐特";
  4. (function (aa_name) {
  5. bb = function () {
  6. var name = "砖家";
  7. (function (bb_name, aa_name) {
  8. cc = function () {
  9. var name = "张磊";
  10. alert(aa_name);
  11. alert(bb_name);
  12. alert(name);
  13. }
  14. })(name, aa_name);
  15. }
  16. })(name);
  17. }
  18. aa();
  19. bb();
  20. cc();//输出“当耐特” “砖家”  “张磊”
复制代码

真实案例:
记得上周,我的一个同事(实习生),对下面一段代码产生疑惑,所以咨询我。如下所示:
  1. $("#dialog-form").dialog({
  2. autoOpen: false,
  3. height: 300,
  4. width: 350,
  5. modal: true,
  6. buttons: {
  7. "Create an account": function () {
  8. var bValid = true;
  9. allFields.removeClass("ui-state-error");
  10. bValid = bValid && checkLength(name, "username", 3, 16);
  11. bValid = bValid && checkLength(email, "email", 6, 80);
  12. bValid = bValid && checkLength(password, "password", 5, 16);
  13. if (bValid) {
  14. $.ajax({
  15. type: "POST",
  16. url: "xxxxx.aspx",
  17. data: "name=xxxxx&email=xxxxx&password=xxxx"
  18. }).done(function (msg) {
  19. alert("Data Saved: " + msg);
  20. $(this).dialog("close");
  21. });
  22. }
  23. },
  24. Cancel: function () {
  25. $(this).dialog("close");
  26. }
  27. },
  28. close: function () {
  29. allFields.val("").removeClass("ui-state-error");
  30. }
  31. });
复制代码
这里用的是JqueryUI的dialog插件。详见: http://jqueryui.com/demos/dialog/#modal-form   


他想要的效果是点击create发起一个异步提交,然后在回调的时候关闭弹出层。令他困惑的地方是,弹出层关闭不了。
他抱怨着说:
  1. Cancel: function () {
  2. $(this).dialog("close");
  3. }
复制代码
我的cancel都能关闭。为什么
  1. if (bValid) {
  2. $.ajax({
  3. type: "POST",
  4. url: "xxxxx.aspx",
  5. data: "name=xxxxx&email=xxxxx&password=xxxx"
  6. }).done(function (msg) {
  7. alert("Data Saved: " + msg);
  8. $(this).dialog("close");
  9. });
  10. }
复制代码
这里面的$(this).dialog("close")为什么就不能关闭?
这是一个很典型的场景,解决办法:
  1. if (bValid) {
  2. (function (outThis) {
  3. $.ajax({
  4. type: "POST",
  5. url: "xxxxx.aspx",
  6. data: "name=xxxxx&email=xxxxx&password=xxxx"
  7. }).done(function (msg) {
  8. alert("Data Saved: " + msg);
  9. $(outThis).dialog("close");
  10. });
  11. }
  12. }(this))
  13. },
复制代码

除了上面的解决方案,我们完全可以使用
var _name=name;
var __name=name;
var _this =this;
然后内部函数使用新定义的变量就可以。然而,下面的几种场景却必须使用匿名函数。
场景2---循环中的内部函数
  1. function TestObj(name) {
  2. this.name = name;
  3. }
  4. var objs = [];
  5. var obj;
  6. function test() {
  7. for (var i = 0; i < 100; i++) {
  8. var name = "张磊" + i;
  9. obj = new TestObj(name);
  10. obj.printName = function () {
  11. console.log(obj.name);
  12. }
  13. objs.push(obj);
  14. }
  15. }
  16. //外部函数执行,闭包形成。内部函数obj.printName中的obj全部指向最后一次new TestObj(name);
  17. test();
  18. //所以这里会输出100次-----"张磊99"
  19. for (var i in objs) {
  20. objs[i].printName();
  21. }
复制代码
解决办法
  1. function TestObj(name) {
  2. this.name = name;
  3. }
  4. var objs = [];
  5. var obj;
  6. function test() {
  7. for (var i = 0; i < 100; i++) {
  8. var name = "张磊" + i;
  9. obj = new TestObj(name);
  10. (function (target) {
  11. obj.printName = function () {
  12. console.log(target.name);
  13. }
  14. } (obj))
  15. objs.push(obj);
  16. }
  17. }
  18. test();
  19. for (var i in objs) {
  20. objs[i].printName();
  21. }
复制代码

真实案例:

  1. // create and populate the screen with random daisies:
  2. for(var i = 0; i < 100; i++){
  3. bitmap = new Bitmap(image);
  4. container.addChild(bitmap);
  5. bitmap.x = canvas.width * Math.random()|0;
  6. bitmap.y = canvas.height * Math.random()|0;
  7. bitmap.rotation = 360 * Math.random()|0;
  8. bitmap.regX = bitmap.image.width/2|0;
  9. bitmap.regY = bitmap.image.height/2|0;
  10. bitmap.scaleX = bitmap.scaleY = bitmap.scale = Math.random()*0.4+0.6;
  11. bitmap.name = "bmp_"+i;
  12. // wrapper function to provide scope for the event handlers:
  13. (function(target) {
  14. bitmap.onPress = function(evt) {
  15. // bump the target in front of it's siblings:
  16. container.addChild(target);
  17. var offset = {x:target.x-evt.stageX, y:target.y-evt.stageY};
  18. // add a handler to the event object's onMouseMove callback
  19. // this will be active until the user releases the mouse button:
  20. evt.onMouseMove = function(ev) {
  21. target.x = ev.stageX+offset.x;
  22. target.y = ev.stageY+offset.y;
  23. // indicate that the stage should be updated on the next tick:
  24. update = true;
  25. }
  26. }
  27. bitmap.onMouseOver = function() {
  28. target.scaleX = target.scaleY = target.scale*1.2;
  29. update = true;
  30. }
  31. bitmap.onMouseOut = function() {
  32. target.scaleX = target.scaleY = target.scale;
  33. update = true;
  34. }
  35. })(bitmap);
  36. }
复制代码
这是Easeljs官网demo的一段代码,因为内部函数不会立即执行,所以当执行的时候,内部函数引用外部函数变量的时候,该变量已经被外层的for循环覆盖了N次,所以要通过上面的方式来解决这个问题。

小结
这是javascript蹩脚的一个方面,除了这个,还有javascript的 getter和setter也是该语言语法特性中令人憋屈的地方。
我相信经过人类的不懈努力,总有那么一天:static 、namespace、interface、private、protected 、class、enum·············都能成为javascript的关键字,而不是五花八门的技巧,对应这些关键字的所有 文章 笔记  心得  日记 技巧 随笔 后门  都将沉尸谷底、永无翻身之日。相信那个时候,HTML5已经成熟。正如小胖所说:什么时候做html5游戏的朋友能够不再纠结于什么js技巧 canvas性能优化的,那就说明html5游戏这个领域成熟了。
本文链接

您需要登录后才可以回帖 登录 | 用户注册

本版积分规则

Archiver|手机版|小黑屋|ACE Developer ( 京ICP备06055248号 )

GMT+8, 2024-3-29 09:57 , Processed in 0.021391 second(s), 7 queries , Redis On.

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表