[javascript] 기본 객체를 확장하는 것이 왜 나쁜 습관입니까?

모든 JS 의견 리더는 기본 객체를 확장하는 것은 나쁜 습관이라고 말합니다. 그런데 왜? 성능에 영향을 받습니까? 그들은 누군가가 “잘못된 길”을 두려워하고 열거 가능한 유형을 추가하여 Object실제로 모든 객체의 모든 루프를 파괴합니까?

가지고 TJ Holowaychukshould.js을 예를 들어. 그는 간단한 getter추가Object 하고 모든 것이 잘 작동합니다 ( source ).

Object.defineProperty(Object.prototype, 'should', {
  set: function(){},
  get: function(){
    return new Assertion(Object(this).valueOf());
  },
  configurable: true
});

이것은 실제로 의미가 있습니다. 예를 들어 하나를 확장 할 수 있습니다 Array.

Array.defineProperty(Array.prototype, "remove", {
  set: function(){},
  get: function(){
    return removeArrayElement.bind(this);
  }
});
var arr = [0, 1, 2, 3, 4];
arr.remove(3);

네이티브 형식 확장에 대한 논쟁이 있습니까?



답변

객체를 확장하면 객체의 동작이 변경됩니다.

자신의 코드에서만 사용할 객체의 동작을 변경하는 것이 좋습니다. 그러나 다른 코드에서도 사용되는 동작을 변경하면 다른 코드가 손상 될 위험이 있습니다.

javascript에서 객체 및 배열 클래스에 메소드를 추가 할 때 javascript 작동 방식으로 인해 무언가를 깨뜨릴 위험이 매우 높습니다. 오랜 경험을 통해 이런 종류의 것들이 자바 스크립트에서 모든 종류의 끔찍한 버그를 일으킨다는 것을 알게되었습니다.

사용자 정의 동작이 필요한 경우 기본 클래스를 변경하는 대신 자체 클래스 (아마도 서브 클래스)를 정의하는 것이 훨씬 좋습니다. 그렇게하면 아무것도 깨지 않을 것입니다.

클래스를 서브 클래 싱하지 않고 클래스의 작동 방식을 변경하는 기능은 좋은 프로그래밍 언어의 중요한 기능이지만 거의 사용하지 않고주의해서 사용해야합니다.


답변

성능 저하와 같이 측정 가능한 단점은 없습니다. 적어도 아무도 언급하지 않았습니다. 이것이 개인적인 취향과 경험의 문제입니다.

주요 프로 논쟁 : 더보기 좋고 직관적입니다 : 구문 설탕. 유형 / 인스턴스 별 기능이므로 해당 유형 / 인스턴스에 특별히 바인딩되어야합니다.

주요 반대 주장 : 코드가 간섭 할 수 있습니다. lib A가 함수를 추가하면 lib B의 함수를 덮어 쓸 수 있습니다. 코드가 매우 쉽게 깨질 수 있습니다.

둘 다 요점이 있습니다. 유형을 직접 변경하는 두 개의 라이브러리에 의존하는 경우 예상되는 기능이 동일하지 않기 때문에 코드가 깨질 가능성이 큽니다. 나는 그것에 동의합니다. 매크로 라이브러리는 기본 유형을 조작해서는 안됩니다. 그렇지 않으면 개발자로서 당신은 실제로 무대 뒤에서 무슨 일이 일어나고 있는지 알 수 없습니다.

그리고 이것이 jQuery, 밑줄 등과 같은 라이브러리를 싫어하는 이유입니다. 그들은 절대적으로 잘 프로그램되어 있고 매력처럼 작동하지만 그들은 크다 . 10 % 만 사용하고 약 1 %를 이해하십시오.

그렇기 때문에 본인 이 실제로 필요한 것만 필요한 원자 적 접근 방식을 선호 합니다. 이런 식으로, 당신은 항상 무슨 일이 일어나는지 알고 있습니다. 마이크로 라이브러리는 원하는 작업 만 수행하므로 간섭하지 않습니다. 어떤 기능이 추가되는지 최종 사용자에게 알리는 기본 유형 확장은 안전하다고 간주 될 수 있습니다.

TL; DR 의심스러운 경우 기본 유형을 확장하지 마십시오. 최종 사용자가 해당 동작을 알고 원하는 경우 100 % 확신 할 경우에만 기본 유형을 확장하십시오. 에서 는 기존의 인터페이스를 깰 것 같은 경우, 기본 유형의 기존 기능을 조작 할 수 있습니다.

