เหตุใดการขยายวัตถุพื้นเมืองจึงเป็นการปฏิบัติที่ไม่ดี


136

ผู้นำความคิดเห็นของ JS ทุกคนกล่าวว่าการขยายวัตถุพื้นเมืองเป็นแนวทางปฏิบัติที่ไม่ดี แต่ทำไม? เราได้รับความนิยมหรือไม่? พวกเขากลัวว่าจะมีใครบางคนทำ "ผิดวิธี" และเพิ่มประเภทที่อธิบายได้เพื่อObjectทำลายลูปทั้งหมดบนวัตถุใด ๆ หรือไม่?

ใช้TJ Holowaychuk 's should.jsตัวอย่างเช่น เขาเพิ่ม getter ง่ายไปObjectและทุกอย่างทำงานได้ดี ( แหล่งที่มา )

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);

มีข้อโต้แย้งใด ๆ เกี่ยวกับการขยายประเภทเนทีฟหรือไม่?


9
คุณคาดหวังว่าจะเกิดอะไรขึ้นเมื่อต่อมาออบเจ็กต์เนทีฟถูกเปลี่ยนเพื่อรวมฟังก์ชัน "ลบ" ที่มีความหมายอื่นเป็นของคุณเอง คุณไม่ได้ควบคุมมาตรฐาน
ta.speot.is

1
ไม่ใช่ประเภทพื้นเมืองของคุณ เป็นประเภทพื้นเมืองของทุกคน

6
"พวกเขากลัวว่าใครบางคนทำ" ผิดวิธี "และเพิ่มประเภทที่นับได้ให้กับ Object ซึ่งจะทำลายลูปทั้งหมดบนวัตถุใด ๆ หรือไม่" : ใช่. ย้อนกลับไปในสมัยที่ความคิดเห็นนี้ก่อตัวขึ้นเป็นไปไม่ได้ที่จะสร้างคุณสมบัติที่ไม่สามารถนับได้ ตอนนี้สิ่งต่างๆอาจแตกต่างกันไปในเรื่องนี้ แต่ลองนึกภาพทุกไลบรารีเพียงแค่ขยายออบเจ็กต์ดั้งเดิมตามที่ต้องการ มีเหตุผลว่าทำไมเราจึงเริ่มใช้เนมสเปซ
Felix Kling

5
สำหรับสิ่งที่ควรค่าแก่ "ผู้นำทางความคิดเห็น" ตัวอย่างเช่น Brendan Eich คิดว่าการขยายต้นแบบพื้นเมืองเป็นเรื่องที่ดี
Benjamin Gruenbaum

2
Foo ไม่ควรเป็นสากลในทุกวันนี้เรามีรวมถึง requirejs, commonjs ฯลฯ
Jamie Pate

คำตอบ:


128

เมื่อคุณขยายวัตถุคุณจะเปลี่ยนพฤติกรรมของวัตถุนั้น

การเปลี่ยนแปลงลักษณะการทำงานของวัตถุที่จะใช้โดยรหัสของคุณเองก็ทำได้ดี แต่เมื่อคุณเปลี่ยนพฤติกรรมของบางสิ่งที่ใช้โดยรหัสอื่นด้วยก็มีความเสี่ยงที่คุณจะทำลายรหัสอื่นนั้น

เมื่อพูดถึงการเพิ่มเมธอดให้กับอ็อบเจ็กต์และคลาสอาร์เรย์ในจาวาสคริปต์ความเสี่ยงที่จะทำให้บางอย่างพังนั้นสูงมากเนื่องจากจาวาสคริปต์ทำงานอย่างไร ประสบการณ์อันยาวนานสอนฉันว่าสิ่งประเภทนี้ทำให้เกิดข้อบกพร่องที่น่ากลัวทุกประเภทในจาวาสคริปต์

หากคุณต้องการพฤติกรรมที่กำหนดเองคุณควรกำหนดคลาสของคุณเอง (อาจเป็นคลาสย่อย) แทนที่จะเปลี่ยนคลาสเนทีฟ ด้วยวิธีนี้คุณจะไม่ทำลายอะไรเลย

ความสามารถในการเปลี่ยนวิธีการทำงานของคลาสโดยไม่ต้องมีคลาสย่อยเป็นคุณสมบัติที่สำคัญของภาษาการเขียนโปรแกรมที่ดี แต่เป็นสิ่งที่ต้องใช้น้อยครั้งและด้วยความระมัดระวัง


