JavaScript에서 새로운 연산자 피하기-더 좋은 방법


16

경고 : 이것은 긴 글입니다.

간단하게 유지합시다. JavaScript에서 생성자를 호출 할 때마다 새 연산자 앞에 접두사를 붙이고 싶지 않습니다. 잊어 버리고 코드가 잘못 작동하기 때문입니다.

이 문제를 해결하는 간단한 방법은 다음과 같습니다.

function Make(x) {
  if ( !(this instanceof arguments.callee) )
  return new arguments.callee(x);

  // do your stuff...
}

그러나 변수 번호를 허용하려면 이것이 필요합니다. 이런 주장들 ...

m1 = Make();
m2 = Make(1,2,3);
m3 = Make('apple', 'banana');

첫 번째 즉각적인 해결책은 다음과 같은 '적용'방법 인 것 같습니다 ...

function Make() {
  if ( !(this instanceof arguments.callee) )
    return new arguments.callee.apply(null, arguments);

  // do your stuff
}

그러나 이것은 잘못입니다. 새로운 객체는 apply생성자 에게 전달 되지 않고 메소드에 전달됩니다 arguments.callee.

이제 세 가지 해결책을 생각해 냈습니다. 내 간단한 질문은 어느 것이 가장 좋을까요. 또는 더 좋은 방법이 있다면 알려주십시오.

먼저eval() 생성자를 호출하는 JavaScript 코드를 동적으로 작성 하는 데 사용 합니다.

function Make(/* ... */) {
  if ( !(this instanceof arguments.callee) ) {
    // collect all the arguments
    var arr = [];
    for ( var i = 0; arguments[i]; i++ )
      arr.push( 'arguments[' + i + ']' );

    // create code
    var code = 'new arguments.callee(' + arr.join(',') + ');';

    // call it
    return eval( code );
  }

  // do your stuff with variable arguments...
}

둘째 – 모든 객체에는 __proto__프로토 타입 객체에 대한 '비밀'링크 인 속성이 있습니다. 다행히이 속성은 쓰기 가능합니다.

function Make(/* ... */) {
  var obj = {};

  // do your stuff on 'obj' just like you'd do on 'this'
  // use the variable arguments here

  // now do the __proto__ magic
  // by 'mutating' obj to make it a different object

  obj.__proto__ = arguments.callee.prototype;

  // must return obj
  return obj;
}

세 번째 – 이것은 두 번째 솔루션과 유사합니다.

function Make(/* ... */) {
  // we'll set '_construct' outside
  var obj = new arguments.callee._construct();

  // now do your stuff on 'obj' just like you'd do on 'this'
  // use the variable arguments here

  // you have to return obj
  return obj;
}

// now first set the _construct property to an empty function
Make._construct = function() {};

// and then mutate the prototype of _construct
Make._construct.prototype = Make.prototype;

  • eval 해결책은 서투른 것처럼 보이고 "악한 평가"의 모든 문제와 함께 제공됩니다.

  • __proto__ 해결책은 비표준이며 "Great Browser of mIsERY"는이를 존중하지 않습니다.

  • 세 번째 해결책은 지나치게 복잡해 보입니다.

그러나 위의 세 가지 솔루션을 모두 사용하면 이와 같은 작업을 수행 할 수 있습니다.

m1 = Make();
m2 = Make(1,2,3);
m3 = Make('apple', 'banana');

m1 instanceof Make; // true
m2 instanceof Make; // true
m3 instanceof Make; // true

Make.prototype.fire = function() {
  // ...
};

m1.fire();
m2.fire();
m3.fire();

따라서 위의 솔루션은 변수 no를 허용하는 "true"생성자를 제공합니다. 의 인수 및 필요하지 않습니다 new. 이것에 대한 당신의 취향은 무엇입니까?

-업데이트-

일부는 "그냥 오류를 던져"라고 말했다. 내 대답은 : 우리는 10 개 이상의 생성자로 무거운 앱을 만들고 있으며 모든 생성자가 콘솔에 오류 메시지를 던지지 않고 그 실수를 "똑똑하게"처리 할 수 ​​있다면 훨씬 더 힘들 것이라고 생각합니다.


2
또는 스택 트레이스를 검사 할 때 오류가 발생하면 코드를 수정할 수 있습니다.
ratchet freak

2
이 질문은 Stack Overflow 또는 Code Review 에서 더 나은 것으로 생각 됩니다. 개념적 질문보다는 코드 중심적 인 것 같습니다.
Adam Lear

1
@greengit 대신 오류를 던지면 jslint를 사용하십시오. 당신이 한 경우 경고합니다Make() 없이 new만들기는 대문자 때문에, 따라서 그것은 생성자입니다 가정
Raynos

