简体   繁体   中英

Cannot read property of Undefined, Runtime Error

I have the following code here:

class ColorPrinter {
    constructor(sourceFile, colors) {
      this.sourceFile = sourceFile 
      this.colors = colors
    }

    showColors() {
      this.colors.forEach(this.printColor);
    }

    printColor(colorObj) {
      console.log(this.sourceFile, colorObj.name, colorObj.hex);
    }
  } 


const colors = [{name: "red", hex: "#FF0000"}, {name: "yellow", hex: "#FFFF00"}, {name: "cyan", hex: "#0000FF"}];
const cp = new ColorPrinter('colors.csv', colors);
cp.showColors();

However when run I run this, I get the run time error of :

"TypeError: Cannot read property 'sourceFile' of undefined"

I don't understand why JS isn't happy. I am not trying to read from a file, but it won't take the string as a parameter.

Change it to:

this.colors.forEach((x) => this.printColor(x));

The reason your original code doesn't work as you expected has to do with the way scope works in javascript, specifically how the value of this is bound.

The main thing to understand is that (in general) when you invoke a function, this is set to the object on which the method was invoked .

wookie.eatBanana(); // inside the eatBanana method, this === wookie

But if you invoke the method separately from the object, this ends up being undefined :

const detachedFn = wookie.eatBanana;

// same function, but now this === undefined
detachedFn(); 

And what you're doing when you pass this.printColor to forEach is passing the function itself , which ends up getting invoked without being bound to your object:

const detachedFn = this.printColor;
this.colors.forEach((x) => detachedFn(x)); // this === undefined inside detachedFn, because it's invoked standalone

Inside the forEach implementation it's just invoking the function it was given. Effectively:

// pseudocode
function forEach(fn) {
  fn(); // no reference to your class instance; fn is just a regular function.
}

Defining a new function that invokes this.printColor() preserves the scope:

this.colors.forEach((x) => this.printColor(x));

function forEach(fn) {
  fn(); // inside this function your method is called *on your object*, preserving the 'this' binding.
}

Arrow functions auto-bind to the parent scope :

An arrow function does not have its own this. The this value of the enclosing lexical scope is used; arrow functions follow the normal variable lookup rules. So while searching for this which is not present in current scope, an arrow function ends up finding the this from its enclosing scope.

Arrow function auto-binding also comes in handy to solve these problems if you declare the methods as arrow functions to begin with. (This is experimental and might require the babel class properties plugin ).

// declaring this method as an arrow function causes it to bind to
// the parent scope (the class instance) which means you can invoke
// it independently of the instance.

printColor = (colorObj) => {
  console.log(this.sourceFile, colorObj.name, colorObj.hex);
}

// now this is fine because printColor is already bound to 'this'
this.colors.forEach(this.printColor);

Or, as palaѕн pointed out , with an explicit call to bind :

// make a copy of printColor that's explicitly bound to 'this'.
const explicitlyBoundFn = this.printColor.bind(this);

// works
this.colors.forEach(explicitlyBoundFn);

You can also accomplish this via call or apply , which both allow you to pass in a scope (although there's no reason to do so in this case).

// no reason to do this, but it works.
const detachedFn = this.printColor;
this.colors.forEach((x) => detachedFn.call(this, x));

Hope this helps. Happy to update it if anything needs clarification.

You can bind the current this also like:

this.colors.forEach(this.printColor.bind(this));

 class ColorPrinter { constructor(sourceFile, colors) { this.sourceFile = sourceFile this.colors = colors } showColors() { this.colors.forEach(this.printColor.bind(this)); } printColor(colorObj) { console.log(this.sourceFile, colorObj.name, colorObj.hex); } } const colors = [{name: "red", hex: "#FF0000"}, {name: "yellow", hex: "#FFFF00"}, {name: "cyan", hex: "#0000FF"}]; const cp = new ColorPrinter('colors.csv', colors); cp.showColors();

The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.

So, without using bind this inside printColor method is undefined and by using bind we are just telling the printColor method that this here should refer to ColorPrinter class so that we can access this.sourceFile ,

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM