阿里云主机折上折
  • 微信号
Current Site:Index > Constructors and the new operator

Constructors and the new operator

Author:Chuan Chen 阅读数:54412人阅读 分类: JavaScript

The Relationship Between Constructors and the new Operator

Constructors are special functions in JavaScript used to create objects. When a function is called with the new operator, it becomes a constructor, automatically creates a new object, and binds this to that new object.

function Person(name, age) {
  this.name = name;
  this.age = age;
}

const person1 = new Person('Alice', 25);
console.log(person1); // Output: Person { name: 'Alice', age: 25 }

The Execution Process of the new Operator

When a function is called with the new operator, the JavaScript engine performs the following steps:

  1. Creates an empty object
  2. Sets the prototype of this empty object to the constructor's prototype property
  3. Binds the constructor's this to the new object
  4. Executes the code inside the constructor
  5. If the constructor does not explicitly return an object, returns the new object
function Car(make, model) {
  // 1. Create an empty object (done by the JavaScript engine)
  // 2. Set the prototype (done by the JavaScript engine)
  // 3. this = new object (done by the JavaScript engine)
  
  this.make = make;
  this.model = model;
  
  // 4. Execute the constructor code
  this.displayInfo = function() {
    console.log(`${this.make} ${this.model}`);
  };
  
  // 5. If there's no return statement, return this (done by the JavaScript engine)
}

const myCar = new Car('Toyota', 'Camry');
myCar.displayInfo(); // Output: Toyota Camry

Return Values of Constructors

Constructors typically do not need an explicit return value. However, if an object is returned, it overrides the default object created by the new operator; if a primitive value is returned, it is ignored.

function Example1() {
  this.value = 1;
  return { value: 2 }; // Returning an object overrides
}

function Example2() {
  this.value = 1;
  return 2; // Returning a primitive value is ignored
}

const ex1 = new Example1();
console.log(ex1.value); // Output: 2

const ex2 = new Example2();
console.log(ex2.value); // Output: 1

Differences Between Constructors and Regular Functions

Although any function can be used as a constructor, constructors typically follow certain conventions:

  1. Constructor names usually start with a capital letter
  2. Constructors should be called with the new operator
  3. Constructors are typically used to initialize object state
// Used as a constructor
function Animal(name) {
  this.name = name;
}

const dog = new Animal('Buddy');

// Used as a regular function
const result = Animal('Buddy'); // Here, `this` points to the global object (in non-strict mode)
console.log(window.name); // Output: Buddy (in browser environments)

Detecting Whether the new Operator Was Used

You can check the value of this to determine if the function was called as a constructor:

function User(name) {
  if (!(this instanceof User)) {
    throw new Error('User must be called with the new operator');
  }
  this.name = name;
}

// Correct call
const user1 = new User('Alice');

// Incorrect call
const user2 = User('Bob'); // Throws error: User must be called with the new operator

The class Syntax Sugar in ES6

ES6 introduced the class syntax, which is essentially syntactic sugar for constructors:

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  
  greet() {
    console.log(`Hello, my name is ${this.name}`);
  }
}

const person = new Person('Charlie', 30);
person.greet(); // Output: Hello, my name is Charlie

Manually Implementing the new Operator

Understanding how the new operator works, we can manually implement a similar function:

function myNew(constructor, ...args) {
  // 1. Create a new object and set its prototype to the constructor's prototype
  const obj = Object.create(constructor.prototype);
  
  // 2. Call the constructor, binding `this` to the new object
  const result = constructor.apply(obj, args);
  
  // 3. If the constructor returns an object, return that object; otherwise, return the new object
  return result instanceof Object ? result : obj;
}

function Test(a, b) {
  this.a = a;
  this.b = b;
}

const testObj = myNew(Test, 1, 2);
console.log(testObj); // Output: Test { a: 1, b: 2 }

Constructors and the Prototype Chain

Instances created by constructors inherit methods and properties from the constructor's prototype:

function Vehicle(type) {
  this.type = type;
}

Vehicle.prototype.start = function() {
  console.log(`${this.type} starting...`);
};

const car = new Vehicle('Car');
car.start(); // Output: Car starting...

Constructor Inheritance

In JavaScript, inheritance can be achieved through the prototype chain:

function Parent(name) {
  this.name = name;
}

Parent.prototype.sayHello = function() {
  console.log(`Hello, I'm ${this.name}`);
};

function Child(name, age) {
  Parent.call(this, name); // Call the parent constructor
  this.age = age;
}

// Set up the prototype chain
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

Child.prototype.sayAge = function() {
  console.log(`I'm ${this.age} years old`);
};

const child = new Child('Emma', 5);
child.sayHello(); // Output: Hello, I'm Emma
child.sayAge();   // Output: I'm 5 years old

Constructors vs. Factory Functions

Besides using constructors and the new operator, objects can also be created using factory functions:

// Constructor approach
function Person(name) {
  this.name = name;
}
const p1 = new Person('Alice');

// Factory function approach
function createPerson(name) {
  return {
    name,
    greet() {
      console.log(`Hi, I'm ${this.name}`);
    }
  };
}
const p2 = createPerson('Bob');

The this Problem in Constructors

When using arrow functions or callbacks in constructors, be mindful of this binding:

function Timer() {
  this.seconds = 0;
  
  // Incorrect example - `this` in setInterval points to the global object
  setInterval(function() {
    this.seconds++; // Here, `this` is not the Timer instance
  }, 1000);
  
  // Solution 1 - Use an arrow function
  setInterval(() => {
    this.seconds++; // Arrow functions do not bind their own `this`
  }, 1000);
  
  // Solution 2 - Save a reference to `this`
  const self = this;
  setInterval(function() {
    self.seconds++;
  }, 1000);
}

const timer = new Timer();

Performance Considerations for Constructors

Defining methods inside constructors causes each instance to have its own copy of the method, impacting performance:

function Inefficient() {
  this.method = function() {
    // Each instance gets a new copy of this method
  };
}

function Efficient() {}
Efficient.prototype.method = function() {
  // All instances share the same method
};

Constructors and the Singleton Pattern

Sometimes we want a constructor to create only one instance:

function Singleton() {
  if (Singleton.instance) {
    return Singleton.instance;
  }
  
  this.value = Math.random();
  Singleton.instance = this;
}

const s1 = new Singleton();
const s2 = new Singleton();
console.log(s1 === s2); // Output: true
console.log(s1.value === s2.value); // Output: true

Constructors and Private Variables

Private variables can be implemented in constructors using closures:

function Counter() {
  let count = 0; // Private variable
  
  this.increment = function() {
    count++;
    console.log(count);
  };
  
  this.decrement = function() {
    count--;
    console.log(count);
  };
}

const counter = new Counter();
counter.increment(); // Output: 1
counter.increment(); // Output: 2
// Cannot directly access the count variable

Constructors and the Object Pool Pattern

Constructors can be used to implement object pools for performance optimization:

function ObjectPool() {
  const pool = [];
  
  this.create = function() {
    if (pool.length > 0) {
      return pool.pop();
    }
    return { id: Date.now() };
  };
  
  this.recycle = function(obj) {
    pool.push(obj);
  };
}

const pool = new ObjectPool();
const obj1 = pool.create();
pool.recycle(obj1);

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.