1
그래서 당신은 이것을 달성하는 더 좋은 방법을 찾고 new있습니까? 아니면 코드를 줄 누군가를 찾고 있습니까? 후자 인 경우 잘못된 사이트를 묻는 것일 수 있습니다. 전자의 경우 새로운 것을 사용하고 오류를 너무 빨리 감지하는 것과 관련된 제안을 무시하고 싶지 않을 수 있습니다 ... 앱이 정말로 "무거운"경우, 마지막으로 원하는 것은 속도를 늦추기위한 과도하게 구축 된 메커니즘입니다. new그것이 얻는 모든 측면에서 꽤 빠릅니다.
Shog9

5
아이러니하게도, 프로그래머 실수를 '똑똑하게'처리하는 것은 JavaScript의 많은 '불량한 부분'을 담당합니다.
Daniel Pratt

답변:


19

먼저 arguments.calleeES5에서는 더 이상 사용되지 않으므로 사용하지 않습니다. 실제 솔루션은 다소 간단합니다.

당신은 전혀 사용하지 않습니다 new.

var Make = function () {
  if (Object.getPrototypeOf(this) !== Make.prototype) {
    var o = Object.create(Make.prototype);
    o.constructor.apply(o, arguments);
    return o;
  }
  ...
}

엉덩이가 옳아 요?

시험 enhance

var Make = enhance(function () {
  ...
});

var enhance = function (constr) {
  return function () {
    if (Object.getPrototypeOf(this) !== constr.prototype) {
      var o = Object.create(constr.prototype);
      constr.apply(o, arguments);
      return o;
    }
    return constr.apply(this, arguments);
  }
}

물론 이것은 ES5가 필요하지만 모두가 ES5 심을 사용 합니까?

다른 js OO 패턴에 관심이있을 수도 있습니다.

옆으로 옵션 2를

var Make = function () {
  var that = Object.create(Make.prototype);
  // use that 

  return that;
}

자신의 ES5 Object.create심을 원한다면 정말 쉽습니다.

Object.create = function (proto) {
  var dummy = function () {};
  dummy.prototype = proto;
  return new dummy;
};

그렇습니다.하지만 여기의 모든 마법은 그 때문입니다 Object.create. 프리 ES5는 어떻습니까? ES5-Shim은 Object.createDUBIOUS로 표시 됩니다.
treecoder

@greengit 다시 읽으면 ES5 shim은 Object.create의 두 번째 인수가 DUBIOUS라고 말합니다. 첫 번째는 괜찮습니다.
Raynos

1
예, 읽었습니다. 그리고 (IF) 그들이 __proto__거기에 어떤 종류의 것을 사용하고 있다고 생각합니다 . 그러면 우리는 여전히 같은 시점에 있습니다. ES5 이전 버전에서는 프로토 타입을 더 쉽게 변경할 수있는 방법이 없습니다. 그러나 어쨌든 귀하의 솔루션은 가장 우아하고 미래 지향적입니다. +1입니다. (투표 제한에 도달 함)
treecoder

1
감사합니다 @psr. 그리고 @Raynos 당신의 Object.create심은 거의 제 3의 해결책이지만 당연히 덜 복잡하고 잘 보입니다.
treecoder

37

간단하게 유지합시다. JavaScript에서 생성자를 호출 할 때마다 새 연산자 앞에 접두사를 붙이고 싶지 않습니다. 잊어 버리고 코드가 잘못 작동하기 때문입니다.

분명한 대답은 new 키워드를 입니다.

언어의 구조와 의미를 바꾸고 있습니다.

내 의견으로는, 미래 코드 관리자를 위해 끔찍한 아이디어입니다.


9
+1 나쁜 코딩 습관으로 언어를 짜는 것이 이상하게 보인다. 분명히 일부 구문 강조 / 체크인 정책은 버그가 발생하기 쉬운 패턴 / 가능한 오타를 피할 수 있습니다.
Stoive

모든 생성자가 당신이 사용했는지 여부를 신경 쓰지 않는 라이브러리를 찾았다면 유지 관리가 가능 new하다는 것을 알았습니다 .
Jack

@Jack이지만 버그를 찾기가 훨씬 더 어렵고 미묘합니다. ;to end 문 을 포함 시키면 자바 스크립트 "배려하지 않음"으로 인해 발생하는 모든 버그를 살펴보십시오 . (자동 세미콜론 삽입)
CaffGeek

@CaffGeek "세미콜론에 관심이없는 자바 스크립트"는 정확하지 않습니다. 세미콜론을 사용하고 사용하지 않는 미묘한 경우가 있고 그렇지 않은 경우가 있습니다. 그게 문제입니다. 허용 된 답변에 제시된 솔루션은 정확히 반대 입니다. 모든 경우new의미 상 동일 하거나 사용 하지 않습니다 . 이 없는 이 불변가 파손되어 더 미묘한 경우가. 그것이 좋은 이유와 그것을 사용하고 싶은 이유입니다.
Jack

14