2
ดังนั้นการเพิ่มสิ่งที่ต้องการ.stgringify()จะถือว่าปลอดภัยหรือไม่?
buschtoens

7
มีปัญหามากมายเช่นจะเกิดอะไรขึ้นหากโค้ดอื่นพยายามเพิ่มstringify()วิธีการของตัวเองด้วยพฤติกรรมที่แตกต่างกัน มันไม่ใช่สิ่งที่คุณควรทำในการเขียนโปรแกรมในชีวิตประจำวัน ... ไม่ใช่ถ้าสิ่งที่คุณต้องการทำคือบันทึกรหัสสองสามตัวที่นี่และที่นั่น ดีกว่าที่จะกำหนดคลาสหรือฟังก์ชันของคุณเองที่ยอมรับอินพุตใด ๆ และกำหนดให้เป็นสตริง เบราว์เซอร์ส่วนใหญ่กำหนดวิธีที่JSON.stringify()ดีที่สุดคือตรวจสอบว่ามีอยู่จริงหรือไม่และหากไม่ได้กำหนดด้วยตัวเอง
Abhi Beckert

5
ฉันชอบมากกว่าsomeError.stringify() errors.stringify(someError)ตรงไปตรงมาและเหมาะกับแนวคิดของ js ฉันกำลังทำบางอย่างที่เกี่ยวข้องกับ ErrorObject บางอย่างโดยเฉพาะ
buschtoens

1
ข้อโต้แย้งเดียว (แต่ดี) ที่ยังคงอยู่คือ macro-libs ขนาดมหึมาบางตัวอาจเริ่มเข้าครอบครองประเภทของคุณและอาจรบกวนซึ่งกันและกัน หรือมีอะไรอีกมั้ย?
buschtoens

4
หากปัญหาคือคุณอาจมีการชนกันคุณสามารถใช้รูปแบบที่ทุกครั้งที่ทำสิ่งนี้ได้หรือไม่คุณยืนยันเสมอว่าฟังก์ชันนั้นไม่ได้กำหนดก่อนที่คุณจะกำหนด และหากคุณใส่ไลบรารีของบุคคลที่สามไว้ก่อนโค้ดของคุณเองเสมอคุณควรตรวจจับการชนกันได้
Dave Cousineau

32

ไม่มีข้อเสียเปรียบที่วัดผลได้เช่นการตีประสิทธิภาพ อย่างน้อยก็ไม่มีใครกล่าวถึง ดังนั้นนี่จึงเป็นคำถามเกี่ยวกับความชอบและประสบการณ์ส่วนตัว

อาร์กิวเมนต์ Pro หลัก:ดูดีขึ้นและใช้งานง่ายมากขึ้น: น้ำตาลไวยากรณ์ เป็นฟังก์ชันเฉพาะประเภท / อินสแตนซ์ดังนั้นจึงควรผูกไว้กับประเภท / อินสแตนซ์นั้นโดยเฉพาะ

ข้อโต้แย้งหลัก:รหัสสามารถรบกวน หาก lib A เพิ่มฟังก์ชันอาจเขียนทับฟังก์ชันของ lib B ได้ สิ่งนี้สามารถทำลายรหัสได้ง่ายมาก

ทั้งสองมีจุด เมื่อคุณใช้ไลบรารีสองไลบรารีที่เปลี่ยนประเภทของคุณโดยตรงคุณมักจะจบลงด้วยโค้ดที่ใช้งานไม่ได้เนื่องจากฟังก์ชันการทำงานที่คาดไว้อาจไม่เหมือนกัน ฉันเห็นด้วยอย่างยิ่งกับเรื่องนี้ ไลบรารีมาโครต้องไม่ปรับเปลี่ยนประเภทเนทีฟ มิฉะนั้นคุณในฐานะนักพัฒนาจะไม่มีทางรู้ว่าเบื้องหลังเกิดอะไรขึ้น

และนั่นคือเหตุผลที่ฉันไม่ชอบ libs เช่น jQuery ขีดล่าง ฯลฯ อย่าเข้าใจฉันผิด พวกเขาเป็นอย่างดีโปรแกรมและพวกเขาทำงานเช่นเสน่ห์ แต่พวกเขามีขนาดใหญ่ คุณใช้เพียง 10% เท่านั้นและเข้าใจประมาณ 1%

นั่นเป็นเหตุผลว่าทำไมฉันถึงชอบแนวทางแบบอะตอมซึ่งคุณต้องการสิ่งที่คุณต้องการจริงๆเท่านั้น ด้วยวิธีนี้คุณจะรู้เสมอว่าเกิดอะไรขึ้น ไมโครไลบรารีจะทำเฉพาะสิ่งที่คุณต้องการให้ทำเท่านั้นจึงจะไม่รบกวน ในบริบทของการให้ผู้ใช้ทราบว่ามีการเพิ่มคุณลักษณะใดบ้างการขยายประเภทเนทีฟถือได้ว่าปลอดภัย

TL; DRหากมีข้อสงสัยอย่าขยายประเภทดั้งเดิม ขยายประเภทเนทีฟเฉพาะในกรณีที่คุณแน่ใจ 100% ผู้ใช้ปลายทางจะรู้และต้องการพฤติกรรมนั้น ไม่ว่าในกรณีใดจะปรับเปลี่ยนฟังก์ชันที่มีอยู่ของประเภทเนทีฟเนื่องจากจะทำให้อินเทอร์เฟซที่มีอยู่เสียหาย

หากคุณตัดสินใจที่จะขยายประเภทให้ใช้Object.defineProperty(obj, prop, desc); ถ้าคุณไม่สามารถprototypeใช้ของชนิด


ตอนแรกฉันคิดคำถามนี้ขึ้นมาเพราะฉันต้องการErrorให้ส่งผ่าน JSON ดังนั้นฉันต้องการวิธีที่จะทำให้พวกเขารัดกุม error.stringify()รู้สึกดีกว่าerrorlib.stringify(error); ตามที่โครงสร้างที่สองแนะนำฉันกำลังดำเนินการerrorlibและไม่ได้อยู่ในerrorตัวเอง


ฉันเปิดรับความคิดเห็นเพิ่มเติมเกี่ยวกับเรื่องนี้
buschtoens

4
คุณกำลังหมายความว่า jQuery และขีดล่างขยายอ็อบเจ็กต์เนทีฟหรือไม่? พวกเขาไม่ ดังนั้นหากคุณหลีกเลี่ยงพวกเขาด้วยเหตุนั้นคุณก็เข้าใจผิด
JLRishe

1
นี่เป็นคำถามที่ฉันเชื่อว่าไม่มีข้อโต้แย้งที่ว่าการขยายออบเจ็กต์ดั้งเดิมด้วย lib A อาจขัดแย้งกับ lib B: ทำไมคุณถึงมีไลบรารีสองไลบรารีที่มีลักษณะคล้ายกันมากหรือมีลักษณะกว้างจนเกิดความขัดแย้งนี้ได้ คือฉันเลือก lodash หรือขีดล่างไม่ใช่ทั้งสองอย่าง ภูมิทัศน์ของ Libraray ในจาวาสคริปต์ในปัจจุบันของเราอิ่มตัวมากเกินไปและนักพัฒนา (โดยทั่วไป) ไม่ใส่ใจในการเพิ่มสิ่งเหล่านี้จนเราต้องหลีกเลี่ยงแนวทางปฏิบัติที่ดีที่สุดเพื่อเอาใจ Lib Lords ของเรา
micahblu

2
@micahblu - ไลบรารีสามารถตัดสินใจปรับเปลี่ยนออบเจ็กต์มาตรฐานเพียงเพื่อความสะดวกในการเขียนโปรแกรมภายในของตัวเอง (นั่นคือสาเหตุที่ผู้คนต้องการทำเช่นนี้) ดังนั้นจึงไม่หลีกเลี่ยงความขัดแย้งนี้เพียงเพราะคุณจะไม่ใช้สองไลบรารีที่มีฟังก์ชันเดียวกัน
jfriend00

1
คุณยังสามารถสร้างคลาสใหม่ซึ่งขยายคลาสที่มีอยู่และใช้ในโค้ดของคุณเอง ดังนั้นหากMyError extends Errorสามารถมีstringifyวิธีการโดยไม่ชนกับคลาสย่อยอื่น ๆ คุณยังคงต้องจัดการข้อผิดพลาดที่ไม่ได้สร้างขึ้นโดยรหัสของคุณเองดังนั้นจึงอาจมีประโยชน์น้อยกว่าข้อผิดพลาดErrorอื่น ๆ
ShadSterling

16

ในความคิดของฉันมันเป็นการปฏิบัติที่ไม่ดี เหตุผลสำคัญคือการรวม การอ้างอิงเอกสาร should.js:

OMG IT EXTENDS OBJECT ???!?! @ ใช่ใช่มันเป็นไปได้ด้วยการโจมตีเพียงครั้งเดียวและไม่มันจะไม่ทำลายรหัสของคุณ

แล้วผู้เขียนจะรู้ได้อย่างไร? จะเกิดอะไรขึ้นถ้ากรอบการล้อเลียนของฉันไม่เหมือนเดิม? จะเกิดอะไรขึ้นถ้าสัญญาของฉัน lib ไม่เหมือนกัน?

หากคุณกำลังทำในโครงการของคุณเองก็ไม่เป็นไร แต่สำหรับห้องสมุดแล้วมันเป็นการออกแบบที่ไม่ดี Underscore.js เป็นตัวอย่างของสิ่งที่ทำอย่างถูกต้อง:

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

2
ฉันแน่ใจว่าคำตอบของ TJ จะไม่ใช้คำสัญญาหรือกรอบการเยาะเย้ยเหล่านั้น: x
Jim Schubert

_(arr).flatten()ตัวอย่างจริงเชื่อฉันไม่ขยายวัตถุพื้นเมือง เหตุผลส่วนตัวของฉันในการทำเช่นนั้นคือการสังเคราะห์อย่างแท้จริง แต่สิ่งนี้ตอบสนองความรู้สึกที่สวยงามของฉัน :) แม้จะใช้ชื่อฟังก์ชั่นปกติเช่นfoo(native).coolStuff()การแปลงเป็นวัตถุ "ขยาย" บางอย่างก็ดูดีในเชิงวากยสัมพันธ์ ขอบคุณสำหรับสิ่งนั้น!
อาทิ

16

หากคุณพิจารณาเป็นกรณี ๆ ไปการใช้งานบางอย่างอาจเป็นที่ยอมรับได้

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

การเขียนทับวิธีการที่สร้างไว้แล้วสร้างปัญหามากกว่าที่จะแก้ได้ซึ่งเป็นเหตุผลว่าทำไมจึงมีการระบุไว้โดยทั่วไปในภาษาการเขียนโปรแกรมหลายภาษาเพื่อหลีกเลี่ยงการปฏิบัตินี้ Devs ที่จะรู้ว่าฟังก์ชั่นมีการเปลี่ยนแปลงอย่างไร?

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

ในกรณีนี้เราไม่ได้เขียนทับเมธอด core JS ใด ๆ ที่รู้จัก แต่เรากำลังขยาย String อาร์กิวเมนต์หนึ่งในโพสต์นี้กล่าวถึงว่า dev ใหม่จะรู้ได้อย่างไรว่าเมธอดนี้เป็นส่วนหนึ่งของ core JS หรือไม่หรือจะหาเอกสารได้ที่ไหน อะไรจะเกิดขึ้นถ้าหลัก JS วัตถุ String จะได้รับวิธีการตั้งชื่อประโยชน์ ?

จะเป็นอย่างไรถ้าแทนที่จะเพิ่มชื่อที่อาจชนกับไลบรารีอื่นคุณใช้ตัวปรับแต่งเฉพาะของ บริษัท / แอพที่นักพัฒนาทุกคนเข้าใจได้

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 นั้นเหมาะสำหรับคุณและ / หรือทีมของคุณสิ่งนี้อาจเหมาะสำหรับคุณ


7

การขยายต้นแบบของบิวท์อินเป็นความคิดที่ไม่ดีอย่างไรก็ตาม ES2015 ได้แนะนำเทคนิคใหม่ที่สามารถใช้เพื่อให้ได้พฤติกรรมที่ต้องการ:

การใช้ประโยชน์ WeakMap s เพื่อเชื่อมโยงประเภทกับต้นแบบในตัว

การใช้งานต่อไปนี้จะขยาย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เป็นอาร์กิวเมนต์เดียวเท่านั้นเนื่องจากสามารถดึงข้อมูลวิธีสร้างตัวสะสมว่างและวิธีการเชื่อมต่อองค์ประกอบกับตัวสะสมนี้จากองค์ประกอบของอาร์เรย์เอง

