जावास्क्रिप्ट में नए ऑपरेटर से बचना - बेहतर तरीका


16

चेतावनी: यह एक लंबी पोस्ट है।

चलो इसे सरल रखें। जब भी मैं जावास्क्रिप्ट में एक निर्माता को बुलाता हूं, मैं हर बार नए ऑपरेटर को उपसर्ग करने से बचना चाहता हूं। ऐसा इसलिए है क्योंकि मैं इसे भूल जाता हूं, और मेरा कोड बुरी तरह खराब हो जाता है।

इसके आस-पास का सरल तरीका यह है ...

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

पहला तात्कालिक उपाय इस तरह से immediate लागू ’विधि प्रतीत होता है…

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

  // do your stuff
}

हालांकि यह गलत है - नई वस्तु applyविधि और हमारे निर्माता को नहीं दी गई है arguments.callee

अब, मैं तीन समाधानों के साथ आया हूँ। मेरा सरल सवाल है: जो सबसे अच्छा लगता है। या, यदि आपके पास एक बेहतर तरीका है, तो इसे बताएं।

पहला - eval()निर्माणकर्ता को कॉल करने वाले जावास्क्रिप्ट कोड को गतिशील रूप से बनाने के लिए उपयोग करें।

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__ समाधान गैर-मानक है और "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
या स्टैक्रेस की खराब जांच करने पर केवल एक त्रुटि फेंकें और आप कोड को ठीक कर सकते हैं
शाफ़्ट फ्रीक

2
मुझे लगता है कि यह सवाल स्टैक ओवरफ्लो या कोड रिव्यू पर बेहतर पूछा जाएगा । यह एक वैचारिक प्रश्न के बजाय सुंदर कोड-केंद्रित प्रतीत होता है।
एडम लेअर

1
@greengit तो त्रुटि फेंकने के लिए एक jslint का उपयोग करें। यदि आपने Make()बिना किया तो यह आपको चेतावनी देगाnew क्योंकि मेक कैपिटलाइज़्ड है और इस तरह यह मान लिया कि यह एक कंस्ट्रक्टर है
रेयनोस

1
तो प्रतीक्षा करें - क्या आप इसे पूरा करने के लिए बेहतर तरीके की तलाश कर रहे हैं, या क्या आप सिर्फ आपको कोड देने के लिए किसी की तलाश कर रहे हैं ताकि आपके पास बिना चर-तर्क वस्तु निर्माण हो सके new? क्योंकि अगर यह बाद की बात है, तो आप शायद गलत साइट पर पूछ रहे हैं। यदि यह पूर्व है, तो आप नई का उपयोग करने और त्रुटियों का इतनी जल्दी पता लगाने के बारे में सुझावों को खारिज नहीं करना चाह सकते हैं ... यदि आपका ऐप वास्तव में "भारी" है, तो आखिरी चीज जो आप चाहते हैं वह कुछ धीमी गति से निर्माण तंत्र है। new, सभी फ्लैक के लिए, यह बहुत तेज़ है।
Shog9

5
विडंबना यह है कि प्रोग्रामर गलतियों को 'स्मार्टली' हैंडल करने की कोशिश करना, जावास्क्रिप्ट के कई 'खराब हिस्सों' के लिए स्वयं जिम्मेदार है।
डैनियल प्रैट

जवाबों:


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-shim का उपयोग करता है सही ?

आपको वैकल्पिक 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 के बारे में क्या? Object.createES5- शिम दूबस के रूप में सूचीबद्ध करता है।
ट्रेकरोड

@greengit यदि आप इसे फिर से पढ़ते हैं, तो ES5 शिम ऑब्जेक्ट के दूसरे तर्क को बताता है। DUBIOUS है। पहले वाला ठीक है।
रेयानोस

1
हां मैंने वह पढ़ा। और मुझे लगता है (IF) वे __proto__वहां किसी तरह की चीज का उपयोग कर रहे हैं , तो हम अभी भी उसी बिंदु पर हैं। क्योंकि पूर्व ES5 में प्रोटोटाइप को म्यूट करने का कोई आसान तरीका नहीं है। लेकिन वैसे भी, आपका समाधान सबसे सुरुचिपूर्ण और आगे की तलाश में लगता है। उसके लिए +1। (मेरी मतदान सीमा समाप्त हो गई है)
treecoder

1
धन्यवाद @psr और @Raynos आपका Object.createशिम मेरे तीसरे समाधान के लिए बहुत कम है, लेकिन कम जटिल और निश्चित रूप से मेरी तुलना में बेहतर है।
treecoder 9:11

37

चलो इसे सरल रखें। जब भी मैं जावास्क्रिप्ट में एक निर्माता को बुलाता हूं, मैं हर बार नए ऑपरेटर को उपसर्ग करने से बचना चाहता हूं। ऐसा इसलिए है क्योंकि मैं इसे भूल जाता हूं, और मेरा कोड बुरी तरह खराब हो जाता है।

स्पष्ट जवाब नहीं भूलना होगा new कीवर्ड को भूलेगा।

आप भाषा की संरचना और अर्थ बदल रहे हैं।