가장 쉬운 해결책은 기억 new하고 오류를 던져 잊어 버린 것을 분명하게하는 것입니다.

if (Object.getPrototypeOf(this) !== Make.prototype) {
    throw new Error('Remember to call "new"!');
}

무엇을하든 사용하지 마십시오 eval. 비표준 속성 __proto__은 비표준이며 기능이 변경 될 수 있기 때문에 비표준 속성을 사용하지 않는 것이 좋습니다.


도전적으로 .__proto__악마를 피 하십시오
Raynos

3

나는 실제로 이것에 관한 게시물을 썼습니다. http://js-bits.blogspot.com/2010/08/constructors-without-using-new.html

function Ctor() {
    if (!(this instanceof Ctor) {
        return new Ctor(); 
    }
    // regular construction code
}

또한 일반화하여 모든 생성자의 맨 위에 추가 할 필요가 없습니다. 내 게시물을 방문하면 볼 수 있습니다

면책 조항 내 코드에서 이것을 사용하지 않고 교훈적인 가치를 위해 게시했습니다. 나는 잊어 버리는 new것이 쉬운 버그 라는 것을 알았 습니다. 다른 사람들과 마찬가지로 대부분의 코드에는 실제로 이것이 필요하다고 생각하지 않습니다. JS 상속을 생성하기위한 라이브러리를 작성하지 않는 한, 한 곳에서 사용할 수 있고 이미 단순 상속과는 다른 접근법을 사용하고있을 것입니다.


잠재적으로 숨겨진 버그 : 내가 가지고 var x = new Ctor();있고 나중에 x as this와 do를 가지고 있다면 var y = Ctor();이것은 예상대로 작동하지 않습니다.
luiscubal

@luiscubal "나중에 x를 가지고있다"는 말이 확실하지 않으면 thisjsfiddle을 게시하여 잠재적 인 문제를 보여줄 수 있습니까?
Juan Mendes

귀하의 코드는 처음에 생각했던 것보다 조금 더 강력하지만 (복잡하지만 여전히 유효한) 예제를 생각해 냈습니다
luiscubal

@ luiscubal 당신의 요점을 보았지만 실제로는 복잡합니다. 전화해도 괜찮다고 가정하고 있습니다 Ctor.call(ctorInstance, 'value'). 당신이하고있는 일에 대한 유효한 시나리오가 보이지 않습니다. 객체를 구성하려면 var x = new Ctor(val)또는을 사용하십시오 var y=Ctor(val). 유효한 시나리오가 있었다하더라도, 내 주장은 당신이 사용하지 않고 생성자를 가질 수 있다는 것입니다 new Ctor, 당신은 사용 일 생성자 가질 수없는 것을 Ctor.call참조 jsfiddle.net/JHNcR/2을
후안 멘데스

0

이건 어때?

/* thing maker! it makes things! */
function thing(){
    if (!(this instanceof thing)){
        /* call 'new' for the lazy dev who didn't */
        return new thing(arguments, "lazy");
    };

    /* figure out how to use the arguments object, based on the above 'lazy' flag */
    var args = (arguments.length > 0 && arguments[arguments.length - 1] === "lazy") ? arguments[0] : arguments;

    /* set properties as needed */
    this.prop1 = (args.length > 0) ? args[0] : "nope";
    this.prop2 = (args.length > 1) ? args[1] : "nope";
};

/* create 3 new things (mixed 'new' and 'lazy') */
var myThing1 = thing("woo", "hoo");
var myThing2 = new thing("foo", "bar");
var myThing3 = thing();

/* test your new things */
console.log(myThing1.prop1); /* outputs 'woo' */
console.log(myThing1.prop2); /* outputs 'hoo' */

console.log(myThing2.prop1); /* outputs 'foo' */
console.log(myThing2.prop2); /* outputs 'bar' */

console.log(myThing3.prop1); /* outputs 'nope' */
console.log(myThing3.prop2); /* outputs 'nope' */

편집 : 나는 추가하는 것을 잊었다 :

"앱이 정말로 '무거운'경우, 마지막으로 원하는 것은 속도를 늦추는 과도하게 구축 된 메커니즘입니다."

나는 'new'키워드없이 위에 'thing'을 만들 때 그것보다 느리거나 무겁습니다. 그들이 당신에게 잘못을 알려주기 때문에 실수는 당신의 친구입니다. 또한 동료 개발자에게 그들이 뭘 잘못하고 있는지 알려줍니다 .


누락 된 생성자 인수에 대한 값을 발명하면 오류가 발생하기 쉽습니다. 누락 된 경우 속성을 정의하지 않은 상태로 두십시오. 필수 인 경우 누락 된 경우 오류를 처리하십시오. prop1이 바보가된다면 어떻게해야합니까? 진실성에 대해 "nope"를 테스트하는 것은 오류의 원인이 될 것입니다.
JBR 윌킨슨
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.