การหลีกเลี่ยงโอเปอเรเตอร์ใหม่ใน 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
}

นี้เป็นธรรม แต่ - วัตถุใหม่จะถูกส่งผ่านไปยังวิธีการและไม่ให้คอนสตรัคของเราapplyarguments.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 การแก้ปัญหาดูเหมือนเงอะงะและมาพร้อมกับปัญหาทั้งหมดของ "ความชั่วร้าย 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();

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

- อัปเดต -

บางคนบอกว่า "เพิ่งโยนข้อผิดพลาด" คำตอบของฉันคือ: เรากำลังทำแอพหนักกับตัวสร้าง 10+ ตัวและฉันคิดว่ามันคงจะเป็นงานที่หนักหนากว่าถ้าตัวสร้างทุกตัวสามารถ "ฉลาด" จัดการกับข้อผิดพลาดนั้นได้โดยไม่ต้องทิ้งข้อความแสดงข้อผิดพลาดบนคอนโซล


2
หรือเพียงแค่โยนข้อผิดพลาดเมื่อไม่ดีตรวจสอบ stacktrace และคุณสามารถแก้ไขรหัสได้
ratchet freak

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

1
@greengit ค่อนข้างแล้วโยนข้อผิดพลาดใช้ jslint มันจะเตือนคุณหากคุณได้Make()โดยไม่ต้องnewเพราะทำคือพิมพ์ใหญ่และดังนั้นจึงถือว่าเป็นผู้สร้าง
Raynos

1
ดังนั้นรอคอย - คุณกำลังมองหาวิธีที่ดีกว่าที่จะบรรลุเป้าหมายนี้หรือคุณเพียงแค่มองหาใครสักคนที่จะให้รหัสคุณเพื่อให้คุณสามารถมีการสร้างวัตถุตัวแปรอาร์กิวเมนต์โดยไม่new? เพราะถ้าเป็นอย่างหลังคุณอาจถามผิดเว็บไซต์ หากเป็นแบบเดิมคุณอาจไม่ต้องการยกเลิกข้อเสนอแนะเกี่ยวกับการใช้ข้อผิดพลาดใหม่และการตรวจจับอย่างรวดเร็ว ... หากแอปของคุณ "หนัก" อย่างแท้จริงสิ่งสุดท้ายที่คุณต้องการคือกลไกการก่อสร้างแบบ overwrought เพื่อทำให้ช้าลง newสำหรับทุกอย่างที่ได้รับมันค่อนข้างเร็ว
Shog9

5
กระแทกแดกดันการพยายาม 'จัดการอย่างชาญฉลาด' ข้อผิดพลาดโปรแกรมเมอร์เป็นตัวเองรับผิดชอบส่วนใหญ่ของ 'ส่วนที่ไม่ดี' ของ JavaScript
Daniel Pratt

คำตอบ:


19

ประการแรกarguments.calleeเลิกใช้งานอย่างเข้มงวดใน ES5 ดังนั้นเราจึงไม่ใช้ ทางออกที่แท้จริงนั้นค่อนข้างง่าย

คุณไม่ได้ใช้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

นอกเหนือจากนี้คุณสามารถเปลี่ยนตัวเลือกที่สองด้วย

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.createเป็น DUBIOUS
treecoder

@greengit ถ้าคุณอ่านอีกครั้ง ES5 shim ระบุอาร์กิวเมนต์ที่สองสำหรับ Object.create คือ DUBIOUS อันแรกก็ดี
Raynos

1
ใช่ฉันอ่านแล้ว และฉันคิดว่า (IF) พวกเขากำลังใช้บางอย่างอยู่__proto__ที่นั่นแล้วเราก็ยังอยู่ในจุดเดียวกัน เนื่องจาก ES5 ก่อนไม่มีวิธีที่ง่ายกว่าในการกลายพันธุ์ต้นแบบ แต่อย่างไรก็ตามโซลูชันของคุณดูสง่างามและมองไปข้างหน้ามากที่สุด +1 สำหรับสิ่งนั้น (ถึงขีด จำกัด การลงคะแนนของฉันแล้ว)
treecoder

1
ขอบคุณ @psr และ @Raynos Object.createshim ของคุณเป็นโซลูชั่นที่สามของฉันมาก แต่ซับซ้อนน้อยกว่าและดูดีกว่าของฉันแน่นอน
treecoder

37

ขอให้มันง่าย ฉันต้องการหลีกเลี่ยงคำนำหน้าโอเปอเรเตอร์ใหม่ทุกครั้งที่ฉันเรียกนวกรรมิกใน JavaScript นี่เป็นเพราะฉันมักจะลืมมันและรหัสของฉันก็แย่มาก

คำตอบที่ชัดเจนคืออย่าลืมnewคำหลัก

คุณกำลังเปลี่ยนโครงสร้างและความหมายของภาษา

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