유형을 확장하기로 결정한 경우 Object.defineProperty(obj, prop, desc) ; 할 수 없으면 type의을 사용하십시오 prototype.


ErrorJSON을 통해 보낼 수 있기를 원했기 때문에 원래이 질문을했습니다 . 그래서 나는 그것들을 묶는 방법이 필요했습니다. error.stringify()보다 나은 느낌 errorlib.stringify(error); 두 번째 구조에서 알 수 있듯이, 나는 스스로 작동 errorlib하지 않고 작동 하고 error있습니다.


답변

제 생각에는 나쁜 습관입니다. 주된 이유는 통합입니다. 인용 해야하는 should.js 문서 :

OMG IT는 개체를 확장합니까 ???!?! @ 예, 그렇습니다. 단일 getter를 사용하면 코드가 중단되지 않습니다.

글쎄, 저자는 어떻게 알 수 있습니까? 내 조롱 프레임 워크가 동일한 경우 어떻게합니까? 내 약속 lib가 동일하게 수행하면 어떻게됩니까?

당신이 당신의 자신의 프로젝트에서하고 있다면 괜찮습니다. 그러나 라이브러리의 경우 나쁜 디자인입니다. Underscore.js는 올바른 방법으로 수행 된 작업의 예입니다.

var arr = [];
_(arr).flatten()
// or: _.flatten(arr)
// NOT: arr.flatten()


답변

사례별로 살펴보면 일부 구현이 허용 될 수 있습니다.

String.prototype.slice = function slice( me ){
  return me;
}; // Definite risk.

이미 작성된 메소드를 겹쳐 쓰면 해결하는 것보다 더 많은 문제가 발생하므로이 방법을 피하기 위해 많은 프로그래밍 언어에서 일반적으로 언급됩니다. 개발자는 기능이 변경되었음을 어떻게 알 수 있습니까?

String.prototype.capitalize = function capitalize(){
  return this.charAt(0).toUpperCase() + this.slice(1);
}; // A little less risk.

이 경우 알려진 핵심 JS 메소드를 덮어 쓰지 않지만 String을 확장합니다. 이 게시물의 한 주장은 새로운 개발자 가이 방법이 핵심 JS의 일부인지 또는 문서를 찾을 수 있는지 아는 방법을 언급했습니다. 핵심 JS String 객체가 capitalize 라는 메소드를 가져 오면 어떻게 될까요? 될까요?

다른 라이브러리와 충돌 할 수있는 이름을 추가하는 대신 모든 개발자가 이해할 수있는 회사 / 앱 특정 수정자를 사용하면 어떻게됩니까?

String.prototype.weCapitalize = function weCapitalize(){
  return this.charAt(0).toUpperCase() + this.slice(1);
}; // marginal risk.

var myString = "hello to you.";
myString.weCapitalize();
// => Hello to you.

다른 객체를 계속 확장하면 모든 개발자가 (이 경우) 우리 와 야생에서 만나게됩니다. 회사 / 앱 특정 확장 프로그램임을 알립니다.

이름 충돌을 제거하지는 않지만 가능성을 줄입니다. 핵심 JS 객체를 확장하는 것이 본인 및 / 또는 팀을위한 것이라고 판단되면 아마도 이것이 당신을위한 것입니다.


답변

내장 프로토 타입을 확장하는 것은 실제로 나쁜 생각입니다.그러나 ES2015에는 원하는 동작을 얻는 데 사용할 수있는 새로운 기술이 도입되었습니다.

활용 WeakMap 내장의 프로토 타입과 연관 유형들

다음 구현은 프로토 타입 NumberArray프로토 타입을 전혀 건드리지 않고 확장합니다 .

// new types

const AddMonoid = {
  empty: () => 0,
  concat: (x, y) => x + y,
};

const ArrayMonoid = {
  empty: () => [],
  concat: (acc, x) => acc.concat(x),
};

const ArrayFold = {
  reduce: xs => xs.reduce(
   type(xs[0]).monoid.concat,
   type(xs[0]).monoid.empty()
)};


// the WeakMap that associates types to prototpyes

types = new WeakMap();

types.set(Number.prototype, {
  monoid: AddMonoid
});

