Movatterモバイル変換


[0]ホーム

URL:


Skip to main content
CSS-Tricks
Search

Understanding JavaScript Constructors

Faraz Kelhini onUpdated on

Get affordable and hassle-free WordPress hosting plans with Cloudways —start your free trial today.

The following is a guest post byFaraz Kelhini. Some of this stuff is out of my comfort zone, so I askedKyle Simpson to tech check it for me. Kyle’s answer (which we did during anOffice Hours session) was very interesting. It was: 1) This article is technically sound. JavaScript doesn’t really have classes in a traditional sense and this is the way most people shoehorn them in. 2) We may want to stop shoehorning them in. JavaScript has objects and we can use them in the way they are intended to do the same kinds of things. Kyle calls it OLOO (Objects Linked to Other Objects).Here’s an intro. I’d think there is value in learning about both.

Having a good understanding of constructors is crucial to truly understand the JavaScript language. Technically, JavaScript doesn’t have classes, but it has constructors and prototypes to bring similar functionality to JavaScript. In fact, the class declaration introduced in ES2015 simply works as syntactic sugar over the existing prototype-based inheritance and does not really add any extra functionality to the language.

In this tutorial, we will explore constructors in detail and see how JavaScript utilizes them to make objects.

Creating and using constructors

Constructors are like regular functions, but we use them with thenew keyword. There are two types of constructors: built-in constructors such asArray andObject, which are available automatically in the execution environment at runtime; and custom constructors, which define properties and methods for your own type of object.

A constructor is useful when you want to create multiple similar objects with the same properties and methods. It’s a convention to capitalize the name of constructors to distinguish them from regular functions. Consider the following code:

function Book() {   // unfinished code} var myBook = new Book();

The last line of the code creates an instance ofBook and assigns it to a variable. Although theBook constructor doesn’t do anything,myBook is still an instance of it. As you can see, there is no difference between this function and regular functions except that it’s called with thenew keyword and the function name is capitalized.

Determining the type of an instance

To find out whether an object is an instance of another one, we use theinstanceof operator:

myBook instanceof Book    // truemyBook instanceof String  // false

Note that if the right side of theinstanceof operator isn’t a function, it will throw an error:

myBook instanceof {};// TypeError: invalid 'instanceof' operand ({})

Another way to find the type of an instance is to use theconstructor property. Consider the following code fragment:

myBook.constructor === Book;   // true

The constructor property ofmyBook points toBook, so the strict equality operator returnstrue. Every object in JavaScript inherits aconstructor property from its prototype, which points to the constructor function that has created the object:

var s = new String("text");s.constructor === String;      // true"text".constructor === String; // truevar o = new Object();o.constructor === Object;      // truevar o = {};o.constructor === Object;      // truevar a = new Array();a.constructor === Array;       // true[].constructor === Array;      // true

Note, however, that using theconstructor property to check the type of an instance is generally considered bad practice because it can be overwritten.

Custom constructor functions

A constructor is like a cookie-cutter for making multiple objects with the same properties and methods. Consider the following example:

function Book(name, year) {  this.name = name;  this.year = '(' + year + ')';}

TheBook constructor expects two parameters:name andyear. When the constructor is called with thenew keyword, it assigns the received parameters to thename andyear property of the current instance, as shown below:

var firstBook = new Book("Pro AngularJS", 2014);var secondBook = new Book("Secrets Of The JavaScript Ninja", 2013); var thirdBook = new Book("JavaScript Patterns", 2010); console.log(firstBook.name, firstBook.year);           console.log(secondBook.name, secondBook.year);           console.log(thirdBook.name, thirdBook.year);

This code logs the following to the console:

As you can see, we can quickly build a large number of different book objects by invoking theBook constructor with different arguments. This is exactly the same pattern that JavaScript uses in its built-in constructors likeArray() andDate().

The Object.defineProperty() method

TheObject.defineProperty() method can be used inside a constructor to help perform all necessary property setup. Consider the following constructor:

function Book(name) {   Object.defineProperty(this, "name", {       get: function() {         return "Book: " + name;             },              set: function(newName) {                    name = newName;              },                     configurable: false        }); }var myBook = new Book("Single Page Web Applications");console.log(myBook.name);    // Book: Single Page Web Applications// we cannot delete the name property because "configurable" is set to falsedelete myBook.name;    console.log(myBook.name);    // Book: Single Page Web Applications// but we can change the value of the name propertymyBook.name = "Testable JavaScript";console.log(myBook.name);    // Book: Testable JavaScript

This code usesObject.defineProperty() to define accessor properties. Accessor properties don’t include any properties or methods, but they define a getter to call when the property is read, and a setter to call when the property is written to.

A getter is expected to return a value, while a setter receives the value being assigned to the property as an argument. The constructor above returns an instance whosename property can be set or changed, but cannot be deleted. When we get the value ofname, the getter prepends the stringBook: to the name and returns it.

Object literal notations are preferred to constructors

The JavaScript language has nine built-in constructors:Object(),Array(),String(),Number(),Boolean(),Date(),Function(),Error() andRegExp(). When creating values, we are free to use either object literals or constructors. However, object literals are not only easier to read but also faster to run, because they can be optimize at parse time. Thus, for simple objects it’s best to stick with literals:

// a number object// numbers have a toFixed() methodvar obj = new Object(5);obj.toFixed(2);     // 5.00// we can achieve the same result using literalsvar num = 5;num.toFixed(2);     // 5.00// a string object// strings have a slice() method var obj = new String("text");obj.slice(0,2);     // "te"// same as abovevar string = "text";string.slice(0,2);  // "te"

As you can see, there’s hardly any difference between object literals and constructors. What’s more intersting is that it’s still possible to call methods on literals. When a method is called on a literal, JavaScript automatically converts the literal into a temporary object so that the method can perform the operation. Once the temporary object is no longer needed, JavaScript discards it.

Using the new keyword is essential

It’s important to remember to use thenew keyword before all constructors. If you accidentally forgetnew, you will be modifying the global object instead of the newly created object. Consider the following example:

function Book(name, year) {  console.log(this);  this.name = name;  this.year = year;}var myBook = Book("js book", 2014);  console.log(myBook instanceof Book);  console.log(window.name, window.year);var myBook = new Book("js book", 2014);  console.log(myBook instanceof Book);  console.log(myBook.name, myBook.year);

Here’s what this code logs to the console:

When we call theBook constructor withoutnew, we are in fact calling a function without a return statement. As a result,this inside the constructor points toWindow (instead ofmyBook), and two global variables are created. However, when we call the function withnew, the context is switched from global (Window) to the instance. So,this correctly points tomyBook.

Note that in strict mode this code would throw an error because strict mode is designed to protect the programmer from accidentally calling a constructor without thenew keyword.

Scope-safe constructors

As we have seen, a constructor is simply a function, so it can be called without thenew keyword. But, for inexperienced programmers, this can be a source of bugs. A scope-safe constructor is designed to return the same result regardless of whether it’s called with or withoutnew, so it doesn’t suffer from those issues.

Most built-in constructors, such asObject,Regex andArray, are scope-safe. They use a special pattern to determine how the constructor is called. Ifnew isn’t used, they return a proper instance of the object by calling the constructor again withnew. Consider the following code:

function Fn(argument) {   // if "this" is not an instance of the constructor  // it means it was called without new    if (!(this instanceof Fn)) {     // call the constructor again with new    return new Fn(argument);  } }

So, a scope-safe version of our constructor would look like this:

function Book(name, year) {   if (!(this instanceof Book)) {     return new Book(name, year);  }  this.name = name;  this.year = year;}var person1 = new Book("js book", 2014);var person2 = Book("js book", 2014);console.log(person1 instanceof Book);    // trueconsole.log(person2 instanceof Book);    // true

Conclusion

It’s important to understand that the class declaration introduced in ES2015 simply works as syntactic sugar over the existing prototype-based inheritance and does not add anything new to JavaScript. Constructors and prototypes are JavaScript’s primary way of defining similar and related objects.

In this article, we have taken a good look at how JavaScript constructors work. We learned that constructors are like regular functions, but they are used with thenew keyword. We saw how constructors enable us to quickly make multiple similar objects with the same properties and methods, and why theinstanceof operator is the safest way to determine the type of an instance. Finally, we looked at scope-safe constructors, which can be called with or withoutnew.

Psst! Create a DigitalOcean account and get$200 in free credit for cloud-based hosting and services.

Comments

  1. Daniel Deverell
    Permalink to comment#

    Absolutely! The prototype design pattern never seemed to gel with me. THIS I can live with.

  2. zzzzBov
    Permalink to comment#

    JavaScript has no class statement

    class has been a reserved word for some time, and ECMAScript 2015 makes use of it to provide a form of classical inheritance to JavaScript.

    Browsers don’t yet support theclass keyword, but if you use something likeESNextBabel you can write usingES6 ES2015 features and compile into ES5 compatible JavaScript.

    Whether theclass keyword is actually a “good part” is the subject of quite a bit of discussion. I haven’t started using ES2015 features to be able to say one way or the other yet.

  3. Dhdhdudhdhdhs
    Permalink to comment#

    This articles should contain a section on ES6.

  4. Paul Kane
    Permalink to comment#

    Clear, concise explanation. I’d add that if you wanted to invoke several new constructors in a loop (perhaps from a list of other items), here’s a simple way using an IIFE:

    // Our modest constructor functionfunction MyConstructor(name, order) {    this.name = name,    this.order = order};// Initialize all our new constructors from an arrayvar myConstructorArray = function(yourArrayOfWhatever) {    for (var i = yourArrayOfWhatever.length - 1; i >= 0; i--) {        // This allows the value of i to be captured for the new object        (function(i) {            var newConstructor = new MyConstructor('item-' + (i+1), i+1);            newArray.push(newConstructor);        })(i);    }    return newArray;};
    • Agop
      Permalink to comment#

      You don’t need an IIFE in your example. This will work fine:

      // Our modest constructor functionfunction MyConstructor(name, order) {    this.name = name,    this.order = order};// Initialize all our new constructors from an arrayvar myConstructorArray = function(yourArrayOfWhatever) {    var newArray = [];    for (var i = yourArrayOfWhatever.length - 1; i >= 0; i--) {        // no IIFE required, we're using `i` right away        var newConstructor = new MyConstructor('item-' + (i+1), i+1);        newArray.push(newConstructor);    }    return newArray;};console.log(myConstructorArray([1, 2, 3, 4, 5]));

      Perhaps you meant to show something else?

    • hkhjkhj
      Permalink to comment#

      You can get rid of the awful for-loop entirely and embrace js’ functional side a bit more.

      // Our modest constructor function
      function MyConstructor(name, order) {
      this.name = name;
      this.order = order;
      }

      // Initialize all our new constructors from an array
      var myConstructorArray = function(yourArrayOfWhatever) {
      return yourArrayOfWhatever.map(function(i, value){
      return new MyConstructor(‘item-‘ + (i), i);
      });
      };

      console.log(myConstructorArray([1, 2, 3, 4, 5]));

    • Agop
      Permalink to comment#

      @hkhjkhj: True, themap version is definitely much cleaner. (Just note it’s looping forward, whereas the original example is looping backward.)

    • Robert Smith
      Permalink to comment#

      @Agop to reverse a map transform you can simply just reverse the array then call map. For example:

      var arr = [1,2,3,4,5];console.log(arr.reverse().map(function(value) {  return value + 1;}));// -> [6, 5, 4, 3, 2]
  5. Natb19
    Permalink to comment#

    Wondering how you use this with inheritance / extending objects?

    • sdfsdffsdf
      Permalink to comment#

      In the case of inheritance your probably better off with object.create(). For extending objects you can add shared properties to the prototype.

    • Agop
      Permalink to comment#

      That’s the one thing I wish this article went into (that, and prototype chains).

      Basically, you would do classical JS inheritance like this:

      // Animal class/constructorfunction Animal(){    this.sound = 'Animal sound!';}// Animal `makeSound()` member functionAnimal.prototype.makeSound = function() {    console.log(this.sound);};// Dog class/constructorfunction Dog(){    // call parent constructor to perform initialization logic    Animal.call(this);    // shadow the Animal's `sound` member variable with our own `sound` member variable    this.sound = 'Woof!';}inherit(Dog, Animal);function inherit(childClass, parentClass){    function Wrapper()    {        // the wrapper class does not perform initialization logic    }    // give the wrapper class the same prototype as the parent class    Wrapper.prototype = parentClass.prototype;    // initialize the child's prototype with a new instance of the parent class    // by using the wrapper class, we avoid calling the parent constructor twice    childClass.prototype = new Wrapper();}var dog = new Dog();// this call will traverse the prototype chain until it is found at `Animal.prototype.makeSound`// even so, `this` will refer to the `Dog` class, which shadows the `sound` member variabledog.makeSound();// trueconsole.log('dog instanceof Dog', dog instanceof Dog);// trueconsole.log('dog instanceof Animal', dog instanceof Animal);

      Not the prettiest thing in the world, but it makes a lot of sense in terms of JS’s prototypical inheritance model.

    • Mr N Brown
      Permalink to comment#

      Thanks for the suggestions guys, I’ll give it a try.

  6. Joe
    Permalink to comment#

    I was disappointed this article didn’t go into ES6 classes

  7. Arman Nisch
    Permalink to comment#

    Thank you for this article. I noticed a small typo, should the following sentence “The book constructor expects two parameters: name and age;” not state “year” at the end instead of “age”?

  8. Robert Smith
    Permalink to comment#

    I’m not a fan of this way of creating objects – using constructors and ‘new’. It’s fraught with problems.
    The factory pattern is a much better alternative, as any function can create and return an object and it doesn’t have to be called with ‘new’.
    Eric Elliot explains in more detail why factories are preferable to ‘new’ constructors =>
    http://ericleads.com/2013/01/javascript-constructor-functions-vs-factory-functions/
    As for ES6 class – it’s a bad addition to the language. I’m of the opinion if you want to use classes then don’t write JavaScript.

    I also think inheritance is vastly overused and in modern computing you only really need it if you are creating tens of thousands of objects.
    Function composition is almost always more favourable than inheritance. Senior Spotify developer Mattias Johansson eloquently explains why this is the case on this video: =>https://www.youtube.com/watch?v=wfMtDGfHWpA

  9. Amos
    Permalink to comment#

    you should distinguish property of constructor and Function of constructor.

    var obj = {};obj.constructor  // this property of constructorvar Obj = function() {};  //this Function of constructorvar a = new Obj();
  10. Ken
    Permalink to comment#

    I decided to start taking lessons on Java Script. At first it was confusing since I was just used to the layman’s terms. Thanks to your post has definitely given me the information and clarity I needed. Thank you!

This comment thread is closed. If you have important information to share, pleasecontact us.

[8]ページ先頭

©2009-2025 Movatter.jp