>

ECMAScript 6에 생성자가 있지만 ECMAScript 6의 소멸자와 같은 것이 있습니까?

예를 들어 생성자의 객체 리스너를 이벤트 리스너로 등록하면 객체가 삭제 될 때 제거하고 싶습니다.

하나의 솔루션은 desctructor 를 만드는 관습을 갖는 것입니다  이런 종류의 행동이 필요한 모든 클래스에 대해 메소드를 호출하고 수동으로 호출하십시오. 그러면 이벤트 핸들러에 대한 참조가 제거되므로 내 객체는 가비지 수집 준비가 완료됩니다. 그렇지 않으면 해당 방법으로 인해 메모리에 남아 있습니다.

그러나 ECMAScript 6에 객체가 가비지 수집되기 직전에 호출 될 네이티브가 있으면 기대하고있었습니다.

그러한 메커니즘이 없다면, 그러한 문제에 대한 패턴/협약은 무엇입니까?


  • 답변 # 1

    와이즈 비즈

    아니요. EcmaScript 6은 가비지 수집 의미를 전혀 지정하지 않으므로 "sup"[1]도 없습니다.

    와이즈 비즈

    소멸자는 여기서 당신을 도와주지 않을 것이다. 여전히 객체를 참조하는 것은 이벤트 리스너 자체이므로 등록을 해제하기 전에 가비지 수집을 수행 할 수 없습니다.
    실제로 찾고있는 것은 리스너를 라이브 루트 객체로 표시하지 않고 리스너를 등록하는 방법입니다. (이 기능에 대해서는 현지 이벤트 소스 제조업체에 문의하십시오.)

    <1>: 글쎄,

    Is there such a thing as destructors for ECMAScript 6?

    의 사양으로 시작이있다  그리고

    If I register some of my object's methods as event listeners in the constructor, I want to remove them when my object is deleted

     사물. 그러나, 실제 약한 참조는 여전히 파이프 라인에 있습니다 [1] [2].

  • 답변 # 2

    방금 소멸자에 대한 검색에서이 질문을 발견했으며 응답하지 않은 부분이 있다고 생각했습니다. 귀하의 의견에 질문을 드리겠습니다.

    와이즈 비즈

    오브젝트에 이제 작업이 완료되었으며 이벤트 리스너를 구체적으로 해제해야한다고 알리려면 일반적인 방법을 만들면됩니다. WeakMap 와 같은 메소드를 호출 할 수 있습니다  또는 WeakSet  또는

    thank you guys. But what would be a good convention if ECMAScript doesn't have destructors? Should I create a method called destructor and call it manually when I'm done with the object? Any other idea?

     또는 그 ilk의 무엇이든. 아이디어는 객체가 연결된 다른 것 (이벤트 리스너 등록 취소, 외부 객체 참조 지우기 등)과의 연결을 끊으라고 지시하는 것입니다. 적절한 시간에 수동으로 호출해야합니다.

    동시에 해당 객체에 대한 다른 참조가없는 경우 해당 시점에서 객체가 가비지 수집 대상이됩니다.

    ES6에는 weakMap 및 weakSet이 있습니다. 이는 가비지 수집시기에 영향을주지 않으면 서 여전히 살아있는 일련의 개체를 추적하는 방법이지만 가비지 수집시 어떤 종류의 알림도 제공하지 않습니다. 어느 시점에서 (GC 일 때) weakMap 또는 weakSet에서 사라집니다.

    <시간>참고로,이 유형의 소멸자에 대한 문제 (아마도 호출이 많지 않은 이유)는 가비지 수집으로 인해 항목이 열려있을 때 가비지 수집에 적합하지 않다는 것입니다 소멸자가 있었더라도 실제로 이벤트 리스너를 제거하기 전까지는 상황에서 호출되지 않습니다. 이벤트 리스너를 제거한 후에는이 목적을 위해 소멸자가 필요하지 않습니다.

    가능한 release() 가 있다고 가정  가비지 수집을 방해하지는 않지만 그러한 것도 존재하지 않습니다.

    <시간> 참고로, 여기 또 다른 관련 질문이 있습니다. 가비지 수집 언어의 객체 소멸자 패러다임이 널리 퍼진 이유는 무엇입니까? 이 토론에서는 종료 자, 소멸자 및 디스 포저 디자인 패턴에 대해 설명합니다. 이 세 가지를 구별하는 것이 유용하다는 것을 알았습니다.

  • 답변 # 3

    JS에서 객체를 수동으로 "파괴"해야합니다. JS에서는 파괴 함수를 만드는 것이 일반적입니다. 다른 언어에서는 이것을 free, release, dispose, close 등이라고 부를 수 있습니다. 내 경험상 내부 참조, 이벤트 및 전파를 파괴하여 하위 객체에 대한 호출을 파괴하는 파괴되는 경향이 있지만

    WeakMaps는 반복 될 수 없기 때문에 거의 쓸모가 없으며 ECMA 7까지는 사용할 수 없을 것입니다. 모든 WeakMaps는 객체 참조와 GC에 의한 조회를 제외하고 객체 자체에서 분리 된 보이지 않는 속성을 가지고있어 방해하지 않습니다. 이는 복수를 캐싱, 확장 및 처리하는 데 유용 할 수 있지만 실제로 관찰 가능 개체 및 관찰자에 대한 메모리 관리에는 도움이되지 않습니다. WeakSet은 기본값이 부울 true 인 WeakMap과 같은 WeakMap의 하위 집합입니다.

    이 소멸자에 대해 약한 참조의 다양한 구현을 사용할지 여부에 대한 다양한 주장이 있습니다. 둘 다 잠재적 인 문제가 있으며 소멸자는 더 제한적입니다.

    파괴자는 실제로 관찰자/청취자에게도 잠재적으로 쓸모가 없습니다. 일반적으로 청취자는 관찰자에 대한 참조를 직접 또는 간접적으로 보유하기 때문입니다. 소멸자는 실제로 약한 참조없이 프록시 방식으로 만 작동합니다. 관찰자가 실제로 다른 청취자를 가져 와서 관찰 가능하게하는 프록시 인 경우 거기에서 무언가를 할 수는 있지만 이런 종류의 일은 거의 유용하지 않습니다. 소멸자는 IO와 관련된 일이나 격리 범위 밖에서 일하는 데 더 많은 역할을합니다 (IE, 생성 한 두 인스턴스를 연결).

    이걸 살펴보기 시작한 구체적인 사례는 생성자에서 클래스 B를 취하는 클래스 A 인스턴스가 있고 B를 수신하는 클래스 C 인스턴스를 생성하기 때문입니다. 나는 항상 B 인스턴스를 어딘가에 높게 유지합니다. AI는 때때로 버리고, 새로운 것을 만들고, 많은 것을 만드는 등의 경우가 있습니다.이 상황에서 소멸자는 실제로 작동하지만 C 인스턴스를 통과했지만 모든 A 참조를 제거한 다음 C 및 B 바인딩이 끊어졌습니다 (C는 그 아래에서 접지를 제거했습니다).

    JS에서 자동 솔루션이없는 것은 고통 스럽지만 쉽게 해결할 수 있다고 생각하지 않습니다. 다음 클래스를 고려하십시오 (의사) :

    deregister()
    
    

    부수적으로, 나중에 다루게 될 익명/고유 기능 없이는 작업하기가 어렵습니다.

    일반적인 경우 인스턴스화는 다음과 같습니다 (의사) :

    unhook()
    
    

    GC에서는 일반적으로 null로 설정하지만 루트에 stdin을 가진 트리를 생성했기 때문에 작동하지 않습니다. 이것이 기본적으로 이벤트 시스템이하는 일입니다. 부모에게 자녀를 주면 자녀가 부모에게 자신을 추가 한 다음 부모에 대한 참조를 유지하거나 유지하지 않을 수 있습니다. 나무는 간단한 예이지만 실제로는 거의 드물지만 복잡한 그래프로 자신을 찾을 수도 있습니다.

    이 경우, Filter는 범위에 따라 Filter를 간접적으로 참조하는 익명 함수 형태로 stdin에 대한 참조를 추가합니다. 범위 참조는 알고 있어야하며 매우 복잡 할 수 있습니다. 강력한 GC는 범위 변수의 항목을 개척하기 위해 흥미로운 일을 할 수 있지만 또 다른 주제입니다. 이해해야 할 중요한 점은 익명 함수를 생성하고이를 관찰 가능 항목에 리스너로 추가 할 때, 관찰 가능 함수는 함수에 대한 참조와 해당 함수가 해당 범위에서 정의 된 모든 함수를 유지한다는 것입니다. )도 유지됩니다. 뷰는 동일하지만 생성자를 실행 한 후에는 부모에 대한 참조를 유지하지 않습니다.

    위에 선언 된 vars 중 일부 또는 전부를 null로 설정하면 아무것도 변경되지 않습니다 ( "주"범위를 마쳤을 때와 비슷). 이들은 여전히 ​​활성 상태이며 stdin에서 stdout 및 stderr로 데이터를 파이프합니다.

    모두 null로 설정하면 stdin의 이벤트를 지우거나 stdin을 null로 설정하지 않고 제거하거나 GCed 할 수 없습니다 (이와 같이 해제 할 수 있다고 가정). 코드의 나머지 부분에 stdin이 필요하고 위에서 언급 한 작업을 금지하는 다른 중요한 이벤트가있는 경우 기본적으로 고아 객체로 메모리 누수가 발생합니다.

    df, v1 및 v2를 제거하려면 각각에 대해 destroy 메소드를 호출해야합니다. 구현 측면에서 이것은 Filter 및 View 메소드 모두 자신이 생성 한 익명 리스너 함수와 observable을 참조하고 removeListener에 전달해야 함을 의미합니다.

    참고로, 리스너를 추적하기 위해 인덱스를 반환하는 Obserable을 가질 수 있으므로 적어도 내 이해에 비해 성능과 메모리가 훨씬 좋을 프로토 타입 함수를 추가 할 수 있습니다. 그래도 반환 된 식별자를 추적하고 객체를 전달하여 리스너가 호출 될 때 바인딩되도록해야합니다.

    파괴 기능은 몇 가지 고통을 추가합니다. 먼저 전화해서 참조를 해제해야합니다.

    weakListener()
    
    

    좀 더 코드가 많지만 실제 문제는 아니기 때문에 약간의 성가심입니다. 이 참고 문헌을 많은 물건에 넘겨 줄 때. 이 경우 정확히 언제 destroy라고 부릅니까? 이것들을 다른 물체에 넘겨 줄 수는 없습니다. 프로그램 흐름이나 다른 수단을 통해 일련의 파괴와 추적의 수동 구현으로 끝납니다. 발사하거나 잊을 수 없습니다.

    이 종류의 문제의 예는 View가 파괴 될 때 df에서 destroy를 호출하기로 결정한 경우입니다. v2가 여전히 df를 파괴하는 경우 df를 중단하면 destroy를 df로 릴레이 할 수 없습니다. 대신 v1이 df를 사용하여 사용하면 df에게 카운터를 올리거나 df와 비슷한 것을 사용하도록 지시해야합니다. df의 destroy 함수는 counter보다 감소 할 것이고 실제로 0 일 경우에만 파괴됩니다. 는 순환 참조를 사용합니다 (이 시점에서는 더 이상 카운터를 관리하는 경우가 아니라 참조 객체의 맵). JS에서 자체 참조 카운터, MM 등을 구현할 생각이라면 아마도 부족할 것입니다.

    WeakSet이 반복 가능한 경우 다음을 사용할 수 있습니다.

    function Filter(stream) {
        stream.on('data', function() {
            this.emit('data', data.toString().replace('somenoise', '')); // Pretend chunks/multibyte are not a problem.
        });
    }
    Filter.prototype.__proto__ = EventEmitter.prototype;
    function View(df, stream) {
        df.on('data', function(data) {
            stream.write(data.toUpper()); // Shout.
        });
    }
    
    

    이 경우 소유 클래스는 f에 대한 토큰 참조를 유지해야합니다. 그렇지 않으면 펑크가됩니다.

    EventListener 대신 Observable을 사용하면 이벤트 리스너와 관련하여 메모리 관리가 자동으로 수행됩니다.

    각 오브젝트에 대해 destroy를 호출하는 대신이를 완전히 제거하기에 충분합니다 :

    var df = new Filter(stdin),
        v1 = new View(df, stdout),
        v2 = new View(df, stderr);
    
    

    df를 null로 설정하지 않으면 여전히 존재하지만 v1과 v2는 자동으로 연결 해제됩니다.

    그러나이 방법에는 두 가지 문제가 있습니다.

    문제는 새로운 복잡성을 추가한다는 것입니다. 때때로 사람들은 실제로이 행동을 원하지 않습니다. 포함 (생성자 범위 또는 객체 속성의 참조)이 아닌 이벤트로 서로 연결된 매우 큰 객체 체인을 만들 수 있습니다. 결국 나무와 나는 뿌리 주위를 통과하고 그것에 대해 걱정해야합니다. 뿌리를 풀어주는 것은 모든 것을 편리하게 해방시킬 것입니다. 코딩 스타일 등에 따른 두 가지 동작 모두 유용하며 재사용 가능한 객체를 만들 때 사람들이 원하는 것, 수행 한 것, 수행 한 작업 및 수행 된 작업을 해결하기가 어려울 수 있습니다. EventListener 대신 Observable을 사용하는 경우 df는 v1 및 v2를 참조하거나 참조 범위를 다른 범위로 전달하려는 경우 모두 전달해야합니다. 사물과 같은 약한 참조는 Observable에서 옵저버로 제어를 전송하여 문제를 약간 완화하지만 완전히 해결하지는 못합니다 (모든 방출 또는 이벤트 자체를 확인해야 함). 이 문제는 GC를 심각하게 복잡하게하는 고립 된 그래프에만 적용되며 실제로 그래프 외부에 참조가있는 경우 (아무런 CPU 사이클 만 소비하고 변경하지 않은 경우)에는 적용되지 않는 것으로 가정합니다.

    문제 2는 특정 경우 예측할 수 없거나 JS 엔진이 성능에 심각한 영향을 줄 수있는 요청시 해당 객체에 대해 GC 그래프를 통과하도록 강요한다는 것입니다 (영리한 경우에는 구성원별로 수행하지 않아도 될 수 있음) 대신 WeakMap 루프마다 수행하십시오. 메모리 사용량이 특정 임계 값에 도달하지 않고 해당 이벤트가있는 객체가 제거되지 않으면 GC가 실행되지 않을 수 있습니다. v1을 null로 설정하면 여전히 stdout으로 릴레이 될 수 있습니다. GC를 얻더라도 이것은 임의적이지만, 어느 정도의 시간 동안 (1 라인, 10 라인, 2.5 라인 등) 계속 stdout으로 릴레이 될 수 있습니다.

    itakable 불가능할 때 WeakMap이 GC를 신경 쓰지 않는 이유는 어쨌든 GC되지 않았거나지도에 추가되지 않았으므로 객체에 대한 참조가 있어야하기 때문입니다. .

    이런 종류에 대해 어떻게 생각하는지 모르겠습니다. 반복 가능한 WeakMap 접근 방식으로 수정하기 위해 일종의 메모리 관리를하고 있습니다. 소멸자에게도 문제 2가 존재할 수 있습니다.

    이 모든 것은 여러 수준의 지옥을 불러 일으키기 때문에 좋은 프로그램 디자인, 모범 사례, 특정 사항을 피하는 등의 방법으로 문제를 해결하는 것이 좋습니다. JS의 유연성은 어느 정도입니까? 제어의 역전으로 인해 더 자연스럽고 비동기 적이며 이벤트이기 때문입니다.

    아주 우아하지만 여전히 심각한 행 아웃이있는 다른 솔루션이 있습니다. 관찰 가능 클래스를 확장하는 클래스가있는 경우 이벤트 함수를 대체 할 수 있습니다. 이벤트가 자신에게 추가 된 경우에만 다른 관찰 가능 이벤트에 이벤트를 추가하십시오. 모든 이벤트가 제거되면 어린이에서 해당 이벤트를 제거하십시오. 또한 관찰 가능한 클래스를 확장하여 클래스를 만들 수도 있습니다. 그러한 클래스는 비어 있고 비어 있지 않은 후크를 제공 할 수 있으므로 자신을 관찰해야합니다. 이 방법은 나쁘지 않지만 끊기가 있습니다. 복잡성 증가 및 성능 저하가 있습니다. 관찰 한 물체에 대한 참조를 유지해야합니다. 비판적으로, 그것은 잎에는 효과가 없지만 잎을 파괴하면 적어도 중간체는 스스로 파괴 할 것입니다. 연쇄 파괴와 비슷하지만 이미 연결해야하는 통화 뒤에 숨겨져 있습니다. 그러나 큰 성능 문제는 클래스가 활성화 될 때마다 Observable에서 내부 데이터를 다시 초기화해야 할 수도 있다는 것입니다. 이 과정에 시간이 오래 걸리면 문제가있을 수 있습니다.

    WeakMap을 반복 할 수 있다면 사물을 결합 할 수 있습니다 (이벤트가 없을 때는 약한 것으로 전환하고 이벤트가있을 때는 강하게 전환 할 수 있음). 그러나 실제로하고있는 모든 것은 성능 문제를 다른 사람에게 전하는 것입니다.

    행동에 관해서는 반복 가능한 WeakMap에 즉각적인 성가심이 있습니다. 범위 참조와 조각이있는 함수에 대해 간략하게 언급했습니다. 생성자에서 리스너 'console.log (param)'을 부모에 연결하고 부모를 유지하지 못하는 자식을 인스턴스화하면 자식에 대한 모든 참조를 제거하면 익명 함수가 추가 된 것처럼 완전히 해제 될 수 있습니다 부모는 자식 내에서 아무것도 참조하지 않습니다. 이것은 parent.weakmap.add (child, (param) =>console.log (param))에 대해 수행 할 작업에 대한 질문을 남깁니다. 내 지식으로는 키가 약하지만 값이 아니므로 weakmap.add (object, object)는 영구적입니다. 이것은 재평가해야 할 부분입니다. 나에게 그것은 다른 모든 객체 참조를 처분하면 메모리 누수처럼 보이지만 실제로는 원형 참조로보고 기본적으로 그것을 관리한다고 생각합니다. 익명 함수는 많은 메모리를 낭비하는 일관성을 위해 부모 범위에서 발생하는 개체에 대한 암시 적 참조를 유지하거나 예측 또는 관리하기 어려운 상황에 따라 동작이 달라집니다. 전자는 실제로 불가능하다고 생각합니다. 후자의 경우 단순히 객체를 가져 와서 console.log를 추가하는 클래스에 메소드가있는 경우 함수를 반환하고 참조를 유지하더라도 클래스에 대한 참조를 지우면 해제됩니다. 이 특정 시나리오를 정당화하기 위해 거의 필요하지 않지만 결국 누군가가 각도를 찾아서 반복 가능하고 (키 및 값 참조에서 무료로 제공) HalfWeakMap을 요구하지만 예측할 수 없습니다 (obj = null로 마술로 끝나는 IO, f = null로 마술처럼 끝나는 IO, 둘 다 믿을 수없는 거리에서 가능합니다.)

  • 답변 # 4

    df.destroy(); v1.destroy(); v2.destroy(); df = v1 = v2 = null;

    그렇지 않다. 소멸자의 목적은 리스너를 등록한 항목이 등록을 해제 할 수 있도록하는 것입니다. 객체에 다른 참조가 없으면 가비지 수집됩니다.

    예를 들어, AngularJS에서 컨트롤러가 파괴되면 destroy 이벤트를 수신하고 이에 응답 할 수 있습니다. 소멸자가 자동으로 호출되는 것과 동일하지는 않지만 가깝습니다. 컨트롤러가 초기화 될 때 설정된 리스너를 제거 할 수있는 기회를 제공합니다.

    function Observable() {
        this.events = {open: new WeakSet(), close: new WeakSet()};
    }
    Observable.prototype.on = function(type, f) {
        this.events[type].add(f);
    };
    Observable.prototype.emit = function(type, ...args) {
        this.events[type].forEach(f => f(...args));
    };
    Observable.prototype.off = function(type, f) {
        this.events[type].delete(f);
    };
    
    

  • 답변 # 5

    df = v1 = v2 = null;

    '정리'라는 용어가 더 적합 할 수 있지만 OP와 일치하기 위해 '소멸자'를 사용합니다

    'function'과 'var'로 완전히 자바 스크립트를 작성한다고 가정하자. 그런 다음 모든

    "A destructor wouldn't even help you here. It's the event listeners themselves that still reference your object, so it would not be able to get garbage-collected before they are unregistered."

    를 작성하는 패턴을 사용할 수 있습니다 // Set event listeners, hanging onto the returned listener removal functions function initialize() { $scope.listenerCleanup = []; $scope.listenerCleanup.push( $scope.$on( EVENTS.DESTROY, instance.onDestroy) ); $scope.listenerCleanup.push( $scope.$on( AUTH_SERVICE_RESPONSES.CREATE_USER.SUCCESS, instance.onCreateUserResponse ) ); $scope.listenerCleanup.push( $scope.$on( AUTH_SERVICE_RESPONSES.CREATE_USER.FAILURE, instance.onCreateUserResponse ) ); } // Remove event listeners when the controller is destroyed function onDestroy(){ $scope.listenerCleanup.forEach( remove => remove() ); } 의 프레임 워크 내의 코드 /

    If there is no such mechanism, what is a pattern/convention for such problems?

    / function  격자. Wyzwyz 내  파괴 코드를 수행하십시오.

    지정되지 않은 수명으로 객체 클래스를 작성하고 임의의 범위와 try 에 대한 암시 적 호출로 수명을 지정하는 C ++ 스타일 대신  범위 끝에서 ( catch  이 자바 스크립트 패턴에서 객체는 함수이고 범위는 정확히 함수 범위이며 소멸자는 finally 입니다.  차단합니다.

    지금 생각하고 있다면 finally 때문에이 패턴에 본질적으로 결함이 있습니다. / ~() / ~()  자바 스크립트에 필수적인 비동기 실행을 포함하지 않으면 올바른 것입니다. 다행히 2018 년부터 비동기 프로그래밍 도우미 개체 Wyzwyz  프로토 타입 기능 finally 를 가지고있다  기존 try 에 추가  그리고 catch  프로토 타입 함수. 즉, 소멸자를 필요로하는 비동기 범위는 finally 로 작성할 수 있습니다.   Promise 를 사용한 객체  소멸자로서. 또한 당신은 finally 를 사용할 수 있습니다 / resolve / catch Wyzwyz에서  전화 Promise finally 의 유무에 관계없이 그러나 try 를 알고 있어야합니다. 대기없이 호출하면 범위 외부에서 비동기 적으로 실행되므로 최종 catch 에서 디스크립터 코드를 처리합니다. .

    다음 코드에서 finally  그리고 async function   Promise 가없는 레거시 API 레벨 약속입니다.  함수 인수가 지정되었습니다. 와이즈 비즈  마지막 인수가 정의되었습니다.

    await
    
    

    자바 스크립트의 모든 객체를 함수로 작성한다고 주장하는 것은 아닙니다. 대신, 수명이 다했을 때 소멸자를 실제로 '원하는'스코프가있는 경우를 고려하십시오. 패턴의 Promise 를 사용하여 해당 범위를 함수 객체로 공식화  블록 (또는 then  비동기 범위의 경우 함수)를 소멸자로 사용하십시오. 함수형 객체를 공식화하면 다른 방식으로 작성된 비 함수형 클래스가 필요하지 않았을 가능성이 높습니다. 추가 코드가 필요하지 않아 범위와 클래스를 정렬하는 것이 더 깨끗할 수 있습니다.

    참고 : 다른 사람들이 작성한 것처럼 우리는 소멸자와 가비지 수집을 혼동해서는 안됩니다. C ++ 소멸자는 종종 수동 가비지 콜렉션에 관심이 있지만독점은 아닙니다. 자바 스크립트는 수동 가비지 수집을 필요로하지 않지만 비동기 스코프 수명이 종종 이벤트 리스너 등을 등록 취소하는 장소입니다.

    PromiseA

  • 이전 c - 객체 파일의 다중 정의
  • 다음 oop - Java 핵심 라이브러리의 GoF 디자인 패턴 예