जो, मेरी राय में, और आपके कोड के भविष्य के रखरखाव के लिए, एक भयानक विचार है।


9
+1 यह किसी की खराब कोडिंग आदतों के आसपास की भाषा को छेड़ने के लिए अजीब लगता है। निश्चित रूप से कुछ वाक्य रचना हाइलाइटिंग / चेक-इन नीति बग-प्रवण पैटर्न / संभावित टाइपो से बचने को लागू कर सकती है।
स्टोइव

अगर मुझे एक लाइब्रेरी मिली जहां सभी कंस्ट्रक्टरों ने परवाह नहीं की अगर आप इस्तेमाल करते हैं newया नहीं, तो मुझे लगता है कि अधिक रखरखाव योग्य होगा।
जैक

@ जैक, लेकिन यह कीड़े खोजने के लिए बहुत कठिन और सूक्ष्मता का परिचय देगा। यदि आप ;अंतिम विवरणों को शामिल करते हैं, तो जावास्क्रिप्ट "देखभाल नहीं" के कारण होने वाले सभी बगों को देखें। (स्वचालित अर्धविराम सम्मिलन)
कैफ़ीक

@CaffGeek "अर्धविराम के बारे में परवाह नहीं करना" जावास्क्रिप्ट सटीक नहीं है - ऐसे मामले हैं जहां अर्धविराम का उपयोग करते हुए और इसका उपयोग नहीं करने वाले सूक्ष्म रूप से भिन्न होते हैं, और ऐसे मामले हैं जहां वे नहीं हैं। यही समस्या है। स्वीकृत उत्तर में प्रस्तुत समाधान ठीक इसके विपरीत है - इसके साथ, सभी मामलों में , शब्दार्थ का उपयोग करना 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जाना स्पॉट करने के लिए एक आसान बग है। दूसरों की तरह, मुझे नहीं लगता कि हमें वास्तव में अधिकांश कोड के लिए इसकी आवश्यकता है। जब तक आप जेएस इनहेरिटेंस बनाने के लिए एक लाइब्रेरी नहीं लिख रहे हैं, उस स्थिति में आप एक ही स्थान से उपयोग कर सकते हैं और आप पहले से ही सीधे इनहेरिटेंस की तुलना में एक अलग दृष्टिकोण का उपयोग कर रहे हैं।


संभावित रूप से छिपा हुआ बग: अगर मेरे पास है var x = new Ctor();और बाद में एक्स के रूप में है thisऔर करते हैं var y = Ctor();, तो यह अपेक्षा के अनुरूप व्यवहार नहीं करेगा।
luiscubal

@luiscubal यकीन नहीं है कि आप "बाद में एक्स के रूप में this" क्या कह रहे हैं, क्या आप संभावित समस्या दिखाने के लिए एक jsfiddle पोस्ट कर सकते हैं?
जुआन मेंडेस

आपका कोड मेरे द्वारा शुरू किए गए विचार से थोड़ा अधिक मजबूत है, लेकिन मैंने एक (कुछ हद तक अभी भी मान्य) उदाहरण के साथ आने का प्रबंधन किया: उदाहरण: jsfiddle.net/JHNcR/1
luiscubal

@luiscubal मैं आपकी बात देख रहा हूं लेकिन यह वास्तव में एक दृढ़ है। आप मान रहे हैं कि कॉल करना ठीक है Ctor.call(ctorInstance, 'value')। आप जो कर रहे हैं, उसके लिए मुझे एक वैध परिदृश्य दिखाई नहीं देता है। किसी ऑब्जेक्ट का निर्माण करने के लिए, आप या तो उपयोग करते हैं var x = new Ctor(val)या var y=Ctor(val)। यहां तक ​​कि अगर एक वैध परिदृश्य था, तो मेरा दावा है कि आपके पास बिना उपयोग किए हुए निर्माता हो सकते हैं new Ctor, न कि आपके पास ऐसे निर्माणकर्ता हो सकते हैं जो Ctor.callSee 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' */

संपादित करें: मैं जोड़ना भूल गया:

"यदि आपका ऐप वास्तव में 'भारी' है, तो आखिरी चीज जो आप चाहते हैं, वह कुछ धीमी गति से निर्माण तंत्र है"

मैं बिल्कुल सहमत हूँ - जब 'नए' कीवर्ड के बिना ऊपर 'चीज़' बनाते हैं, तो यह उसके साथ धीमी / भारी होता है। त्रुटियां आपके मित्र हैं, क्योंकि वे आपको बताते हैं कि क्या गलत है। इसके अलावा, वे आपके साथी डेवलपर्स को बताते हैं कि वे क्या गलत कर रहे हैं।


लापता कंस्ट्रक्टर तर्कों के लिए मानों को शामिल करना त्रुटि प्रवण है। यदि वे गायब हैं, तो अपरिभाषित गुणों को छोड़ दें। यदि वे आवश्यक हैं, तो एक त्रुटि है अगर वे गायब हैं। क्या होगा अगर Prop1 को बूल माना जाए? सत्यता के लिए "nope" का परीक्षण त्रुटियों का स्रोत होने वाला है।
जेबीआरविल्किन्सन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.