js到底怎么做才算是闭包?能解决什么样的实际问题?



 function aa(){
    this.qqq = "qqqName";
    www = "wwwName";
    console.log(this.www);
}

_h = aa.prototype;

_h.bb = function(){
    console.log(this.www);
    console.log(this.qqq);
}

var cc = new aa();
cc.bb();
console.log(cc.qqq);
console.log(cc.www);

//输出
//undefined
//undefined
//qqqName
//qqqName
//undefined

上面代码中,_h.bb算是闭包吗?还是说只有在子函数里面return父函数的变量,这个子函数才算是闭包?但是我这里,this.qqq在最外面都是可以访问的啊,修改也是可以的啊,而www只能在aa()里访问,这样是不是比直接把bb()函数写在aa()函数里要好一点?毕竟变量的作用域在定义的时候就可以自由选择又能做到常在内存。

另外还有一种是把函数当做对象的其中一个key/value来使用,这样应该毫无疑问是闭包的做法了,但是我还是想知道我这种算不算闭包呀...

还是说我对闭包的理解错了?求解呀,弄不懂这个我js就进步不了了TAT

最后,闭包能解决什么样的实际问题?下面是我想到的几个可能的答案
1.在主函数内的变量可以随便起名字,不用怕外面有重名的?
2.在需要实例化多个对象(就像上面 var cc = new aa(); )的时候,这些对象里的同名变量不会互相影响,并且可以独立工作?比如


 var cc = new aa();
var dd = new aa();
cc.qqq = "ooooooooo";
console.log(dd.qqq)//=>qqqName
console.log(cc.qqq)//=>ooooooooo

3.当要保存一个变量,而且要在以后再用的时候,比如保存一个audio对象,需要暂停的时候再回来找它,但是又不想声明全局变量,因为在任何地方都能直接读取也不好。额,这个其实就是答案1的详细版...

JavaScript 闭包

一只玲珑辞 10 years, 7 months ago

我看到现在,表示,楼主是想自己写一些JS的东西,不过楼主对JS并不怎么了解,个人建议还是系统的看一下基础的书籍比较好,个人推荐《JavaScript权威指南(第六版)》或《JavaScript高级程序设计(第3版)》,看完之后以上问题自然迎刃热解。

下面我说一下楼主混淆的几个点:
1. this
2. 函数 与 构造函数
3. 作用域 与 闭包

楼主的疑惑

this关键字

函数调用 中的 this :
根据 ECMAScript 3 和非严格的 ECMAScript 5 对函数调用的规定,调用上下文(this的值)是全局对象,然而,在严格模式下,调用上下文则是undefined。
方法调用 中的 this :
此时,this的值指向调用它的对象。
构造函数 中的 this :
此时,this指向新创建的对象,更多构造函数的知识请看下面

this 还有很多知识点,本文不展开说明。

函数与构造函数

构造函数 :简单的理解为,如果函数或者方法调用之前带有关键字new,它就构成构造函数调用,那么该函数就是构造函数。当然函数和构造函数有很多不同,此文不展开说明。
构造函数调用 创建一个新的空对象,这个对象继承自构造函数的 prototype 属性(原型),构造函数试图初始化这个新创建的对象,并将这个对象做其调用上下文,因此构造函数可以使用 this 关键字来引用这个新创建的对象。 @bumfod 说的 instance level prototype level 就是这个道理。

作用域与闭包

其实,楼主对于作用域的理解还是挺不错的。楼主上面讲的跟就是作用域,因为楼主不了解闭包。 @bumfod 关于闭包的阐述很到位。本人也简单明了的讲一下。

作用域
与其它编程语言相比,JavaScript使用了 函数作用域 :变量在声明它们的函数体以及这个函数体嵌套的任意函数体内都都是有定义的。楼主注意!!!,所以可以是用匿名函数来避免对象或者变量的覆盖。


 // 匿名函数
    (function() {
        /* 编写逻辑,这里声明的函数,变量外部不可访问 */
    }());

闭包 :
这里直接使用 @bumfod 的代码


 // 闭包
    var b = (function /* 这里的 generator 可选 */ generator() {
        var a = 0;
        return function b() { return a; };
    }());

    b(); // 0

说明:由于JavaScript也采用词法作用域,也就是说,函数的执行依赖于变量作用域,这个作用域是在函数定义时决定的,而不是函数调用时决定的。然后就有了闭包的说法,如果一个函数定义了嵌套的函数,并将它作为返回值返回或者存储在某处的属性里,这时就会有一个外部引用指向这个嵌套函数。它就不会被当做垃圾回收(不回收作用域链),所以才有了上面闭包的代码。

闭包简单讲就是这样,其实还有挺多知识的。出于楼主个人情况,为了引导楼主学习,我只是简要说明,希望本文对楼主有帮助。还是那句话,如果楼主对JavaScript感兴趣,想深入学习,还是有必要系统的看一下书,边学边实战,这才是学习编程的最快方法。

秀吉saber answered 10 years, 7 months ago

先泼冷水:题主在题目描述中所说的三点 完全不是闭包

回答题主的几点认识:

1.在主函数内的变量可以随便起名字,不用怕外面有重名的?

之所以会这样理解,完全是因为例子里面的书写不够规范。理论上来说, www 前面还是应该加一个变量声明 var 的,不然会被当做 全局变量 解析。而且在函数作用域里声明的变量,和外部变量是不在一个作用域内的,完全不存在重名的问题。闭包的存在不是为了解决这个问题。

