JavaScript로 커스텀 객체를 "적절하게"만드는 방법은 무엇입니까?


471

속성과 메서드가있는 JavaScript 객체를 만드는 가장 좋은 방법이 무엇인지 궁금합니다.

사람이 사용 var self = this하고 self.모든 기능에서 사용하여 범위가 항상 올바른지 확인하는 예제를 보았습니다 .

그런 다음 .prototype속성을 추가 하는 데 사용 하는 예제를 보았지만 다른 사람들은 인라인으로 수행했습니다.

누군가 속성과 메소드가있는 JavaScript 객체의 적절한 예를 제공 할 수 있습니까?


13
"최상의"방법은 없습니다.
Triptych

하지가 self예약 된 단어는? 그렇지 않다면 그것은되어야한다. self현재 창을 참조하는 사전 정의 된 변수 이므로 self === window
Shaz

2
@Shaz : 또는 window같은 브라우저 객체 모델 의 다른 속성보다 더 이상 예약어가 아닙니다 . 식별자를 변수 이름으로 재사용 할 수 있습니다. 그래도 문체 적으로 나는 혼란을 피하는 것을 선호 한다. 궁극적으로 무의미 하지만 그것을 만질 이유가 거의 없습니다. documentframesvar that= thiswindow.self
bobince

7
JS가 축소되면 this로컬 변수 (예 :)에 할당 self하면 파일 크기가 줄어 듭니다.
Patrick Fisher

Classjs 새 링크 : github.com/divio/classjs
Nikola

답변:


889

JavaScript에서 클래스와 인스턴스를 구현하는 데에는 프로토 타입 방식과 클로저 방식의 두 가지 모델이 있습니다. 둘 다 장점과 단점이 있으며 확장 된 변형이 많이 있습니다. 많은 프로그래머와 라이브러리는 언어의 더 추악한 부분을 처리하기 위해 서로 다른 접근 방식과 클래스 처리 유틸리티 기능을 가지고 있습니다.

결과적으로 혼합 회사에서는 메타 클래스가 혼동되어 약간 다르게 동작합니다. 게다가 대부분의 JavaScript 튜토리얼 자료는 끔찍하며 모든 기반을 다루기 위해 어떤 종류의 타협을 제공하여 혼란스러워합니다. (아마도 저자는 혼란 스러울 수 있습니다. JavaScript의 객체 모델은 대부분의 프로그래밍 언어와 매우 다르며, 여러 곳에서 똑바로 잘못 설계되었습니다.)

prototype way로 시작해 봅시다 . 이것은 당신이 얻을 수있는 가장 JavaScript 고유의 것입니다 : 최소한의 오버 헤드 코드가 있으며 instanceof는 이런 종류의 객체의 인스턴스와 작동합니다.

function Shape(x, y) {
    this.x= x;
    this.y= y;
}

이 생성자 함수 new Shapeprototype조회 에 작성하여 작성된 메소드에 메소드를 추가 할 수 있습니다 .

Shape.prototype.toString= function() {
    return 'Shape at '+this.x+', '+this.y;
};

이제 JavaScript가 서브 클래 싱을하는 것을 호출 할 수있는만큼 서브 클래 싱합니다. 우리는 그 이상한 마법 prototype속성 을 완전히 대체하여 그렇게 합니다.

function Circle(x, y, r) {
    Shape.call(this, x, y); // invoke the base class's constructor function to take co-ords
    this.r= r;
}
Circle.prototype= new Shape();

메소드를 추가하기 전에 :

Circle.prototype.toString= function() {
    return 'Circular '+Shape.prototype.toString.call(this)+' with radius '+this.r;
}

이 예제는 작동하며 많은 자습서에서 이와 유사한 코드를 볼 수 있습니다. 그러나 사람, 그것은 new Shape()추악합니다. 실제 모양을 만들지 않아도 기본 클래스를 인스턴스화하고 있습니다. 자바 스크립트가 너무 실수이기 때문에이 간단한 경우 작업에 발생합니다 제로 인수하는 경우, 전달 될 수 있도록 x하고 yundefined및 프로토 타입의에 할당 this.x하고 this.y. 생성자 함수가 더 복잡한 작업을 수행하면 얼굴이 평평 해집니다.

따라서 우리가해야 할 일은 기본 클래스의 생성자 함수를 호출하지 않고 클래스 수준에서 원하는 메서드와 다른 멤버를 포함하는 프로토 타입 객체를 만드는 방법을 찾는 것입니다. 이를 위해 헬퍼 코드 작성을 시작해야합니다. 이것이 내가 아는 가장 간단한 접근법입니다.

function subclassOf(base) {
    _subclassOf.prototype= base.prototype;
    return new _subclassOf();
}
function _subclassOf() {};

프로토 타입의 기본 클래스 멤버를 아무것도하지 않는 새로운 생성자 함수로 전송 한 다음 해당 생성자를 사용합니다. 이제 간단히 쓸 수 있습니다 :

function Circle(x, y, r) {
    Shape.call(this, x, y);
    this.r= r;
}
Circle.prototype= subclassOf(Shape);

new Shape()잘못 대신 . 이제 클래스를 빌드하는 데 허용 가능한 기본 세트가 있습니다.

이 모델에서 고려할 수있는 몇 가지 개선 및 확장이 있습니다. 예를 들어 다음은 구문 설탕 버전입니다.

Function.prototype.subclass= function(base) {
    var c= Function.prototype.subclass.nonconstructor;
    c.prototype= base.prototype;
    this.prototype= new c();
};
Function.prototype.subclass.nonconstructor= function() {};

...

function Circle(x, y, r) {
    Shape.call(this, x, y);
    this.r= r;
}
Circle.subclass(Shape);

두 버전 모두 생성자 함수를 여러 언어로 상속 할 수 없다는 단점이 있습니다. 따라서 서브 클래스가 구성 프로세스에 아무 것도 추가하지 않더라도 기본이 원하는 인수를 사용하여 기본 생성자를 호출해야합니다. 이것은를 사용하여 약간 자동화 할 수 apply있지만 여전히 작성해야합니다.

function Point() {
    Shape.apply(this, arguments);
}
Point.subclass(Shape);

따라서 일반적인 확장은 초기화 항목을 생성자 자체가 아닌 자체 기능으로 나누는 것입니다. 이 함수는 기본에서 잘 상속받을 수 있습니다.

function Shape() { this._init.apply(this, arguments); }
Shape.prototype._init= function(x, y) {
    this.x= x;
    this.y= y;
};

function Point() { this._init.apply(this, arguments); }
Point.subclass(Shape);
// no need to write new initialiser for Point!

이제 우리는 각 클래스에 대해 동일한 생성자 함수 상용구를 얻었습니다. 어쩌면 우리는 그것을 자신의 도우미 함수로 옮길 수 있으므로 예를 들어 대신에 입력하고 계속 Function.prototype.subclass돌리지 않아도 기본 클래스의 함수가 서브 클래스를 뱉어 낼 필요가 없습니다.

Function.prototype.makeSubclass= function() {
    function Class() {
        if ('_init' in this)
            this._init.apply(this, arguments);
    }
    Function.prototype.makeSubclass.nonconstructor.prototype= this.prototype;
    Class.prototype= new Function.prototype.makeSubclass.nonconstructor();
    return Class;
};
Function.prototype.makeSubclass.nonconstructor= function() {};

...

Shape= Object.makeSubclass();
Shape.prototype._init= function(x, y) {
    this.x= x;
    this.y= y;
};

Point= Shape.makeSubclass();

Circle= Shape.makeSubclass();
Circle.prototype._init= function(x, y, r) {
    Shape.prototype._init.call(this, x, y);
    this.r= r;
};

... 약간 더 어색한 구문이기는하지만 다른 언어와 비슷하게 보이기 시작합니다. 원하는 경우 몇 가지 추가 기능을 뿌릴 수 있습니다. 어쩌면 makeSubclass클래스 이름을 기억하고 toString사용하여 기본값 을 제공하고 싶을 수도 있습니다 . 어쩌면 new연산자 없이 실수로 호출되었을 때 생성자를 감지하고 싶을 수도 있습니다 (그렇지 않으면 종종 매우 성가신 디버깅이 발생합니다).

Function.prototype.makeSubclass= function() {
    function Class() {
        if (!(this instanceof Class))
            throw('Constructor called without "new"');
        ...

어쩌면 모든 새 멤버를 전달 makeSubclass하고 프로토 타입에 추가하여 Class.prototype...너무 많이 쓰지 않아도 될 수 있습니다 . 다음과 같은 많은 클래스 시스템이이를 수행합니다.

Circle= Shape.makeSubclass({
    _init: function(x, y, z) {
        Shape.prototype._init.call(this, x, y);
        this.r= r;
    },
    ...
});

객체 시스템에서 바람직하다고 생각할 수있는 많은 잠재적 인 기능이 있으며 아무도 특정 공식에 동의하지 않습니다.


폐쇄 방법 , 그럼. 상속을 전혀 사용하지 않음으로써 JavaScript의 프로토 타입 기반 상속 문제를 피할 수 있습니다. 대신 :

function Shape(x, y) {
    var that= this;

    this.x= x;
    this.y= y;

    this.toString= function() {
        return 'Shape at '+that.x+', '+that.y;
    };
}

function Circle(x, y, r) {
    var that= this;

    Shape.call(this, x, y);
    this.r= r;

    var _baseToString= this.toString;
    this.toString= function() {
        return 'Circular '+_baseToString(that)+' with radius '+that.r;
    };
};

var mycircle= new Circle();

이제 모든 단일 인스턴스에는 Shape고유 한 toString메소드 사본 (및 추가 한 다른 메소드 또는 다른 클래스 멤버)이 있습니다.

각 클래스 멤버의 자체 사본을 가진 모든 인스턴스의 나쁜 점은 효율성이 떨어진다는 것입니다. 많은 수의 서브 클래스 인스턴스를 처리하는 경우 프로토 타입 상속이 더 적합합니다. 또한 기본 클래스의 메소드 호출은 약간 성가시다. 서브 클래스 생성자가 메소드를 덮어 쓰기 전에 메소드가 무엇인지 기억해야한다.

[또한 여기에는 상속이 없기 때문에 instanceof연산자는 작동하지 않습니다. 필요한 경우 클래스 스니핑을위한 고유 한 메커니즘을 제공해야합니다. 프로토 타입 상속과 비슷한 방식으로 프로토 타입 객체를 피들 링 할 수는 있지만 약간 까다 롭고 실제로 instanceof작업하는 것이 가치가 없습니다 .]

고유 한 메서드를 가진 모든 인스턴스에 대한 좋은 점은 메서드가 해당 메서드를 소유 한 특정 인스턴스에 바인딩 될 수 있다는 것입니다. 이것은 this메소드 호출에서 JavaScript의 이상한 바인딩 방법 때문에 유용합니다 . 메소드에서 소유자와 메소드를 분리하면 결과가 나타납니다.

var ts= mycircle.toString;
alert(ts());

그런 다음 this메서드 내부에 예상대로 Circle 인스턴스가되지 않습니다 (실제로 전역 window객체가되어 광범위한 디버깅 문제가 발생합니다). 방법은 촬영과에 할당 될 때 현실에서 이것은 일반적으로 발생 setTimeout, onclick또는 EventListener일반적이다.

프로토 타입 방식을 사용하면 이러한 모든 과제에 대한 마감을 포함해야합니다.

setTimeout(function() {
    mycircle.move(1, 1);
}, 1000);

또는 앞으로 (또는 지금 Function.prototype을 해킹하는 경우) 다음과 같이 할 수도 있습니다 function.bind().

setTimeout(mycircle.move.bind(mycircle, 1, 1), 1000);

인스턴스가 클로저 방식으로 완료되면 바인딩은 인스턴스 변수에 대한 클로저에 의해 무료로 수행됩니다 (일반적으로 that또는 라고 self하지만 개인적으로 후자 self는 JavaScript에서 다른 다른 의미를 가지고 있기 때문에 조언합니다 ). 1, 1위의 스 니펫에서 인수 를 무료로 얻지 못 하므로 다른 클로저가 필요하거나 bind()그렇게 해야하는 경우 여전히 필요 합니다.

클로저 방법에는 많은 변형이 있습니다. 연산자 를 사용하는 대신 this완전히 작성하여 새로 작성하여 that리턴 하는 것을 선호 할 수 있습니다 new.

function Shape(x, y) {
    var that= {};

    that.x= x;
    that.y= y;

    that.toString= function() {
        return 'Shape at '+that.x+', '+that.y;
    };

    return that;
}

function Circle(x, y, r) {
    var that= Shape(x, y);

    that.r= r;

    var _baseToString= that.toString;
    that.toString= function() {
        return 'Circular '+_baseToString(that)+' with radius '+r;
    };

    return that;
};

var mycircle= Circle(); // you can include `new` if you want but it won't do anything

"적절한"방법은 무엇입니까? 양자 모두. 어느 것이 "최고"입니까? 상황에 따라 다릅니다. FOWW 나는 강력한 OO 작업을 할 때 실제 JavaScript 상속을위한 프로토 타입을 만들고 경향이 있으며 간단한 버리기 페이지 효과를 닫습니다.

그러나 두 가지 방법 모두 대부분의 프로그래머에게는 직관적이지 않습니다. 둘 다 잠재적으로 지저분한 변형이 많이 있습니다. 다른 사람의 코드 / 라이브러리를 사용하는 경우 (중간 및 일반적으로 손상된 많은 체계뿐만 아니라) 두 가지를 모두 충족하게됩니다. 일반적으로 인정되는 답변은 없습니다. JavaScript 객체의 놀라운 세계에 오신 것을 환영합니다.

[이것은 JavaScript가 내가 가장 좋아하는 프로그래밍 언어가 아닌 이유 94의 일부였습니다.]


13
"클래스"def에서 객체 인스턴스화에 이르기까지 매우 점진적인 단계별 진행입니다. 우회에 대한 좋은 터치 new.
Crescent Fresh

8
마치 마치 클래스가있는 것처럼 JavaScript를 사용하고 싶기 때문에 JavaScript가 가장 좋아하는 언어가 아닌 것 같습니다.
Jonathan Feinberg

59
물론, 나도 그렇습니다. 클래스와 인스턴스 모델은 오늘날 프로그래머들이 직면하는 일반적인 문제의 대부분에 대해 더 자연스러운 모델입니다. 이론적으로 프로토 타입 기반 상속이 더 유연한 작업 방식을 제공 할 수 있지만 JavaScript는 그 약속을 완전히 이행하지는 않는다는 데 동의합니다. 그 생성자 함수 시스템은 우리에게 두 세계 중 최악의 상황을 제공하여 유연성이나 단순성 프로토 타입이 제공 할 수없는 클래스 급 상속을 어렵게 만듭니다. 요컨대, 똥입니다.
bobince

4
Bob 나는 이것이 멋진 대답이라고 생각합니다. 나는이 두 가지 패턴을 잠시 동안 잡고 있었고 Resig보다 더 간결하게 코딩하고 Crockford보다 더 통찰력을 설명했다고 생각합니다. 더 이상 칭찬은 생각할 수 없습니다 ....
제임스 웨스트 게이트

4
자바 스크립트와 같은 원형 언어에 고전적인 상속 패러다임을 그래프로 그리는 것은 사각형 못과 둥근 구멍 인 것으로 항상 나에게 보였다. 이것이 정말로 필요한 때입니까, 아니면 사람들이 언어를 단순히 언어로 사용하기보다는 원하는 방식으로 언어를 혼동시키는 방법일까요?
slf

90

이 패턴을 상당히 자주 사용합니다. 필자가 필요할 때 상당히 많은 유연성을 얻었습니다. 사용시 Java 스타일 클래스와 비슷합니다.

var Foo = function()
{

    var privateStaticMethod = function() {};
    var privateStaticVariable = "foo";

    var constructor = function Foo(foo, bar)
    {
        var privateMethod = function() {};
        this.publicMethod = function() {};
    };

    constructor.publicStaticMethod = function() {};

    return constructor;
}();

생성시 호출되는 익명 함수를 사용하여 새 생성자 함수를 반환합니다. 익명 함수는 한 번만 호출되므로 비공개 정적 변수를 만들 수 있습니다 (클로저 내부에 있으며 클래스의 다른 멤버에게 표시됨). 생성자 함수는 기본적으로 표준 Javascript 객체입니다. 내부에 개인 속성을 정의하면 공용 속성이 this변수에 첨부됩니다 .

기본적으로이 접근 방식은 Crockfordian 접근 방식과 표준 Javascript 객체를 결합하여보다 강력한 클래스를 만듭니다.

다른 Javascript 객체와 마찬가지로 사용할 수 있습니다.

Foo.publicStaticMethod(); //calling a static method
var test = new Foo();     //instantiation
test.publicMethod();      //calling a method

4
C # 인 "home-turf"에 가깝기 때문에 흥미로워 보입니다. 또한 privateStaticVariable이 실제로 비공개 인 이유를 이해하기 시작했다고 생각합니다 (함수의 범위 내에서 정의되고 참조가있는 한 계속 살아남는가?)
Michael Stum

사용 this하지 않기 때문에 여전히 인스턴스화해야 new합니까?
Jordan Parmer

사실, this 않습니다 에 익숙해 constructor된다 기능, Foo예에.
ShZ

4
여기서 문제는 모든 객체가 모든 개인 및 공공 기능의 자체 사본을 얻는다는 것입니다.
virtualnobi

2
@virtualnobi :이 패턴은 protytpe 메소드를 작성하는 것을 막지 않습니다 : constructor.prototype.myMethod = function () { ... }.
Nicolas Le Thierry d' Ennequin

25

Douglas CrockfordThe Good Parts 에서이 주제에 대해 광범위하게 논의합니다 . 연산자를 사용하여 새 개체를 만들지 않는 것이 좋습니다 . 대신 그는 맞춤형 생성자를 만들 것을 제안합니다. 예를 들어 :

var mammal = function (spec) {     
   var that = {}; 
   that.get_name = function (  ) { 
      return spec.name; 
   }; 
   that.says = function (  ) { 
      return spec.saying || ''; 
   }; 
   return that; 
}; 

var myMammal = mammal({name: 'Herb'});

Javascript에서 함수는 객체이며 new 연산자 와 함께 객체를 구성하는 데 사용할 수 있습니다 . 일반적으로 생성자로 사용되는 함수는 대문자로 시작합니다. 당신은 종종 다음과 같은 것을 본다 :

function Person() {
   this.name = "John";
   return this;
}

var person = new Person();
alert("name: " + person.name);**

새 객체를 인스턴스화하는 동안 new 연산자 를 사용하는 것을 잊어 버린 경우 일반적인 함수 호출 이 이루어 지며 새 객체 대신 전역 객체에 바인딩됩니다.


5
나입니까 아니면 Crockford가 새로운 운영자를 강타하는 데 전혀 의미가 없다고 생각합니까?
meder omuraliev

3
@meder : 당신뿐만 아니라. 적어도 새 운영자에게는 아무런 문제가 없다고 생각합니다. 그리고 암시 거기 newvar that = {};어쨌든.
Tim Down

17
Crockford는 어리석은 노인이며 나는 그에게 많이 동의하지 않지만 적어도 JavaScript를 비판적으로 살펴 보는 것을 장려하고 있으며 그가해야 할 말을 듣는 것이 좋습니다.
bobince

2
@bobince : 동의합니다. 클로저에 대한 그의 글은 약 5 년 전에 많은 것들에 눈을 뜨고, 신중한 접근을 장려합니다.
Tim Down

20
Crockford에 동의합니다. 새 연산자의 문제점은 JavaScript가 "this"의 컨텍스트를 달리 호출하는 함수와 다르게 만드는 것입니다. 적절한 경우 규칙에도 불구하고 개발자가 새로운 것을 사용하는 것을 잊고 대문자를 잊는 등의 코드 기반에서 발생하는 문제가 있습니다. 실용적으로 새 키워드없이 필요한 모든 작업을 수행 할 수 있습니다. 코드에서 더 많은 실패 지점을 소개 하시겠습니까? JS는 클래스 기반 언어가 아닌 프로토 타입입니다. 왜 정적으로 입력 된 언어처럼 행동하기를 원합니까? 나는 확실히하지 않습니다.
Joshua Ramirez

13

bobince의 답변 을 계속하려면

es6에서는 이제 실제로 class

이제 할 수 있습니다 :

class Shape {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }

    toString() {
        return `Shape at ${this.x}, ${this.y}`;
    }
}

따라서 다른 답변과 마찬가지로 원으로 확장하면 다음과 같이 할 수 있습니다.

class Circle extends Shape {
    constructor(x, y, r) {
        super(x, y);
        this.r = r;
    }

    toString() {
        let shapeString = super.toString();
        return `Circular ${shapeString} with radius ${this.r}`;
    }
}

es6에서 약간 더 깔끔하고 읽기가 더 쉽습니다.


다음은 이에 대한 좋은 예입니다.


6

구조체를 사용하여 이런 식으로 할 수도 있습니다.

function createCounter () {
    var count = 0;

    return {
        increaseBy: function(nb) {
            count += nb;
        },
        reset: function {
            count = 0;
        }
    }
}

그런 다음 :

var counter1 = createCounter();
counter1.increaseBy(4);

6
공백이 중요하기 때문에 그런 방식이 마음에 들지 않습니다. 교차 브라우저 호환성을 위해 리턴 후 곱슬은 동일한 행에 있어야합니다.
geowa4

5

또 다른 방법은 http://jsfiddle.net/nnUY4/입니다 (이러한 종류의 객체 생성 및 공개 기능이 특정 패턴을 따르는 지 여부는 알 수 없습니다)

// Build-Reveal

var person={
create:function(_name){ // 'constructor'
                        //  prevents direct instantiation 
                        //  but no inheritance
    return (function() {

        var name=_name||"defaultname";  // private variable

        // [some private functions]

        function getName(){
            return name;
        }

        function setName(_name){
            name=_name;
        }

        return {    // revealed functions
            getName:getName,    
            setName:setName
        }
    })();
   }
  }

  // … no (instantiated) person so far …

  var p=person.create(); // name will be set to 'defaultname'
  p.setName("adam");        // and overwritten
  var p2=person.create("eva"); // or provide 'constructor parameters'
  alert(p.getName()+":"+p2.getName()); // alerts "adam:eva"

4

생성자 호출 중에 "this"를 닫는 트릭을 사용하는 경우, 오브젝트에서 메소드를 호출하지 않으려는 다른 오브젝트에 의해 콜백으로 사용될 수있는 함수를 작성하기위한 것입니다. "범위를 올바르게 만들기"와 관련이 없습니다.

다음은 바닐라 JavaScript 객체입니다.

function MyThing(aParam) {
    var myPrivateVariable = "squizzitch";

    this.someProperty = aParam;
    this.useMeAsACallback = function() {
        console.log("Look, I have access to " + myPrivateVariable + "!");
    }
}

// Every MyThing will get this method for free:
MyThing.prototype.someMethod = function() {
    console.log(this.someProperty);
};

Douglas Crockford 가 JavaScript에 대해 말한 내용 을 읽으면 많은 것을 얻을 수 있습니다 . 존 레식 도 훌륭합니다. 행운을 빕니다!


1
주변 this을 닫으면 "범위를 올바르게 만드는 것"과 관련이 있습니다.
Roatin Marth

3
조나단이 옳습니다. js 함수의 범위는 원하는대로 설계됩니다. self =이 트릭은 특정 인스턴스에 연결하여 다른 컨텍스트에서 호출 될 때 변경되지 않는 한 가지 방법입니다. 그러나 때때로 그것은 당신이 실제로 원하는 것입니다. 상황에 따라 다릅니다.
Marco

나는 당신이 모두 똑같은 말을하고 있다고 생각합니다. 지속 self=this되지는 않지만 this클로저를 통해 "올바른"범위 지정을 쉽게 허용합니다.
Crescent Fresh

2
그렇게하는 이유 = 이것은 생성자 함수에 존재하는 중첩 된 함수에 이것의 범위에 대한 액세스 권한을 부여하기위한 것입니다. 중첩 함수가 생성자 함수 안에 있으면 "this"범위가 전역 범위로 되돌아갑니다.
Joshua Ramirez

4

Closure다목적입니다. bobince 는 객체 생성시 프로토 타입과 클로저 접근 방식 을 잘 요약했습니다 . 그러나 OOP함수형 프로그래밍 방식으로 클로저 를 사용하는 몇 가지 측면을 모방 할 수 있습니다 . 함수는 JavaScript의 객체 임을 기억하십시오 . 함수를 다른 방식으로 객체로 사용하십시오.

폐쇄 예는 다음과 같습니다.

function outer(outerArg) {
    return inner(innerArg) {
        return innerArg + outerArg; //the scope chain is composed of innerArg and outerArg from the outer context 
    }
}

얼마 전에 저는 Closure에 관한 Mozilla의 기사를 보았습니다. "잠금으로 일부 데이터 (환경)를 해당 데이터에서 작동하는 함수와 연관시킬 수 있습니다. 이는 객체 지향 프로그래밍과 명백한 유사점을 가지고 있습니다. )를 하나 이상의 방법으로 " 프로토 타입을 참조하지 않고 클로저와 클래식 OOP의 병렬 처리를 처음 읽은 것은 처음이었습니다.

어떻게?

일부 품목의 부가가치세를 계산한다고 가정합니다. VAT는 신청 기간 동안 안정적으로 유지 될 것입니다. OOP (의사 코드)에서이를 수행하는 한 가지 방법 :

public class Calculator {
    public property VAT { get; private set; }
    public Calculator(int vat) {
        this.VAT = vat;
    }
    public int Calculate(int price) {
        return price * this.VAT;
    }
}

기본적으로 VAT 값을 생성자에 전달하면 계산 메소드가 클로저 를 통해 계산할 수 있습니다 . 이제 클래스 / 생성자를 사용하는 대신 VAT를 인수로 함수에 전달하십시오. 관심있는 유일한 것은 계산 자체이기 때문에 새로운 함수를 반환합니다.

function calculator(vat) {
    return function(item) {
        return item * vat;
    }
}
var calculate = calculator(1.10);
var jsBook = 100; //100$
calculate(jsBook); //110

프로젝트에서 VAT가 계산에 적합한 후보 인 최상위 값을 식별하십시오. 동일한 인수를 계속해서 전달할 때마다 경험적으로 클로저를 사용하여이를 개선 할 수있는 방법이 있습니다. 전통적인 객체를 만들 필요가 없습니다.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures


3

객체 만들기

JavaScript로 객체를 생성하는 가장 쉬운 방법은 다음 구문을 사용하는 것입니다.

var test = {
  a : 5,
  b : 10,
  f : function(c) {
    return this.a + this.b + c;
  }
}

console.log(test);
console.log(test.f(3));

이것은 구조화 된 방식으로 데이터를 저장하는 데 효과적입니다.

그러나 더 복잡한 사용 사례의 경우 종종 함수 인스턴스를 작성하는 것이 좋습니다.

function Test(a, b) {
  this.a = a;
  this.b = b;
  this.f = function(c) {
return this.a + this.b + c;
  };
}

var test = new Test(5, 10);
console.log(test);
console.log(test.f(3));

예를 들어 클래스를 사용하는 방법과 유사하게 동일한 "청사진"을 공유하는 여러 객체를 만들 수 있습니다. 자바.

그러나 프로토 타입을 사용하면 여전히 더 효율적으로 수행 할 수 있습니다.

함수의 다른 인스턴스가 동일한 메소드 또는 특성을 공유 할 때마다 해당 오브젝트의 프로토 타입으로 이동할 수 있습니다. 이렇게하면 함수의 모든 인스턴스가 해당 메서드 나 속성에 액세스 할 수 있지만 모든 인스턴스에 대해 복제 할 필요는 없습니다.

우리의 경우, 메소드 f를 프로토 타입 으로 옮기는 것이 합리적입니다 .

function Test(a, b) {
  this.a = a;
  this.b = b;
}

Test.prototype.f = function(c) {
  return this.a + this.b + c;
};

var test = new Test(5, 10);
console.log(test);
console.log(test.f(3));

계승

JavaScript에서 상속을 수행하는 간단하지만 효과적인 방법은 다음 두 줄을 사용하는 것입니다.

B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;

그것은 이것을하는 것과 비슷합니다 :

B.prototype = new A();

둘의 주요 차이점은의 생성자 A가를 사용할 때 실행되지 않는다는 것입니다 Object.create. 이는 클래스 기반 상속과 더 직관적이고 유사합니다.

다음과 같이 생성자 에 추가하여 A새 인스턴스를 만들 때 생성자를 선택적으로 실행하도록 선택할 수 있습니다 .BB

function B(arg1, arg2) {
    A(arg1, arg2); // This is optional
}

당신의 모든 인수 전달하려는 경우 B에를 A, 당신은 또한 사용할 수 있습니다 Function.prototype.apply():

function B() {
    A.apply(this, arguments); // This is optional
}

다른 객체를의 생성자 체인에 혼합 B하려면 다음 Object.create과 결합 하면됩니다 Object.assign.

B.prototype = Object.assign(Object.create(A.prototype), mixin.prototype);
B.prototype.constructor = B;

데모

function A(name) {
  this.name = name;
}

A.prototype = Object.create(Object.prototype);
A.prototype.constructor = A;

function B() {
  A.apply(this, arguments);
  this.street = "Downing Street 10";
}

B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;

function mixin() {

}

mixin.prototype = Object.create(Object.prototype);
mixin.prototype.constructor = mixin;

mixin.prototype.getProperties = function() {
  return {
    name: this.name,
    address: this.street,
    year: this.year
  };
};

function C() {
  B.apply(this, arguments);
  this.year = "2018"
}

C.prototype = Object.assign(Object.create(B.prototype), mixin.prototype);
C.prototype.constructor = C;

var instance = new C("Frank");
console.log(instance);
console.log(instance.getProperties());


노트

Object.createIE9 +를 포함한 모든 최신 브라우저에서 안전하게 사용할 수 있습니다. Object.assignIE 버전이나 일부 모바일 브라우저에서는 작동하지 않습니다. 하는 것이 좋습니다 polyfill Object.create 및 / 또는 Object.assign당신이 그들을 지원 브라우저를 사용하려는 경우 그 그들을 구현하지 않습니다.

당신의 polyfill을 찾을 수 있습니다 Object.create 여기 와 하나 Object.assign 여기 .


0
var Person = function (lastname, age, job){
this.name = name;
this.age = age;
this.job = job;
this.changeName = function(name){
this.lastname = name;
}
}
var myWorker = new Person('Adeola', 23, 'Web Developer');
myWorker.changeName('Timmy');

console.log("New Worker" + myWorker.lastname);

4
그것이 이미 제공된 수많은 광범위한 답변에 추가되는 것은 무엇입니까?
blm

나는이 답변이 간결하고 구현의 세 부분을 보여주기 때문에 좋아합니다 : 1) 객체 정의, 2) 객체 인스턴스 인스턴스화, 3) 인스턴스 사용-구문 분석 대신 한 눈에 모두 표시 위의 모든 자세한 답변을 통해 (물론, 원하는 모든 세부 사항에 대해 매우 좋은 답변입니다)-간단한 요약의 예
G-Man

0

최신 브라우저를 대상으로 할 수 있으면 Object.defineProperty를 사용할 수 있습니다 .

Object.defineProperty () 메서드는 객체에서 직접 새 속성을 정의하거나 객체의 기존 속성을 수정하고 객체를 반환합니다. 출처 : Mozilla

var Foo = (function () {
    function Foo() {
        this._bar = false;
    }
    Object.defineProperty(Foo.prototype, "bar", {
        get: function () {
            return this._bar;
        },
        set: function (theBar) {
            this._bar = theBar;
        },
        enumerable: true,
        configurable: true
    });
    Foo.prototype.toTest = function () {
        alert("my value is " + this.bar);
    };
    return Foo;
}());

// test instance
var test = new Foo();
test.bar = true;
test.toTest();

데스크탑 및 모바일 호환성 목록을 보려면 Mozilla의 브라우저 호환성 목록을 참조하십시오 . 예, IE9 +는 Safari 모바일뿐만 아니라이를 지원합니다.


0

당신은 또한 이것을 시도 할 수 있습니다

    function Person(obj) {
    'use strict';
    if (typeof obj === "undefined") {
        this.name = "Bob";
        this.age = 32;
        this.company = "Facebook";
    } else {
        this.name = obj.name;
        this.age = obj.age;
        this.company = obj.company;
    }

}

Person.prototype.print = function () {
    'use strict';
    console.log("Name: " + this.name + " Age : " + this.age + " Company : " + this.company);
};

var p1 = new Person({name: "Alex", age: 23, company: "Google"});
p1.print();

0
나에게 잘 어울리는 패턴
var Klass = function Klass() {
    var thus = this;
    var somePublicVariable = x
      , somePublicVariable2 = x
      ;
    var somePrivateVariable = x
      , somePrivateVariable2 = x
      ;

    var privateMethod = (function p() {...}).bind(this);

    function publicMethod() {...}

    // export precepts
    this.var1 = somePublicVariable;
    this.method = publicMethod;

    return this;
};

먼저 생성자의 prototype객체 대신 인스턴스에 메소드를 추가하는 환경 설정을 변경할 수 있습니다 . 상속자 및 데코레이터와 관련 하여 생성자 하이재킹을 매우 자주 사용하기 때문에 생성자 내부에서 메서드를 거의 항상 선언합니다 .

다음은 어느 선언이 작성되는지 결정하는 방법입니다.

  • 컨텍스트 객체에 직접 메소드를 선언하지 마십시오 ( this)
  • 보자 var선언보다 우선 function선언
  • 프리미티브가 객체 ( {}[]) 보다 우선합니다.
  • 보자 public선언보다 우선 private선언
  • 선호 Function.prototype.bind이상 thus, self, vm,etc
  • 다음과 같은 경우를 제외하고 다른 클래스 내에서 클래스를 선언하지 마십시오.
    • 두 사람은 분리 할 수 ​​없다는 것이 분명해야합니다
    • 내부 클래스는 명령 패턴을 구현합니다.
    • Inner 클래스는 싱글 톤 패턴을 구현합니다
    • Inner 클래스는 State 패턴을 구현합니다.
    • 내부 클래스는이를 보증하는 다른 디자인 패턴을 구현합니다.
  • 폐쇄 공간 this어휘 범위 내에서 항상 반환 하십시오.

이것이 도움이되는 이유는 다음과 같습니다.

생성자 하이재킹
var Super = function Super() {
    ...
    this.inherited = true;
    ...
};
var Klass = function Klass() {
    ...
    // export precepts
    Super.apply(this);  // extends this with property `inherited`
    ...
};
모델 디자인
var Model = function Model(options) {
    var options = options || {};

    this.id = options.id || this.id || -1;
    this.string = options.string || this.string || "";
    // ...

    return this;
};
var model = new Model({...});
var updated = Model.call(model, { string: 'modified' });
(model === updated === true);  // > true
디자인 패턴
var Singleton = new (function Singleton() {
    var INSTANCE = null;

    return function Klass() {
        ...
        // export precepts
        ...

        if (!INSTANCE) INSTANCE = this;
        return INSTANCE;
    };
})();
var a = new Singleton();
var b = new Singleton();
(a === b === true);  // > true

보시다시피, thus내가 선호하는 Function.prototype.bind(또는 .call또는 .apply) 이상thus 이므로 실제로 필요하지 않습니다 . 우리 Singleton학급 에서는 더 많은 정보를 전달 하기 thus때문에 이름을 INSTANCE짓지 않습니다 . 를 들어 Model, 우리는 반환 this우리가 사용하는 생성자를 호출 할 수 있도록 .call우리가 그것으로 전달 된 인스턴스를 반환 할 수 있습니다. 중복 적으로 우리는 변수에 변수를 할당 updated했지만 다른 시나리오에서는 유용합니다.