types.set(Array.prototype, {
  monoid: ArrayMonoid,
  fold: ArrayFold
});


// auxiliary helpers to apply functions of the extended prototypes

const genericType = map => o => map.get(o.constructor.prototype);
const type = genericType(types);


// mock data

xs = [1,2,3,4,5];
ys = [[1],[2],[3],[4],[5]];


// and run

console.log("reducing an Array of Numbers:", ArrayFold.reduce(xs) );
console.log("reducing an Array of Arrays:", ArrayFold.reduce(ys) );
console.log("built-ins are unmodified:", Array.prototype.empty);

보시다시피이 기술로 원시 프로토 타입까지 확장 할 수 있습니다. 지도 구조를 사용하고Object ID를 유형을 내장 프로토 타입과 연결합니다.

내 예제는 빈 인수를 만드는 방법과이 자체와 요소를 배열 자체의 요소에서 연결하는 방법에 대한 정보를 추출 할 수 있기 때문에 단일 인수 reduce로만 필요한 함수를 활성화 Array합니다.

Map약한 참조는 가비지 수집되지 않는 내장 프로토 타입을 나타낼 때 의미가 없으므로 일반 유형 을 사용할 수 있습니다 . 그러나 a WeakMap는 반복 할 수 없으며 올바른 키가 없으면 검사 할 수 없습니다. 이것은 모든 유형의 반사를 피하고 싶기 때문에 원하는 기능입니다.


답변

하지 말아야 할 또 하나의 이유네이티브 객체를 확장 :

우리는 prototype.js 를 사용하는 Magento를 사용합니다 하고 네이티브 객체에 많은 것들을 확장하는 . 이것은 새로운 기능을 도입하기로 결정하고 큰 문제가 시작될 때까지 잘 작동합니다.

웹 컴포넌트 중 하나에 웹 컴포넌트를 도입 했으므로 webcomponents-lite.js는 IE의 전체 (네이티브) 이벤트 객체를 대체하기로 결정합니다 (이유는 무엇입니까?). 이것은 물론 prototype.js 를 깨뜨려 마 젠토를 깨뜨린 다. (문제를 찾을 때까지 많은 시간을 투자하여 추적 할 수 있습니다)

문제가 마음에 드시면 계속하십시오!


답변

적어도 응용 프로그램 내 에서이 작업을 수행하지 않는 세 가지 이유를 알 수 있습니다.

  1. 잘못하면 확장 가능한 유형의 모든 객체에 열거 가능한 속성이 실수로 추가됩니다. Object.defineProperty기본적으로 열거 할 수없는 속성을 만드는을 사용하여 쉽게 해결할 수 있습니다.
  2. 사용중인 라이브러리와 충돌 할 수 있습니다. 부지런히 피할 수 있습니다. 프로토 타입에 무언가를 추가하기 전에 사용하는 라이브러리가 정의한 방법을 확인하고 업그레이드시 릴리스 정보를 확인한 다음 응용 프로그램을 테스트하십시오.
  3. 이후 버전의 기본 JavaScript 환경과 충돌이 발생할 수 있습니다.

포인트 3은 틀림없이 가장 중요한 것입니다. 당신은 때문에 프로토 타입 확장, 사용하는 라이브러리와 충돌을 일으키지 않는 것을 테스트를 통해 확인 할 수 있습니다 당신은 당신이 사용하는 도서관 것을 결정한다. 코드가 브라우저에서 실행된다고 가정하면 기본 객체의 경우도 마찬가지입니다. Array.prototype.swizzle(foo, bar)오늘 정의 하고 내일 Google Array.prototype.swizzle(bar, foo)이 Chrome에 추가 하면 혼란스러워하는 동료들과 함께 .swizzle행동이 왜 MDN에 문서화 된 내용과 일치하지 않는지 궁금해하는 경향이 있습니다.

mootools가 자신이 소유하지 않은 프로토 타입을 처리하는 방법에 대한 이야기웹을 중단하지 않기 위해 ES6 메소드의 이름을 변경하도록 강요했습니다 .

네이티브 객체에 추가 된 메소드 (예 : Array.prototype.myappSwizzle대신에 정의 Array.prototype.swizzle)에 응용 프로그램 특정 접두어를 사용하면 피할 수 있지만, 추악합니다. 프로토 타입을 보강하는 대신 독립형 유틸리티 기능을 사용하여 해결할 수 있습니다.