9
+1 ดูเหมือนแปลกที่จะเขียนภาษารอบ ๆ นิสัยการเข้ารหัสที่ไม่ดีของคน แน่นอนว่าการเน้นที่การเน้นไวยากรณ์ / การเช็คอินสามารถบังคับใช้การหลีกเลี่ยงรูปแบบข้อผิดพลาด / พิมพ์ผิดที่เป็นไปได้
Stoive

หากฉันพบห้องสมุดที่คอนสตรัคทั้งหมดไม่สนใจว่าคุณใช้newหรือไม่ฉันจะพบว่ามันสามารถบำรุงรักษาได้มากขึ้น
แจ็ค

@ แจ็ค แต่มันจะแนะนำให้หนักขึ้นและยากขึ้นกว่าเดิมในการค้นหาข้อบกพร่อง เพียงมองไปรอบ ๆ ข้อบกพร่องทั้งหมดที่เกิดจากจาวาสคริปต์ "ไม่สนใจ" ถ้าคุณรวมถึงการ;สิ้นสุดงบ (การแทรกเครื่องหมายอัฒภาคอัตโนมัติ)
CaffGeek

@CaffGeek "Javascript ที่ไม่สนใจเครื่องหมายอัฒภาค" ไม่ถูกต้อง - มีบางกรณีที่ใช้เครื่องหมายอัฒภาคและไม่ได้ใช้มันจะแตกต่างกันอย่างละเอียดและมีบางกรณีที่พวกเขาไม่ได้ นั่นคือปัญหา. วิธีการแก้ปัญหาที่นำเสนอในคำตอบที่ได้รับการยอมรับเป็นอย่างแม่นยำตรงข้าม - กับมันในทุกกรณีการใช้newหรือไม่เป็นเหมือนกันความหมาย มีอยู่ไม่มีกรณีที่ลึกซึ้งที่คงที่นี้เสีย นั่นเป็นเหตุผลว่าทำไมถึงดีและทำไมคุณถึงต้องการใช้มัน
แจ็ค

14

ทางออกที่ง่ายที่สุดคือการจำnewและโยนข้อผิดพลาดเพื่อทำให้ชัดเจนว่าคุณลืม

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

evalสิ่งที่คุณทำไม่ได้ใช้ ฉันจะอายที่จะไม่ใช้คุณสมบัติที่ไม่ได้มาตรฐานเหมือน__proto__อย่างเฉพาะเจาะจงเพราะมันไม่ได้มาตรฐานและฟังก์ชั่นการทำงานอาจเปลี่ยนแปลง


หลีกเลี่ยง.__proto__การ
ท้าทาย

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 thisand do สิ่งvar y = Ctor();นี้จะไม่ทำงานตามที่คาดไว้
luiscubal

@luiscubal ไม่แน่ใจว่าสิ่งที่คุณพูดว่า "ภายหลังมี x เป็นthis" คุณสามารถโพสต์ jsfiddle เพื่อแสดงปัญหาที่อาจเกิดขึ้น?
Juan Mendes

รหัสของคุณแข็งแกร่งกว่าที่ฉันคิดไว้เล็กน้อย แต่ฉันจัดการมาด้วยตัวอย่าง (ค่อนข้างซับซ้อน แต่ยังใช้ได้) jsfiddle.net/JHNcR/1
luiscubal

@ luiscubal ฉันเห็นประเด็นของคุณ แต่มันเป็นเรื่องที่ซับซ้อน Ctor.call(ctorInstance, 'value')คุณกำลังสมมติว่ามันตกลงเพื่อการโทร ฉันไม่เห็นสถานการณ์ที่ถูกต้องสำหรับสิ่งที่คุณทำ การสร้างวัตถุคุณอาจใช้หรือvar x = new Ctor(val) var y=Ctor(val)แม้ว่าจะมีสถานการณ์ที่ถูกต้องการอ้างสิทธิ์ของฉันคือคุณสามารถมี Constructor โดยไม่ใช้new Ctorไม่ได้คุณสามารถใช้ Constructor ที่ทำงานโดยใช้Ctor.callSee jsfiddle.net/JHNcR/2
Juan Mendes

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' */

แก้ไข: ฉันลืมเพิ่ม:

"หากแอปของคุณ 'หนัก' อย่างแท้จริงสิ่งสุดท้ายที่คุณต้องการคือกลไกการก่อสร้างที่ overwrought เพื่อทำให้ช้าลง"

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


การประดิษฐ์ค่าสำหรับอาร์กิวเมนต์ตัวสร้างที่หายไปนั้นเกิดข้อผิดพลาดได้ง่าย หากพวกเขาหายไปทิ้งคุณสมบัติที่ไม่ได้กำหนด หากพวกเขาจำเป็นต้องโยนข้อผิดพลาดหากพวกเขาหายไป เกิดอะไรขึ้นถ้า prop1 ควรจะบูล? การทดสอบ "ไม่" สำหรับความจริงจะเป็นแหล่งของข้อผิดพลาด
JBRWilkinson
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.