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 की वैल्यू में बदलाव<br नहीं किया जा सकता:

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

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

ऊपर दिए गए जवाब से ऐसा लग सकता है कि यह "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();

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

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 वैल्यू होती है और वह इस तरह का एक DOM नोड होना चाहिए. साथ ही, ऊपर दिया गया कनेक्शन, उस कनेक्शन को तोड़ देता है. ऊपर दिए गए लक्ष्यों को सही तरीके से हासिल करने के लिए:

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

मज़ेदार जानकारी: सभी एपीआई अंदरूनी तौर पर this का इस्तेमाल नहीं करते. console.log जैसे कंसोल के तरीकों को 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 के बारे में बस इतना ही पता है. क्या आपका कोई सवाल है? क्या आपसे कुछ छूट गया है? बेझिझक मुझसे ट्वीट करें.

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