JavaScript: इसका क्या मतलब है?

JavaScript में this की वैल्यू का पता लगाना मुश्किल हो सकता है. इसे पता लगाने का तरीका यहां बताया गया है…

JavaScript का this कई चुटकुलों का विषय है. इसकी वजह यह है कि यह काफ़ी मुश्किल है. हालांकि, मैंने डेवलपर को इस this से बचने के लिए, डोमेन के हिसाब से बहुत ज़्यादा मुश्किल और खास चीज़ें करते देखा है. अगर आपको this के बारे में नहीं पता है, तो उम्मीद है कि इससे आपको मदद मिलेगी. यह मेरी this गाइड है.

हम सबसे खास स्थिति से शुरू करेंगे और सबसे कम खास स्थिति पर खत्म करेंगे. यह लेख एक बड़े if (…) … else if () … else if (…) … की तरह है. इसलिए, सीधे उस सेक्शन पर जाएं जो आपके कोड से मैच करता है.

  1. अगर फ़ंक्शन को ऐरो फ़ंक्शन के तौर पर तय किया गया है
  2. अगर फ़ंक्शन/क्लास को new के साथ कॉल किया जाता है, तो
  3. अगर फ़ंक्शन में 'बाउंड' this वैल्यू है, तो
  4. अगर this को कॉल के समय पर सेट किया गया है, तो
  5. अगर फ़ंक्शन को पैरंट ऑब्जेक्ट (parent.func()) के ज़रिए कॉल किया जाता है, तो
  6. अगर फ़ंक्शन या पैरंट स्कोप, स्ट्रिक्ट मोड में है, तो
  7. अन्य मामलों में

अगर फ़ंक्शन को ऐरो फ़ंक्शन के तौर पर तय किया गया है, तो:

const arrowFunction = () => {
  console.log(this);
};

इस मामले में, this की वैल्यू, पैरंट स्कोप में this की वैल्यू के हमेशा बराबर होती है:

const outerThis = this;

const arrowFunction = () => {
  // Always logs `true`:
  console.log(this === outerThis);
};

ऐरो फ़ंक्शन बहुत अच्छे होते हैं, क्योंकि this की अंदरूनी वैल्यू को बदला नहीं जा सकता. यह हमेशा बाहरी this जैसी ही होती है.

अन्य उदाहरण

ऐरो फ़ंक्शन की मदद से, this की वैल्यू को bind से बदला नहीं जा सकता:

// Logs `true` - bound `this` value is ignored:
arrowFunction.bind({foo: 'bar'})();

ऐरो फ़ंक्शन की मदद से, this की वैल्यू को call या apply से बदला नहीं जा सकता:

// Logs `true` - called `this` value is ignored:
arrowFunction.call({foo: 'bar'});
// Logs `true` - applied `this` value is ignored:
arrowFunction.apply({foo: 'bar'});

ऐरो फ़ंक्शन की मदद से, किसी दूसरे ऑब्जेक्ट के सदस्य के तौर पर फ़ंक्शन को कॉल करके, this की वैल्यू नहीं बदली जा सकती:

const obj = {arrowFunction};
// Logs `true` - parent object is ignored:
obj.arrowFunction();

ऐरो फ़ंक्शन के साथ, फ़ंक्शन को कॉन्स्ट्रक्टर के तौर पर कॉल करके, this की वैल्यू नहीं बदली जा सकती:

// TypeError: arrowFunction is not a constructor
new arrowFunction();

'बाउंड' इंस्टेंस के तरीके

अगर आपको यह पक्का करना है कि this हमेशा क्लास इंस्टेंस का रेफ़रंस देता है, तो ऐरो फ़ंक्शन और क्लास फ़ील्ड का इस्तेमाल करना सबसे बेहतर तरीका है:

class Whatever {
  someMethod = () => {
    // Always the instance of Whatever:
    console.log(this);
  };
}

यह पैटर्न, कॉम्पोनेंट (जैसे, React कॉम्पोनेंट या वेब कॉम्पोनेंट) में इवेंट के लिसनर के तौर पर इंस्टेंस मेथड का इस्तेमाल करते समय काफ़ी मददगार होता है.

