2014-10-04 2 views
1

내가 JS에 간단한 계산기 응용 프로그램을 만들어 연습을 결심만들기 개인 인스턴스의 변수에 액세스 할 프로토 타입 방법

배경입니다. 첫 번째 단계는 스택 클래스를 구현하는 것이 었습니다. 그러나 공개 프로토 타입 패턴 (?)으로 데이터 캡슐화를 달성 할 때 몇 가지 문제가 발생했습니다.

스택 "클래스":

var Stack = (function() { 

    var Stack = function() { 
     this.arr = []; // accessible to prototype methods but also to public 
    }; 

    Stack.prototype = Object.prototype; // inherits from Object 

    Stack.prototype.push = function(x) { 
     this.arr.push(x); 
    }; 

    Stack.prototype.pop = function() { 
     return this.arr.length ? (this.arr.splice(this.arr.length - 1, 1))[0] : null; 
    }; 

    Stack.prototype.size = function() { 
     return this.arr.length; 
    }; 

    Stack.prototype.empty = function() { 
     return this.arr.length === 0; 
    }; 

    return Stack; 

})(); 

테스트 코드를 : 여기 지금 보이는 방법

var s1 = new Stack(); 
var s2 = new Stack(); 

for(var j = 1, k = 2; j < 10, k < 11; j++, k++) { 

    s1.push(3*j); 
    s2.push(4*k); 

} 

console.log("s1:"); 
while(!s1.empty()) console.log(s1.pop()); 
console.log("s2:"); 
while(!s2.empty()) console.log(s2.pop()); 

문제점

유일한 문제는 arr에 액세스 할 수 있는지입니다 . 어떻게 든 변수 arr을 숨기고 싶습니다. 다음 arr 변수이기 때문에,

var Stack = (function() { 

    var arr = []; // private, but shared by all instances 

    var Stack = function() { }; 
    Stack.prototype = Object.prototype; 

    Stack.prototype.push = function(x) { 
     arr.push(x); 
    }; 

    // etc. 

})(); 

그러나 물론이 방법이 작동하지 않습니다 솔루션에

시도

내 첫번째 생각은 Stack 같은 개인 변수를 만드는 것이 었습니다 은 모든 인스턴스별로을 공유합니다. 따라서 개인용 클래스를 변수로 만들 수있는 좋은 방법이지만 개인 인스턴스 변수는 사용할 수 없습니다.