โปรดทราบว่าฉันสามารถใช้Mapประเภทปกติได้เนื่องจากการอ้างอิงที่อ่อนแอไม่สมเหตุสมผลเมื่อพวกเขาเป็นเพียงตัวแทนของต้นแบบในตัวซึ่งจะไม่เก็บขยะ อย่างไรก็ตามWeakMapไม่สามารถทำซ้ำได้และไม่สามารถตรวจสอบได้เว้นแต่คุณจะมีคีย์ที่ถูกต้อง นี่เป็นคุณสมบัติที่ต้องการเนื่องจากฉันต้องการหลีกเลี่ยงการสะท้อนรูปแบบใด ๆ


นี่คือการใช้ WeakMap ที่ยอดเยี่ยม โปรดใช้ความระมัดระวังxs[0]ในอาร์เรย์ว่าง type(undefined).monoid ...
ขอบคุณ

@naomik ฉันรู้ - ดูคำถามล่าสุดของฉันเกี่ยวกับข้อมูลโค้ดที่ 2

การรองรับ @ftor นี้เป็นมาตรฐานแค่ไหน?
onassar

5
-1; ประเด็นนี้คืออะไร? คุณเพียงแค่สร้างประเภทการแมปพจนานุกรมส่วนกลางแยกกับวิธีการจากนั้นค้นหาวิธีการในพจนานุกรมนั้นอย่างชัดเจนตามประเภท ด้วยเหตุนี้คุณจึงสูญเสียการสนับสนุนสำหรับการสืบทอด (เมธอดที่ "เพิ่ม" Object.prototypeในวิธีนี้ไม่สามารถเรียกใช้กับ an Array) และไวยากรณ์นั้นยาว / น่าเกลียดกว่าอย่างมากหากคุณขยายต้นแบบอย่างแท้จริง การสร้างคลาสยูทิลิตี้ด้วยวิธีคงที่จะง่ายกว่าเสมอ ข้อได้เปรียบประการเดียวที่แนวทางนี้มีคือการสนับสนุนที่ จำกัด สำหรับความหลากหลาย
Mark Amery

4
@MarkAmery เฮ้เพื่อนคุณไม่เข้าใจ ฉันไม่สูญเสียมรดก แต่กำจัดมันซะ มรดกคือ 80 ดังนั้นคุณควรลืมมันไป จุดรวมของการแฮ็กนี้คือการเลียนแบบคลาสประเภทซึ่งพูดง่ายๆคือมีฟังก์ชันมากเกินไป มีคำวิจารณ์ที่ถูกต้อง: การเลียนแบบคลาสประเภทใน Javascript มีประโยชน์หรือไม่? ไม่มันไม่ใช่ แทนที่จะใช้ภาษาพิมพ์ที่สนับสนุนโดยกำเนิด ฉันค่อนข้างมั่นใจว่านี่คือสิ่งที่คุณหมายถึง

7

อีกเหตุผลหนึ่งที่คุณไม่ควรขยายออบเจ็กต์ดั้งเดิม:

เราใช้ Magento ซึ่งใช้ต้นแบบ jsและขยายสิ่งต่างๆมากมายบนวัตถุดั้งเดิม วิธีนี้ใช้งานได้ดีจนกว่าคุณจะตัดสินใจรับคุณสมบัติใหม่และนั่นคือจุดเริ่มต้นของปัญหาใหญ่

เราได้แนะนำ Webcomponents ในหน้าหนึ่งของเราดังนั้น webcomponents-lite.js จึงตัดสินใจแทนที่ Event Object (เนทีฟ) ทั้งหมดใน IE (เพราะเหตุใด) แน่นอนว่านี่เป็นการทำลายต้นแบบ jsซึ่งจะทำให้ Magento แตก (จนกว่าคุณจะพบปัญหาคุณอาจใช้เวลาหลายชั่วโมงในการติดตามกลับ)

ถ้าคุณชอบปัญหาจงทำต่อไป!


4

ฉันเห็นเหตุผลสามประการที่จะไม่ทำสิ่งนี้ (จากในแอปพลิเคชันเป็นอย่างน้อย) ซึ่งมีเพียงสองข้อเท่านั้นที่กล่าวถึงในคำตอบที่มีอยู่ที่นี่:

  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) แต่มันก็น่าเกลียด มันสามารถแก้ไขได้เช่นกันโดยใช้ฟังก์ชั่นยูทิลิตี้แบบสแตนด์อโลนแทนการเพิ่มต้นแบบ


2