또한, new{brackets} 대신 키워드를 사용하여 객체 리터럴을 구성하는 것을 선호합니다 .

선호하는
var klass = new (function Klass(Base) {
    ...
    // export precepts
    Base.apply(this);  //
    this.override = x;
    ...
})(Super);
선호하지 않음
var klass = Super.apply({
    override: x
});

보다시피 후자는 슈퍼 클래스의 "재정의"속성을 재정의하는 기능이 없습니다.

클래스의 prototype객체 에 메소드를 추가 하면 new키워드 를 사용하거나 사용하지 않고 객체 리터럴을 선호합니다 .

선호하는
Klass.prototype = new Super();
// OR
Klass.prototype = new (function Base() {
    ...
    // export precepts
    Base.apply(this);
    ...
})(Super);
// OR
Klass.prototype = Super.apply({...});
// OR
Klass.prototype = {
    method: function m() {...}
};
선호하지 않음
Klass.prototype.method = function m() {...};

0

제목 이나 문자열 을 사용하여 객체를 선언 할 수 있다고 언급하고 싶습니다 .
각 유형을 호출하는 방법에는 여러 가지가 있습니다. 아래를보십시오 :

var test = {

  useTitle : "Here we use 'a Title' to declare an Object",
  'useString': "Here we use 'a String' to declare an Object",
  
  onTitle : function() {
    return this.useTitle;
  },
  
  onString : function(type) {
    return this[type];
  }
  
}

console.log(test.onTitle());
console.log(test.onString('useString'));


-1

기본적으로 JS에는 클래스 개념이 없으므로 기존 디자인 패턴과 관련된 클래스 생성자로 함수를 사용합니다.

//Constructor Pattern
function Person(name, age, job){
 this.name = name;
 this.age = age;
 this.job = job;
 this.doSomething = function(){
    alert('I am Happy');
}
}

지금까지 JS는 객체를 만들려는 단서가 없으므로 여기에 새로운 키워드가옵니다.

var person1 = new Person('Arv', 30, 'Software');
person1.name //Arv

Ref : 웹 개발자를위한 전문 JS-Nik Z


공감대 수락 : 정당한 사유가 있으면 유익한 정보였으며 개선의 기회를 제공했을 것입니다.
Airwind711

a의 개념 class당신이 사용하여 제목에 언급 된 것처럼 JS에서이 function키워드. 그건 아니 디자인 패턴하지만 언어의 의도적 인 기능입니다. 나는 이것에 대해 당신을 표결하지 않았지만, 다른 사람이 간결하고 질문과 거의 관련이 없기 때문에 다른 사람처럼 보입니다. 이 의견이 도움이 되길 바랍니다.
Cody
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.