why use this keyword in constructor function

2020-04-27 05:35发布

问题:

compare code 1 and code 2, which one is correct?

function Rectangle(height, width) {
  this.height = height;
  this.width = width;
  this.calcArea = function() { // why use this here?
      return this.height * this.width;
  };

}

code 2 I thought it's fine with this :

function Rectangle(height, width) {
      this.height = height;
      this.width = width;
      calcArea = function() {
          return this.height * this.width;
      };

    }

回答1:

If you don't use this, calcArea will not be accessible through the object of Rectangle. When you say,

this.calcArea = function () ...

you create a new variable in the current object (this) and the function will be assigned to it, so that the object will have access to it.

Try these statements, with and without this. You ll understand better.

var a = new Rectangle(1, 2);
console.log(a.calcArea());


回答2:

which one is correct?

This depends on how you view "correct":

  • Will either declaration fail to be parse correctly?
    • No, both are valid JavaScript.
  • Which one will calculate calcArea?
    • Code 1 will calculate it correctly and Code 2 does not create a member function of the Rectangle class but you can make it calculate correctly with a bit of difficulty ad redirection. See below.
  • Is either one good practice for creating classes?
    • No, neither of them. See at the bottom.

Code 1 - calcArea()

If you create a new instance of the Rectangle in code 1 then:

function Rectangle(height, width) {
    this.height = height;
    this.width = width;
    this.calcArea = function() { // why use this here?
        return this.height * this.width;
    };    
}

var rect = new Rectangle( 3, 4 );
console.log( rect.calcArea() );

Will correctly output 12

Code 2 - calcArea()

If you create a new instance of the Rectangle in code 2 then:

function Rectangle(height, width) {
    this.height = height;
    this.width = width;
    calcArea = function() {
        return this.height * this.width;
    };
}

var rect = new Rectangle( 3, 4 );
console.log( rect.calcArea() );

Will throw an error: TypeError: rect.calcArea is not a function

calcArea is, instead, attached to the global scope so we can do:

console.log( calcArea() );

Will output NaN as calcArea in part of the global scope so has no knowledge of any instance of a Rectangle class and the global scope does not have a height or a width attribute.

If we do:

var rect = new Rectangle( 3, 4 );
width = 7;   // Set in the global scope.
height = 10; // Set in the global scope.
console.log( calcArea() );

Then it will return 70 (and not 12 since, within calcArea(), this references the global scope and not the rect object).

If we change what this refers using .call() to invoke the function:

var rect = new Rectangle( 3, 4 );
width = 7;   // Set in the global scope.
height = 10; // Set in the global scope.
console.log( calcArea.call( rect ) );

Then it will output 12 (since this now refers to the rect object and not to the global scope).

You probably don't want to have to do this every time you want to use calcArea().

Why Code 1 is not optimal

Code 1 will work but is not the optimal solution because each time you create a new Rectangle object it will create an calcArea attribute of that object which is a different function to any calcArea attributes of any other Rectangle object.

You can see this if you do:

function Rectangle(height, width) {
    this.height = height;
    this.width = width;
    this.calcArea = function() { // why use this here?
        return this.height * this.width;
    };    
}

var r1 = new Rectangle( 3, 4 ),
    r2 = new Rectangle( 6, 7 );

console.log( r1.calcArea.toString() === r2.calcArea.toString() ); // Line 1
console.log( r1.calcArea === r2.calcArea );                       // Line 2

Which will output true when testing the string representation of the functions are identical but false when testing whether the functions are identical.

What does this mean? If you create 10,000 instances of Rectangle then you will have 10,000 different instances of the calcArea attribute as well and each copy will require additional memory (plus time to allocate that memory and to garbage collect it at the end).

What is better practice?

function Rectangle(height, width) {
      this.setHeight( height );
      this.setWidth( width );
}
Rectangle.prototype.setHeight = function( height ){ this.height = height; }
Rectangle.prototype.setWidth  = function( width  ){ this.width = width; }
Rectangle.prototype.calcArea  = function(){ return this.height * this.width; }

Then if you do:

var r1 = new Rectangle( 3, 4 ),
    r2 = new Rectangle( 6, 7 );

console.log( r1.calcArea.toString() === r2.calcArea.toString() ); // Line 1
console.log( r1.calcArea === r2.calcArea );                       // Line 2

It will return true for both - meaning that r1.calcArea and r2.calcArea refer to the identical function and regardless of how many instances of Rectangle there are.



回答3:

The second version will set the global variable calcArea to do stuff specific to your object whenever an instance of your object is constructed. Use of this is required to set properties of your particular object.



回答4:

When you preface your methods and properties with 'this' in your constructor they allow any new objects that get created with that constructor to use those properties and methods and have those properties and methods point to the newly created object.

If you create a new object based on your version of the Rectangle constructor that doesn't use 'this' as a preface to calcArea and look at the chrome debugger you get the following error:

Object #<Rectangle> has no method 'calcArea' 

In short it simply isn't recognized.

The other aspects of not using "this" is that the method becomes 'global'.

Here is a code example to demonstrate:

function Rectangle(height, width) {
  this.height = height;
  this.width = width;
  calcArea = function() { // Not prefaced with 'this'
      return 'hello world';
  };

}


var aNewObj = new Rectangle(10,10);

console.log(calcArea()) // Notice this is not aNewObj.calcArea() ...just calcArea() but its still accessible.