Perf ยังเป็นเหตุผล บางครั้งคุณอาจต้องวนคีย์ มีหลายวิธีในการดำเนินการนี้

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 ผลลัพธ์

เพียงแค่คาดเดาเหตุผลที่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สร้างอ็อบเจกต์คีย์ที่ไม่เปลี่ยนรูปใหม่ทั้งหมด แต่อย่างใดก็ตามมันช้าลงอย่างชัดเจนถึง 15x

แม้แต่การตรวจสอบhasOwnPropertyก็ช้าลงถึง 2 เท่า

จุดทุกที่คือว่าถ้าคุณมี perf รหัสที่สำคัญและจำเป็นที่จะต้องห่วงกุญแจแล้วคุณต้องการเพื่อให้สามารถใช้งานได้โดยไม่ต้องโทรfor in hasOwnPropertyคุณสามารถทำได้ก็ต่อเมื่อคุณไม่ได้แก้ไขObject.prototype

โปรดทราบว่าหากคุณใช้Object.definePropertyเพื่อแก้ไขต้นแบบหากสิ่งที่คุณเพิ่มไม่สามารถระบุได้สิ่งเหล่านั้นจะไม่ส่งผลต่อพฤติกรรมของ JavaScript ในกรณีข้างต้น น่าเสียดายที่อย่างน้อยใน Chrome 83 จะส่งผลต่อประสิทธิภาพ

ป้อนคำอธิบายภาพที่นี่

ฉันเพิ่มคุณสมบัติที่ไม่สามารถนับได้ 3000 รายการเพื่อพยายามบังคับให้ปัญหาเกี่ยวกับความสมบูรณ์ปรากฏขึ้น ด้วยคุณสมบัติเพียง 30 รายการการทดสอบจึงใกล้เกินไปที่จะบอกได้ว่ามีผลกระทบอย่างสมบูรณ์หรือไม่

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

Firefox 77 และ Safari 13.1 ไม่พบความแตกต่างระหว่างคลาส Augmented และ Unaugmented บางที v8 จะได้รับการแก้ไขในพื้นที่นี้และคุณสามารถเพิกเฉยต่อปัญหาที่สมบูรณ์แบบได้

แต่ขอฉันเพิ่มด้วยมีเรื่องราวของArray.prototype.smoosh . รุ่นสั้น Mootools Array.prototype.flattenห้องสมุดที่นิยมทำของตัวเอง เมื่อคณะกรรมการมาตรฐานพยายามเพิ่มเนทีฟArray.prototype.flattenพวกเขาพบว่าไม่สามารถทำได้โดยไม่ทำลายไซต์จำนวนมาก นักพัฒนาที่ค้นพบเกี่ยวกับการหยุดพักแนะนำให้ตั้งชื่อเมธอด es5 smooshเป็นเรื่องตลก แต่ผู้คนกลับไม่เข้าใจว่ามันเป็นเรื่องตลก พวกเขาตัดสินflatแทนflatten

คุณธรรมของเรื่องราวคือคุณไม่ควรขยายวัตถุพื้นเมือง หากคุณพบปัญหาเดียวกันกับการทำลายสิ่งของและเว้นแต่ว่าไลบรารีเฉพาะของคุณจะได้รับความนิยมเท่ากับ MooTools ผู้จำหน่ายเบราว์เซอร์ก็ไม่น่าจะแก้ไขปัญหาที่คุณเกิดได้ หากห้องสมุดของคุณได้รับความนิยมอย่างนั้นการบังคับให้คนอื่นแก้ไขปัญหาที่คุณเกิดขึ้น ดังนั้นโปรดอย่าขยายวัตถุดั้งเดิม


รอhasOwnPropertyบอกคุณว่าคุณสมบัติอยู่ในวัตถุหรือต้นแบบ แต่การfor inวนซ้ำจะทำให้คุณได้รับคุณสมบัติที่แจกแจงได้เท่านั้น ฉันเพิ่งลองใช้แล้ว: เพิ่มคุณสมบัติที่ไม่สามารถนับได้ลงใน Array ต้นแบบและจะไม่ปรากฏในลูป ไม่จำเป็นไม่ต้องปรับเปลี่ยนต้นแบบสำหรับวงที่ง่ายที่สุดในการทำงาน
ygoe

แต่ก็ยังเป็นการปฏิบัติที่ไม่ดีด้วยเหตุผลอื่น ๆ ;)
gman
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.