2.在需要实例化多个对象(就像上面 var cc = new aa(); )的时候,这些对象里的同名变量不会互相影响,并且可以独立工作?

这个和闭包也没有半毛钱的关系,不同对象中的属性当然不会互相影响。在例子中, www 不是变量,仅仅是一个对象属性而已,例子中的 www 才是真正的变量。但是你可以看到,上面的例子是 无法访问 内部变量 www 的!如果你要访问到这个变量怎么办?这个时候 闭包 就正式出场了。

先看一下第三点认识:

3.当要保存一个变量,而且要在以后再用的时候,比如保存一个audio对象,需要暂停的时候再回来找它,但是又不想声明全局变量,因为在任何地方都能直接读取也不好。额,这个其实就是答案1的详细版...

题主这一观点算是勉强认识到了闭包的作用之一: 保护并保持私有变量 。就顺着你的意思说下去,我真的有这么一个 audio 对象:


 function Audio() {
    var status = 'start';
    this.play = function() {
        status = 'continue...';
    };
    this.stop = function() {
        status = 'pause';
    };
    this.getStatus = function() {
        return status;
    };
}

这里的 status 就是你理解的要保存的一个变量,它确实不适合作为全局变量————因为对于一个 audio 对象来说,只有用户的播放和暂停能够改变它的状态,其他操作是不能改变的。也就是说,这个对象只向外提供 play stop 方法来修改 status 的状态,提供 getStatus 的方法来访问这个状态的值,但 任何其他的途径都不能改变这个变量


 var a = new Audio();
a.getStatus(); // start
a.stop();      // pause
a.play();      // continue...
a.status;      // undefined

一笑见红颜 answered 10 years, 7 months ago

简单来讲就是在方法a里面定义了方法b,方法b通过原型链调用了方法a的一个属性c,因为方法b在方法a执行完之后不会被销毁,所以方法a的属性c任然存在于执行环境中。
这可能导致内存泄露,当然你也可以用来保存不想销毁的数据。

vivira answered 10 years, 7 months ago

閉包 (電腦科學) - 維基百科,自由的百科全書

在電腦科學中,閉包(Closure)是詞法閉包(Lexical Closure)的簡稱,是參照了自由變數的函式。這個被參照的自由變數將和這個函式一同存在,即使已經離開了創造它的環境也不例外。所以,有另一種說法認為閉包是由函式和與其相關的參照環境組合而成的實體。閉包在執行時可以有多個例項,不同的參照環境和相同的函式組合可以產生不同的例項。

簡單地說,閉包就是 變量 作用域生命的延伸。

在一些語言中,在函式中可以(巢狀)定義另一個函式時,如果內部的函式參照了外部的函式的變數,則可能產生閉包。執行時,一旦外部的函式被執行,一個閉包就形成了,閉包中包含了內部函式的代碼,以及所需外部函式中的變數的參照。其中所參照的變數稱作上值(upvalue)。

Javascript 中的閉包就是如此。

例如:


 var b = (function generator() {
    var a = 0;

    return function b() { return a; };
}());

b(); // 0

即便 generator 的作用域已經結束,變量 a 仍舊可以通過函數 b 訪問,這是閉包的典型運用。

閉包一詞經常和匿名函式混淆。這可能是因為兩者經常同時使用,但是它們是不同的概念。

題主就混淆了這兩個概念。 _h.bb 是一個匿名函數,但這個匿名函數並沒有用到閉包的性質。

題主的代碼應該寫成這樣:


 function Aa() {
    var a = 0;

    this._b = 1;

    this.a = function(val) {
        if (val === void(0))
            return a;
        a = val;
    }
}

Aa.prototype = {
    constructor: aa,
    b: function(val) {
        if (val === void(0))
            return this._b;
        this._b = val;
    }
}

var aa = new Aa;

aa.a(); // 0
aa.b(); // 1
aa._b;  // 1

此時, aa.a() 是 instance level 函數, aa.b() 等價於 Aa.prototype.b.call(aa) 是 prototype level 函數。

a 可以看作是一個私有屬性, _b 是一個公共屬性。

而性能問題的擔憂是樓主對現代 Javascript 引擎的不理解。

aa.b() 的性能不如 aa.a()

首先是由於需要遍歷原型鏈,其次是 b 額外訪問了一次 aa 的屬性。

再者即便閉包是 instance level 函數,它也是「閉包在執行時可以有多個例項,不同的參照環境和相同的函式組合可以產生不同的例項」中的例項,也就是實例,函數本身並不需要單獨佔用額外內存。

prototype 版本在 jsperf 完勝 closure 版本。 http://jsperf.com/clos-vs-proto

之前的分析忘記了創建閉包的性能開銷。

不過如果對象只有一個實例,單純訪問屬性的速度,closure 還是略微快一點的。 http://jsperf.com/prototype-vs-closure-single-object

如 Humphry 所說, aa.b() aa.a() 的比較實際上是比较原型链寻址 vs 作用域链寻址

http://jsperf.com/clos-vs-proto 比較的主要是創建對象的速度。

最後回答題主的三個問題。

這三個問題實際上是同一個問題,就是變量的作用域問題。

另外樓主混淆了 屬性 變量

屬性是屬於對象實例的,變量是屬於閉包的。
全局變量比較特殊,因爲它也可以通過 window.a 訪問,這是 js 設計的一個失誤?因爲 你可以 delete 通過 window.a 聲明的 a ,不可以 delete 通過 var a 聲明的 window.a

伊红与美蓝 answered 10 years, 7 months ago

Your Answer