var Stack = (function() { 

    var pass = String(Math.floor(Math.pow(10, 15 * Math.random())); 
    var arrKey = "arr" + pass; 

    var Stack = function() { 
     this[arrKey] = []; // private instance and accessible to prototypes, but too dirty 
    }; 

    Stack.prototype = Object.prototype; 

    Stack.prototype.push = function(x) { 
     this[arrKey].push(x); 
    }; 

    // etc. 

})(); 

이 솔루션 :

(읽기 쉽도록 확실히 좋은 정말 미친 짓하고) 내가 생각하는 두 번째 방법은 거의 암호처럼 배열 변수에 대한 액세스를 제한하는 임의의 숫자를 사용하는 것입니다 ... 재미 있습니다. 그러나 분명히 내가하고 싶은 것이 아닙니다.

마지막 아이디어는 Crockford does인데, 개인 인스턴스 멤버를 만들 수는 있지만 정의하고있는 공개 프로토 타입 메서드에서이를 볼 수있는 방법은 없습니다.

var Stack = (function() { 

    var Stack = function() { 
     var arr = []; // private instance member but not accessible to public methods 
     this.push = function(x) { arr.push(x); }; // see note [1] 
    } 

})(); 

[1]이 거의 다입니다,하지만 그들은 인스턴스가 생성 될 때마다 다시 얻을 때문에 나는 var Stack = function() {...} 내에서 함수 정의를 갖고 싶어하지 않습니다. 똑똑한 JS 컴파일러는 어떤 조건에 의존하지 않고 this.push을 반복해서 작성하는 대신 기능 코드를 캐시하지 않는다는 것을 깨닫게 될 것입니다. 그러나 피할 수 있다면 추측 캐싱에 의존하지 않을 것입니다.

[질문

프로토 타입 방법에 액세스 할 수있는 개인 인스턴스 멤버를 만들 수있는 방법이 있나요? 어떻게 든 둘러싸고있는 익명의 기능에 의해 형성된 '영향의 거품'을 이용함으로써?

+0

Douglas Crockford는 괄호 안에 함수를 래핑 할 때 대신'})();'dogballs를 고려하고'}());을 추천합니다. http://www.youtube.com/watch?v=eGArABpLy0k –

+0

권한있는 함수 당 모든 인스턴스에 대해 클로저를 만들지 않아야합니다.여기에 더 많은 코드가 있지만 클로저가 적은 protected 패턴이 있습니다. http://stackoverflow.com/questions/21799353/pseudo-classical-inheritance-with-privacy/21800194#21800194 – HMR

+0

@HMR 아래에서 내 새로운 대답을 봅니다. 기본적으로 연결 한 것과 같습니다. 부끄러움 나는 당신이 처음 게시 할 때 그것을 이해하지 못했다. –

답변

1

당신은 당신을 위해 인스턴스를 생성하는 공장 기능을 사용할 수

function createStack() { 
    var arr = []; 

    function Stack() { 

    }; 

    Stack.prototype = Object.prototype; // inherits from Object 

    Stack.prototype.push = function(x) { 
     arr.push(x); 
    }; 

    Stack.prototype.pop = function() { 
     return arr.length ? (this.arr.splice(this.arr.length - 1, 1))[0] : null; 
    }; 

    Stack.prototype.size = function() { 
     return arr.length; 
    }; 

    Stack.prototype.empty = function() { 
     return arr.length === 0; 
    }; 

    return new Stack(); 

} 

당신은 공장 기능의 모든 실행에 클래스를 정의하는 것입니다,하지만 당신은 가장을 정의하려면이 옵션을 변경하여이 문제를 얻을 수 Stack의 생성자 함수 밖에서 arr을 사용하지 않는 부분은 프로토 타입 체인을 더 멀리 올릴 수 있습니다. 개인적으로 나는 프로토 타입 대신에 Object.create을 사용하며 거의 항상이 유형의 객체의 인스턴스를 만들기 위해 팩토리 함수를 사용합니다.

할 수있는 또 다른 방법은 인스턴스를 추적하고 배열 배열을 유지하는 카운터를 유지하는 것입니다.

var Stack = (function() { 

    var data = []; 


    var Stack = function() { 
     this.id = data.length; 
     data[this.id] = []; 
    }; 

    Stack.prototype = Object.prototype; 

    Stack.prototype.push = function(x) { 
     data[this.id].push(x); 
    }; 

    // etc. 

}()); 

이제는 숨겨진 데이터 다차원 배열이 있으며 모든 인스턴스는 해당 배열에서 색인을 유지 관리합니다. 이제 메모리를 관리해야하므로 인스턴스가 더 이상 사용되지 않을 때 배열에있는 내용을 제거 할 수 있도록주의해야합니다. 신중하게 데이터를 폐기하지 않는 한 이런 식으로하지 않는 것이 좋습니다.

1

실제 솔루션

편집 :이 솔루션은 처음 내 위의 질문에 대한 코멘트에 HMR에 의해 게시, 기본적으로 하나 here를 설명한 것과 동일 밝혀졌습니다. 그래서 확실히 새로운 것은 아니지만 잘 작동합니다.

var Stack = (function Stack() { 

    var key = {}; 

    var Stack = function() { 
     var privateInstanceVars = {arr: []}; 
     this.getPrivateInstanceVars = function(k) { 
      return k === key ? privateInstanceVars : undefined; 
     }; 
    }; 

    Stack.prototype.push = function(el) { 
     var privates = this.getPrivateInstanceVars(key); 
     privates.arr.push(el); 
    }; 

    Stack.prototype.pop = function() { 
     var privates = this.getPrivateInstanceVars(key); 
     return privates.arr.length ? privates.arr.splice(privates.arr.length - 1, 1)[0] : null; 
    }; 

    Stack.prototype.empty = function() { 
     var privates = this.getPrivateInstanceVars(key); 
     return privates.arr.length === 0; 
    }; 

    Stack.prototype.size = function() { 
     var privates = this.getPrivateInstanceVars(key); 
     return privates.arr.length; 
    }; 

    Stack.prototype.toString = function() { 
     var privates = this.getPrivateInstanceVars(key); 
     return privates.arr.toString(); 
    }; 

    Stack.prototype.print = function() { 
     var privates = this.getPrivateInstanceVars(key); 
     console.log(privates.arr); 
    } 

    return Stack; 

}()); 


// TEST 

// works - they ARE separate now 
var s1 = new Stack(); 
var s2 = new Stack(); 
s1.push("s1a"); 
s1.push("s1b"); 
s2.push("s2a"); 
s2.push("s2b"); 
s1.print(); // ["s1a", "s1b"] 
s2.print(); // ["s2a", "s2b"] 

// works! 
Stack.prototype.push.call(s1, "s1c"); 
s1.print(); // ["s1a", "s1b", "s1c"] 


// extending the Stack 

var LimitedStack = function(maxSize) { 

    Stack.apply(this, arguments); 
    this.maxSize = maxSize; 

} 

LimitedStack.prototype = new Stack(); 
LimitedStack.prototype.constructor = LimitedStack; 

LimitedStack.prototype.push = function() { 

    if(this.size() < this.maxSize) { 
    Stack.prototype.push.apply(this, arguments); 
    } else { 
    console.log("Maximum size of " + this.maxSize + " reached; cannot push."); 
    } 

    // note that the private variable arr is not directly accessible 
    // to extending prototypes 
    // this.getArr(key) // !! this will fail (key not defined) 

}; 

var limstack = new LimitedStack(3); 

limstack.push(1); 
limstack.push(2); 
limstack.push(3); 
limstack.push(4); // Maximum size of 3 reached; cannot push 
limstack.print(); // [1, 2, 3] 

단점 : 기본적으로 없음, 약간의 추가 코드를

기존 솔루션

(원래 게시 된 첫 번째 방법을 기억 이외의 아래 무엇 실질적으로 다른,하지만 약간의 부주의 한 편집을 통해 어쨌든 잘 작동하지 않으므로 아무런 해를 끼치 지 않습니다.)

여기서 새 개체/프로토 타입은 모든 인스턴스화와 함께 만들어 지지만 차용합니다 정적 코드 privilegedInstanceMethods의 코드 대부분. 아직도 실패한 것은 Stack.prototype.push.call(s1, val)을 할 수있는 능력이지만, 프로토 타입이 객체에 설정되었으므로 점점 가까워지고있는 것 같습니다.

var Stack = (function() { 

    var privilegedInstanceMethods = { 
     push: function(x) { 
      this.arr.push(x); 
     }, 
     pop: function() { 
      return this.arr.length ? this.arr.splice(this.arr.length - 1, 1)[0] : null; 
     }, 
     size: function() { 
      return this.arr.length; 
     }, 
     empty: function() { 
      return this.arr.length === 0; 
     }, 
     print: function() { 
      console.log(this.arr); 
     }, 
    }; 

    var Stack_1 = function() { 
     var Stack_2 = function() { 
      var privateInstanceMembers = {arr: []}; 
      for (var k in privilegedInstanceMethods) { 
       if (privilegedInstanceMethods.hasOwnProperty(k)) { 
        // this essentially recreates the class each time an object is created, 
        // but without recreating the majority of the function code 
        Stack_2.prototype[k] = privilegedInstanceMethods[k].bind(privateInstanceMembers); 
       } 
      } 
     }; 
     return new Stack_2(); // this is key 
    }; 

    // give Stack.prototype access to the methods as well. 
    for(var k in privilegedInstanceMethods) { 
     if(privilegedInstanceMethods.hasOwnProperty(k)) { 
      Stack_1.prototype[k] = (function(k2) { 
       return function() { 
        this[k2].apply(this, arguments); 
       }; 
      }(k)); // necessary to prevent k from being same in all 
     } 
    } 

    return Stack_1; 

}()); 

테스트 :

// works - they ARE separate now 
var s1 = new Stack(); 
var s2 = new Stack(); 
s1.push("s1a"); 
s1.push("s1b"); 
s2.push("s2a"); 
s2.push("s2b"); 
s1.print(); // ["s1a", "s1b"] 
s2.print(); // ["s2a", "s2b"] 

// works! 
Stack.prototype.push.call(s1, "s1c"); 
s1.print(); // ["s1a", "s1b", "s1c"] 

장점 :

  • this.arr가 그렇게
  • 방법 코드뿐만 아니라 인스턴스마다 한 번
  • s1.push(x) 작품을 정의하고 직접 액세스 할 수 없습니다 Stack.prototype.push.call(s1, x)

단점 :

  • 바인드 호출이 모든 인스턴스에 네 개의 새로운 래퍼 함수를 ​​생성한다 (하지만 코드가 내부 푸시/팝/빈/크기 기능마다 만드는 것보다 훨씬 작습니다).
  • 코드는 여기에 짧은 대답, 당신은 조금 희생하지 않고, 모든 것을 가질 수 없다는 것입니다
+0

그건 작동하지 않습니다, s1과 s2는 같은 것을 거기에서 공유하고 있습니다. 그것을 확인하십시오 : http://jsfiddle.net/btipling/59p8fs3g/ –

+0

이 작품 : http://jsfiddle.net/btipling/59p8fs3g/1/ 나는 그것에 대해 좀 더 생각할 것입니다. –

+0

제 의견으로는 흥미로운 아이디어이지만 defineProperties 인수로 얻을 수 없었던 모든 것을 제공하지는 않습니다. 당신은 기본적으로 단지'defineProperties'로 더 적은 작업으로 할 수있는 쓸모없는 속성을 만들고 있습니다. 이것의 부작용은 stack의 인스턴스에서'privilegedInstanceMethods'의'this'가'this'와 같지 않아서 값에 대한 액세스를 공유 할 수 없다는 것입니다. 배열 기능의 하위 집합이 필요한 경우 유용 할 수 있지만 괜찮은 응용 프로그램을 생각할 수는 없습니다. –

1

조금 복잡하다.

Stack는 일종의 struct 느낌, 또는 적어도 상기 배열하는 peek 형태 또는 판독 액세스 중 하나를 가져야 데이터 타입. 어레이가 확장 여부를 여부

은 ... 당신과 당신의 해석까지 물론

입니다 ...하지만 내 포인트 낮은 수준이 같은 간단한 일이, 솔루션은 하나라는 것이다 두 가지 중 : 여기

function Stack() { 
    this.arr = []; 
    this.push = function (item) { this.arr.push(item); } 
    // etc 
} 

또는

function Stack() { 
    var arr = []; 
    var stack = this; 

    extend(stack, { 
     _add : function (item) { arr.push(item); }, 
     _read : function (i) { return arr[i || arr.length - 1]; }, 
     _remove : function() { return arr.pop(); }, 
     _clear : function() { arr = []; } 
    }); 
} 

extend(Stack.prototype, { 
    push : function (item) { this._add(item); }, 
    pop : function() { return this._remove(); } 
    // ... 
}); 

extend 당신이 첫 번째 개체에 개체의 키 -> 발을 복사, 쓸 수있는 단순한 기능 (기본적으로, 나는 계속 입력하지 않아도된다 this. 또는 Class.prototype..

이러한 스타일을 작성하는 데 수십 가지 방법이 있으며, 모두 수정 된 스타일을 사용하여 기본적으로 동일한 결과를 얻을 수 있습니다.

여기에 문지름이 있습니다. 전역 레지스트리를 사용하지 않는 한, 각 인스턴스는 생성시에 고유 한 Symbol (또는 unique-id)이 주어지며, 배열을 등록하는 데 사용됩니다. ... ... 물론, 그 키를 의미합니다. 공개적으로 액세스 할 수 있어야하거나 공용 액세스 객체가 필요한 경우 인스턴스 기반 메서드를 작성하거나 인스턴스화 된 메서드를 사용하여 인스턴스 기반 접근자를 작성하거나 필요한 모든 것을 공용 범위에 넣을 수 있습니다. 향후

,이 같은 일을 할 수있을 것입니다 :
var Stack = (function() { 
    var registry = new WeakMap(); 

    function Stack() { 
     var stack = this, 
      arr = []; 

     registry[stack] = arr; 
    } 

    extend(Stack.prototype, { 
     push (item) { registry[this].push(item); } 
     pop() { return registry[this].pop(); } 
    }); 

    return Stack; 
}()); 

는 거의 모든 출혈 가장자리 브라우저 (메소드 뺀 속기)이 모델을 지원합니다.
그러나 거기에 ES6 -> ES5 컴파일러가 있습니다 (Traceur, 예를 들면).
WeakMaps가 Traceur에서 지원되지 않는다고 생각합니다. ES5 구현은 많은 후프 또는 작동중인 프록시가 필요하지만 Map은 작동합니다 (GC를 직접 처리했다고 가정).

이것은 실용적인 관점에서 Stack만큼 작은 클래스의 경우 실제로 배열을 내부에 유지하려는 경우 각 인스턴스에 고유 한 메서드를 제공 할 수 있습니다.

무해한 초급 클래스의 경우 데이터 숨기기가 무의미 할 수 있으므로 모든 데이터가 공개 될 수 있습니다.

큰 클래스 나 고급 클래스의 경우 프로토 타입 메서드가있는 인스턴스의 접근자를 비교적 깨끗하게 유지합니다. 특히 DI를 사용하여 하위 수준의 기능을 제공하고 인스턴스 접근자가 종속성의 인터페이스에서 자신의 인터페이스를 위해 필요한 모양으로 브리징하는 경우에 특히 그렇습니다.

+0

와우, 매우 상세한 답변을 보내 주셔서 감사합니다. WeakMap에 익숙하지 않아서 소화 할 시간이 조금 걸립니다. 그건 그렇고, 난 그냥 내 대답을 업데이 트하고 그것은 (적어도 피상적으로) 내가 원하는 것을 달성 것으로 보인다. 'arr'은 숨겨져 있고'push' 메소드는's.push (x)'와'Stack.prototype.push.call (s, x)'를 통해 액세스 할 수 있습니다. –

+2

약하게 맵 좋을 것입니다. 기본적으로 전통적인 객체의 경우'{str : val} '대신'Map'을 사용하여 키 {{myObj : val}, myMap [myObj] === val'과 같은 값을 사용할 수 있습니다. WeakMap은 똑같은 일을하지만, 객체가 GCed 될 때 WeakMap에서 제거됩니다 (또는 key/val이 GCed에서 배제되지 않음). 반면에 전통적인 Map (또는 object/array), 객체가 map/set/obj/arr에 여전히 포함되어 있다면, 그 객체는 지울 수 없습니다. – Norguard

+0

WeakMap없이 풀면 기본적으로 var stack = this, arr = [], id = UUID(); registry [id] = arr; this.id = id;'로 변경하고, 각 메소드는'registry [this.id] .pop();'등으로 바뀔 것입니다. 물론 레지스트리를 삭제하라는 지시를 찾을 필요가 있습니다. 배열을 마치면 ... .destroy 메소드와 같이 수동으로 스택을 호출하여 (메모리에 남아 있지 않도록), WeakMap은 스택/배열 인스턴스에 대해 신경 쓰지 않을 것이다. 더 이상 존재하지 않으면. – Norguard

관련 문제