ऊपर दिए गए कोड से ऐसा लग सकता है कि यह "this, पैरंट स्कोप में मौजूद this जैसा ही होगा" के नियम का उल्लंघन कर रहा है. हालांकि, अगर कन्स्ट्रक्टर में चीज़ों को सेट करने के लिए, क्लास फ़ील्ड को सिंटैक्टिक शुगर के तौर पर देखा जाए, तो यह समझ में आने लगता है:

class Whatever {
  someMethod = (() => {
    const outerThis = this;
    return () => {
      // Always logs `true`:
      console.log(this === outerThis);
    };
  })();
}

// …is roughly equivalent to:

class Whatever {
  constructor() {
    const outerThis = this;
    this.someMethod = () => {
      // Always logs `true`:
      console.log(this === outerThis);
    };
  }
}

अन्य पैटर्न में, कन्स्ट्रक्टर में किसी मौजूदा फ़ंक्शन को बांधना या कन्स्ट्रक्टर में फ़ंक्शन असाइन करना शामिल है. अगर किसी वजह से क्लास फ़ील्ड का इस्तेमाल नहीं किया जा सकता, तो कंस्ट्रक्टर में फ़ंक्शन असाइन करना एक अच्छा विकल्प है:

class Whatever {
  constructor() {
    this.someMethod = () => {
      // …
    };
  }
}

अगर फ़ंक्शन/क्लास को new के साथ कॉल किया जाता है, तो:

new Whatever();

ऊपर दिए गए कोड में, Whatever (या अगर यह क्लास है, तो उसका कंस्ट्रक्टर फ़ंक्शन) को this के तौर पर सेट किया जाएगा, जो Object.create(Whatever.prototype) के नतीजे पर सेट होगा.

class MyClass {
  constructor() {
    console.log(
      this.constructor === Object.create(MyClass.prototype).constructor,
    );
  }
}

// Logs `true`:
new MyClass();

पुराने स्टाइल के कन्स्ट्रक्टर पर भी यही बात लागू होती है:

function MyClass() {
  console.log(
    this.constructor === Object.create(MyClass.prototype).constructor,
  );
}

// Logs `true`:
new MyClass();

अन्य उदाहरण

new के साथ कॉल करने पर, this की वैल्यू को bind से बदला नहीं जा सकता:

const BoundMyClass = MyClass.bind({foo: 'bar'});
// Logs `true` - bound `this` value is ignored:
new BoundMyClass();

new के साथ कॉल करने पर, फ़ंक्शन को किसी दूसरे ऑब्जेक्ट के सदस्य के तौर पर कॉल करके, this की वैल्यू नहीं बदली जा सकती:

const obj = {MyClass};
// Logs `true` - parent object is ignored:
new obj.MyClass();

अगर फ़ंक्शन में 'बाउंड' this वैल्यू है, तो:

function someFunction() {
  return this;
}

const boundObject = {hello: 'world'};
const boundFunction = someFunction.bind(boundObject);

जब भी boundFunction को कॉल किया जाएगा, तब उसकी this वैल्यू, bind (boundObject) को पास किया गया ऑब्जेक्ट होगा.

// Logs `false`:
console.log(someFunction() === boundObject);
// Logs `true`:
console.log(boundFunction() === boundObject);

अन्य उदाहरण

बाउंड किए गए फ़ंक्शन को कॉल करते समय, this की वैल्यू को call या apply से बदला नहीं जा सकता:

// Logs `true` - called `this` value is ignored:
console.log(boundFunction.call({foo: 'bar'}) === boundObject);
// Logs `true` - applied `this` value is ignored:
console.log(boundFunction.apply({foo: 'bar'}) === boundObject);

बाउंड किए गए फ़ंक्शन को कॉल करते समय, फ़ंक्शन को किसी दूसरे ऑब्जेक्ट के सदस्य के तौर पर कॉल करके, this की वैल्यू नहीं बदली जा सकती:

const obj = {boundFunction};
// Logs `true` - parent object is ignored:
console.log(obj.boundFunction() === boundObject);

अगर this को कॉल के समय पर सेट किया गया है, तो:

function someFunction() {
  return this;
}

const someObject = {hello: 'world'};

