source

네이티브 오브젝트를 확장하는 것이 나쁜 관행인 이유는 무엇입니까?

ittop 2023. 10. 24. 21:35
반응형

네이티브 오브젝트를 확장하는 것이 나쁜 관행인 이유는 무엇입니까?

JS의 모든 오피니언 리더는 고유 객체를 확장하는 것은 나쁜 관행이라고 말합니다.하지만 왜 그랬을까?공연 대박 났나요?그들은 누군가가 그것을 "잘못된 방식"으로 하고 열거할 수 있는 유형을 추가하는 것을 두려워합니까?Object, 모든 물체의 고리를 파괴할 수 있습니까?

TJ Holowaychukshould.js를 예로 들어 보겠습니다.그는 간단한 게터를 추가합니다.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);

네이티브 유형을 확장하는 것에 반대하는 주장이 있습니까?

개체를 확장할 때 개체의 동작을 변경합니다.

자신의 코드로만 사용될 개체의 동작을 변경해도 좋습니다.그러나 다른 코드에서도 사용되는 동작을 변경하면 해당 다른 코드를 위반할 위험이 있습니다.

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

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

서브클래스 없이 클래스가 작동하는 방식을 변경할 수 있는 능력은 좋은 프로그래밍 언어의 중요한 특징이지만 거의 사용하지 않고 주의 깊게 사용해야 합니다.

성능적인 히트와 같은 측정 가능한 단점은 없습니다.적어도 아무도 언급하지 않았습니다.그래서 이것은 개인적인 선호와 경험의 문제입니다.

주요 논거:그것은 더 좋아 보이고 더 직관적입니다: 구문 설탕.유형/인스턴스별 함수이므로 해당 유형/인스턴스에 특별히 바인딩되어야 합니다.

주요 모순점: 코드가 간섭할 수 있습니다.lib A가 함수를 추가하면 lib B의 함수를 덮어쓸 수 있습니다.이것은 코드를 아주 쉽게 깰 수 있습니다.

둘 다 일리가 있습니다.유형을 직접 변경하는 두 개의 라이브러리에 의존할 경우 예상되는 기능이 동일하지 않기 때문에 코드가 깨질 가능성이 높습니다.그것에 전적으로 동의합니다.매크로 라이브러리는 네이티브 유형을 조작해서는 안 됩니다.그렇지 않으면 개발자로서 당신은 정말로 막후에서 무슨 일이 일어나고 있는지 절대 알 수 없을 것입니다.

그리고 그것이 제가 jQuery, 언더스코어 등과 같은 립을 싫어하는 이유입니다.오해하지 마세요. 그들은 완전히 프로그램이 잘 되어있고 매력적으로 작용하지만 덩치가 큽니다.10%만 사용하고 1% 정도는 이해합니다.

그렇기 때문에 저는 원자론적 접근법을 선호합니다. 정말 필요한 것만 요구하는 것이죠.이렇게 하면 무슨 일이 일어나는지 항상 알 수 있습니다.마이크로 도서관은 당신이 원하는 일만 해서 방해가 되지 않습니다.어떤 기능이 추가되었는지 최종 사용자가 알 수 있도록 하는 상황에서 네이티브 유형을 확장하는 것은 안전한 것으로 간주될 수 있습니다.

TL;DR 의심스러울 때는 네이티브 타입을 확장하지 마십시오.100% 확실한 경우에만 네이티브 유형을 확장하여 최종 사용자가 해당 동작을 알고 싶어할 수 있습니다.어떤 경우에도 네이티브 유형의 기존 기능을 조작하지 않으면 기존 인터페이스가 손상됩니다.

유형을 확장하기로 결정한 경우 를 사용하고, 확장할 수 없는 경우 유형의 것을 사용합니다.prototype.


저는 원래 제가 원해서 이런 질문을 하게 되었습니다.ErrorJSON을 통해 보낼 수 있습니다.그래서 저는 그들을 끈으로 묶을 방법이 필요했습니다.error.stringify()보다 훨씬 기분이 좋았습니다errorlib.stringify(error); 두 번째 구조가 시사하는 바와 같이, 제가 수술하고 있는 것은errorlib하지도 않고error그 자체.

제 생각에 그건 나쁜 관행입니다.가장 큰 이유는 통합입니다.should.js 문서 인용:

OMGIT이 개체를 확장 ??!?!@ 네, 그렇습니다, 한 번의 게터로 해야 하고, 아니요, 당신의 코드를 깨뜨리지는 않을 것입니다.

자, 작가가 어떻게 알 수 있을까요?만약 내 조롱의 틀이 똑같이 적용된다면요?내가 한 약속들이 똑같이 하면 어쩌죠?

본인이 직접 하는 프로젝트라면 괜찮습니다.하지만 도서관 입장에서는 좋지 않은 디자인입니다.언더스코어.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유형을 내장형 프로토타입과 연관시키다

다음 구현은 다음과 같이 확장합니다.Number그리고.Array손을 전혀 대지 않고 시제품을 제작할 수 있습니다.

// 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기본 제공 프로토타입과 유형을 연결할 수 있는 아이덴티티입니다.

내 예는 다음을 가능하게 합니다.reduce오직 기대하는 함수.Array그것의 단일 인수로서, 그것은 빈 누적기를 만드는 방법과 배열 자체의 요소로부터 이 누적기와 요소를 연결하는 방법을 정보를 추출할 수 있기 때문입니다.

제가 정상적인 방법을 사용할 수도 있었다는 것을 알아두세요.Maptype, 약한 참조는 결코 쓰레기를 수집하지 않는 내장 프로토타입만을 나타내는 경우에는 의미가 없기 때문입니다., a.WeakMap올바른 키를 가지고 있지 않으면 검사할 수 없고 검사할 수 없습니다.이것은 원하는 기능입니다. 왜냐하면 저는 어떤 형태의 타입 반사도 피하고 싶기 때문입니다.

native Objects를 확장하지 말아야 하는 또 다른 이유:

우리는 prototype.js를 사용하고 native Objects에 많은 것을 확장하는 마젠토를 사용합니다.새 기능을 도입하기로 결정할 때까지 이 기능은 잘 작동하며, 여기서 큰 문제가 발생하기 시작됩니다.

페이지 중 하나에 웹 구성 요소가 소개되어 있으므로 webcomponents-lite.js는 IE의 전체(원본) 이벤트 개체를 대체하기로 결정합니다(왜?).이것은 당연히 prototype.js를 부수고, 이것은 다시 마젠토를 부수는 것입니다.(문제를 찾을 때까지 많은 시간을 투자하여 문제를 추적할 수 있습니다.)

만약 여러분이 문제를 좋아한다면, 계속 하세요!

이렇게 하지 말아야 할 세 가지 이유(적어도 애플리케이션 내에서)를 알 수 있는데, 이 중 두 가지만 기존 답변에서 다루고 있습니다.

  1. 잘못하면 확장 형식의 모든 개체에 열거형 속성을 실수로 추가하게 됩니다.를 사용하여 쉽게 해결할 수 있으므로 기본적으로 숫자가 아닌 속성을 만듭니다.
  2. 사용 중인 라이브러리와 충돌이 발생할 수 있습니다.프로토타입에 무언가를 추가하기 전에 라이브러리에서 정의하는 방법을 확인하고 업그레이드할 때 릴리스 노트를 확인하고 응용프로그램을 테스트하는 것이 중요합니다.
  3. 네이티브 자바스크립트 환경의 향후 버전과 충돌이 발생할 수 있습니다.

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

(무툴이 가지고 있지 않은 프로토타입을 만지작거리면서 어떻게 ES6 방식으로 웹이 끊기지 않도록 이름을 바꾸게 되었는지에 대한 이야기도 참조하십시오.

네이티브 개체에 추가되는 메서드에 대해 응용 프로그램별 접두사를 사용하여 이를 피할 수 있습니다(예: 정의).Array.prototype.myappSwizzle대신에Array.prototype.swizzle프로토타입을 증강하는 대신 독립 실행형 유틸리티 기능을 사용하면 해결이 가능합니다.

퍼프도 이유입니다.때로는 키를 넘겨야 할 수도 있습니다.이를 수행하는 몇 가지 방법이 있습니다.

for (let key in object) { ... }
for (let key in object) { if (object.hasOwnProperty(key) { ... } }
for (let key of Object.keys(object)) { ... }

주로 사용합니다.for of Object.keys()올바른 작업을 수행하고 비교적 간결하기 때문에 수표를 추가할 필요가 없습니다.

하지만 훨씬 느려요.

for-of vs for-in perf results

그 이유를 추측해 보는 것만으로도Object.keys느리다는 것은 분명하지만,Object.keys()할당을 해야 합니다.실제로 AFAIK는 이후 모든 키의 복사본을 할당해야 합니다.

  const before = Object.keys(object);
  object.newProp = true;
  const after = Object.keys(object);

  before.join('') !== after.join('')

JS 엔진이 일종의 불변 키 구조를 사용해서Object.keys(object)불변 키를 반복하는 참조를 반환합니다.object.newProp완전히 새로운 불변의 키 객체를 생성하지만 무엇이든 간에, 분명히 최대 15배의 속도로 느려집니다.

체크까지hasOwnProperty최대 2배 느린 속도입니다.

이 모든 것의 요점은 당신이 완벽한 민감한 코드를 가지고 있고 키를 루프오버해야 한다면 당신이 사용할 수 있기를 원한다는 것입니다.for in전화할 필요도 없이hasOwnProperty. 수정하지 않은 경우에만 이 작업을 수행할 수 있습니다.Object.prototype

를 사용하는 경우 주의하십시오.Object.defineProperty당신이 추가한 것들이 열거할 수 없다면, 그것들은 위의 경우에 자바스크립트의 동작에 영향을 미치지 않을 것입니다.불행히도, 적어도 크롬 83에서는 성능에 영향을 미칩니다.

enter image description here

나는 단지 모든 퍼펙트 문제가 나타나도록 하기 위해 숫자가 없는 속성을 3000개 추가했습니다.30개의 특성만 가지고도 검정이 너무 가까워서 성능에 영향이 있는지 여부를 알 수 없습니다.

https://jsperf.com/does-adding-non-enumerable-properties-affect-perf

Firefox 77과 Safari 13.1은 증강 클래스와 비증강 클래스 간의 성능 차이를 보이지 않았습니다. 아마도 v8은 이 영역에서 수정될 것이고 성능 문제를 무시할 수 있습니다.

하지만, 에 대한 이야기도 덧붙이겠습니다.짧은 버전은 유명한 도서관인 무툴즈가 직접 만든 것입니다.Array.prototype.flatten. 표준위원회가 원주민을 추가하려고 했을 때Array.prototype.flatten그들은 많은 사이트를 깨지 않고서는 불가능한 것을 발견했습니다.브레이크를 알게 된 개발자들은 es5 방식의 이름을 제안했습니다.smoosh농담으로. 근데 사람들은 그게 농담이라는 걸 이해하지 못하고 기겁을 했어요.그들은 결정했습니다.flat대신에flatten

이 이야기의 교훈은 토종 물건을 확장해서는 안 된다는 것입니다.그렇게 하면 동일한 내용이 깨지는 문제가 발생할 수 있으며 특정 라이브러리가 MooTools만큼 인기를 끌지 않는 한 브라우저 공급업체는 사용자가 야기한 문제를 해결하지 못할 것입니다.만약 당신의 도서관이 그렇게 인기를 끌게 된다면, 당신이 야기한 문제를 다른 모든 사람들이 해결하도록 강요하는 것은 비열한 짓일 것입니다.따라서 네이티브 개체를 확장하지 마십시오.

편집됨:

시간이 지나고 나는 마음을 바꾸었습니다 - 원형 오염은 좋지 않습니다 (그러나 게시물의 끝에 예제를 남겼습니다).

위와 아래 게시물에 언급된 것보다 훨씬 더 많은 문제를 일으킬 수 있습니다.

JS/TS 세계 전체에 걸쳐 단일 표준을 갖는 것이 중요합니다(npmjs가 일관성 있게 적용되면 좋을 것입니다).

이전에 저는 bull**it를 작성하고 사람들에게 도서관에서 이를 수행하도록 권장했습니다. 죄송합니다.

Jeff Clayton 제안도 좋은 것 같습니다. 메서드 이름 접두사는 패키지 이름 뒤에 언더스코어(예:Array.prototype.<custom>_Flatten(기존 패키지 접두사가 향후 기존 패키지가 될 수 있음)


원래 답변의 일부:

저는 개인적으로 네이티브 메소드를 확장하고 있었습니다. 단지 사용하고 있습니다.x내 라이브러리의 접두사(서드파티 라이브러리 확장 시에도 사용).

TS만:

declare global {
  interface Array<T> {
    xFlatten(): ExtractArrayItemsRecursive<T>;
  }
}

JS+TS:

Array.prototype.xFlatten = function() { /*...*/ }

언급URL : https://stackoverflow.com/questions/14034180/why-is-extending-native-objects-a-bad-practice

반응형