// Logs `true`:
console.log(someFunction.call(someObject) === someObject);
// Logs `true`:
console.log(someFunction.apply(someObject) === someObject);

this की वैल्यू, call/apply को पास किया गया ऑब्जेक्ट है.

माफ़ करें, डीओएम इवेंट लिसनर जैसी चीज़ों की वजह से this किसी दूसरी वैल्यू पर सेट हो जाता है. इसका इस्तेमाल करने पर, कोड को समझना मुश्किल हो सकता है:

यह न करें
element.addEventListener('click', function (event) {
  // Logs `element`, since the DOM spec sets `this` to
  // the element the handler is attached to.
  console.log(this);
});

ऊपर दिए गए मामलों में, हम this का इस्तेमाल नहीं करते. इसके बजाय:

यह करें
element.addEventListener('click', (event) => {
  // Ideally, grab it from a parent scope:
  console.log(element);
  // But if you can't do that, get it from the event object:
  console.log(event.currentTarget);
});

इसके अलावा, अगर फ़ंक्शन को पैरंट ऑब्जेक्ट (parent.func()) के ज़रिए कॉल किया जाता है, तो:

const obj = {
  someMethod() {
    return this;
  },
};

// Logs `true`:
console.log(obj.someMethod() === obj);

इस मामले में, फ़ंक्शन को obj के सदस्य के तौर पर कॉल किया जाता है, इसलिए this obj होगा. यह कॉल-टाइम पर होता है. इसलिए, अगर फ़ंक्शन को उसके पैरंट ऑब्जेक्ट के बिना या किसी दूसरे पैरंट ऑब्जेक्ट के साथ कॉल किया जाता है, तो लिंक टूट जाता है:

const {someMethod} = obj;
// Logs `false`:
console.log(someMethod() === obj);

const anotherObj = {someMethod};
// Logs `false`:
console.log(anotherObj.someMethod() === obj);
// Logs `true`:
console.log(anotherObj.someMethod() === anotherObj);

someMethod() === obj गलत है, क्योंकि someMethod को obj का सदस्य नहीं कहा जाता है. ऐसा हो सकता है कि आपने कुछ ऐसा करने की कोशिश की हो, जिससे आपको यह गड़बड़ी मिली हो:

const $ = document.querySelector;
// TypeError: Illegal invocation
const el = $('.some-element');

ऐसा इसलिए होता है, क्योंकि querySelector को लागू करने पर, वह अपनी this वैल्यू देखता है और उम्मीद करता है कि वह किसी तरह का डीओएम नोड हो. ऊपर दिए गए उदाहरण में, यह कनेक्शन नहीं होता. ऊपर बताई गई बातों को सही तरीके से लागू करने के लिए:

const $ = document.querySelector.bind(document);
// Or:
const $ = (...args) => document.querySelector(...args);

मज़ेदार जानकारी: सभी एपीआई, अंदरूनी तौर पर this का इस्तेमाल नहीं करते. console.log जैसे Console के तरीकों को बदला गया था, ताकि this रेफ़रंस से बचा जा सके. इसलिए, log को console से बंधे होने की ज़रूरत नहीं है.

अगर फ़ंक्शन या पैरंट स्कोप, स्ट्रिक्ट मोड में है, तो:

function someFunction() {
  'use strict';
  return this;
}

// Logs `true`:
console.log(someFunction() === undefined);

इस मामले में, this की वैल्यू तय नहीं है. अगर पैरंट स्कोप स्ट्रिक्ट मोड में है और सभी मॉड्यूल स्ट्रिक्ट मोड में हैं, तो फ़ंक्शन में 'use strict' की ज़रूरत नहीं है.

अन्यथा:

function someFunction() {
  return this;
}

// Logs `true`:
console.log(someFunction() === globalThis);

इस मामले में, this की वैल्यू globalThis की वैल्यू के बराबर है.

वाह!

यह बहुत आसान है! this के बारे में मुझे बस इतना ही पता है. क्या आपका कोई सवाल है? क्या मुझे कुछ पता नहीं है? मुझे ट्वीट करें.

समीक्षा करने के लिए, मैथियास बीन्स, इंगवार स्टीफ़नीन, और थॉमस स्टाइनर का धन्यवाद.