Quantcast
Channel: Open »» javascript
Viewing all articles
Browse latest Browse all 21

Using ECMAScript 5 Objects and Properties

$
0
0

With ECMAScript 6 inching closer and closer to becoming an accepted standard, it’s a perfect time to look at some of the incredibly useful changes in the last big ES release. ECMAScript 5, published in 2009, gave JavaScript developers some great new options. One of its most powerful additions was Object.defineProperty. This method allows you to make better use of the browser’s internals. John Resig explains:

This new code gives you the ability to dramatically affect how users will be able to interact with your objects, allowing you to provide getters and setters, prevent enumeration, manipulation, or deletion, and even prevent the addition of new properties. In short: You will be able to replicate and expand upon the existing JavaScript-based APIs (such as the DOM) using nothing but JavaScript itself.

This post shows you how to use Object.defineProperty and some other useful Object static methods. If you’re completely unfamiliar with those methods, you might want to review these resources before you read further:

(Note: there’s no shim for Object.defineProperty, but you can use it via Node.js.)

Creating a Class

In JavaScript, creating a class is pretty simple: define a function, then add some properties and methods. But defining a class this way has some serious flaws. Everything you do can be overridden!

Let’s take a look.

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

If we instantiate this class, we can change the values at any time. We could even modify methods on the prototype. Depending on our requirements, this might be fine. But in this example, we can change the dog’s name, which doesn’t seem desirable. Let’s fix this using Object.defineProperties, and also provide a setter and getter for age while we’re at it:

function Dog(name, age) {
 
Object.defineProperties(this, {
    name: {
        value: name,
        enumerable: true
    },
    age: {
        set: function (value) {
            value = parseInt(value, 10);
            if (isNaN(value)) throw new Error("Value set on age is not a number");
            age = value;
        },
        get: function () {
            return age;
        },
        enumerable: true
    }
});
}

The name property now has a value. And the writable attribute, which we didn’t set for name, defaults to false. So Fido’s name will stick.

Additionally, the age property now has a setter and a getter. Whenever we set a value for age, it will automatically be converted to a Number or throw an error.

Awesome, but we’re not done yet! Let’s add some methods to our Dog class:

Object.defineProperties(Dog.prototype, {
    bark: {
        value: function () {
            console.log("Bark!");
        }
    } 
});

Dog now has a basic bark method, but notice the omission of the enumerable attribute. This attribute is false by default. If a user of this class decides to iterate over a Dog object for some reason, the bark method will be omitted from the iteration — the user won’t need to test whether the current property is a function. Nice!

(You can always use the Object.getOwnPropertyNames method to see all properties on the prototype, enumerable or not.)

Adding Inheritance

At this point, we have a fully working Dog class. But there are many dog breeds. Let’s start subclassing!

In this section, we’ll be using a heavily modified inheritance script that CoffeeScript generates to “subclass” objects. Review the extend script, then come back here to create some subclasses.

Note: Most of the complexity of this post lies in the extend script. It’s thoroughly commented to help you understand how it works.

Let’s create a Bulldog class:

__extends(Bulldog, Dog);
 
function Bulldog() {
    Dog.super.constructor.apply(this, arguments);
}
 
Object.defineProperties(Bulldog.prototype, {
    bark: {
        value: function () {
            console.log("Wooooof");
        }
    },
    beLazy: {
        value: function () {
            console.log("Zzzzzzz.");
        }
    }
});

Pretty simple. We just override Dog‘s bark method. Neither method has properties set other than value. That keeps them nice and safe: these properties cannot be overridden or deleted.

Going Final

JavaScript doesn’t have classes, so we simulate them. JavaScript doesn’t have the notion of final for a “class” either, but we can approximate it. Object.freeze allows us to stop any sort of modification of an object.

Let’s make sure our Bulldog class isn’t extensible. The extends method will throw an error if any parent.prototype returns true for Object.isFrozen.

All we have to do is call Object.freeze(Bulldog.prototype). If we then try to extend this class, we’ll get a helpful error:

  TypeError: Extending this class is forbidden

Strict Mode

ECMAScript’s strict mode is not required to use these object property features, but strict mode is very helpful for debugging purposes. In strict mode, if you try to change the value of a property that isn’t writable or try to delete a property that isn’t configurable, a clear error will be thrown.

Demo

Let’s put it all together. This code should run in all modern browsers and in a Node.js environment.

Browser Demo

Postscripts

Sealing Objects

Let’s say we want a subclass to be able to override existing methods, but not add more methods to the object. Object.seal prevents new methods from being added to the prototype and also prevents them from being deleted. All writable methods remain writable (and all unwritable remain unwritable).

Object.seal(Bulldog.prototype)
Bulldog.prototype.foo = function () {};
// TypeError: Can't add property foo, object is not extensible

Constants

On a related note, Object.freeze allows us to make real (sort of) constants!

var CONST: {
    PI: Math.PI,
    FOO: "bar"
};
 
Object.freeze(CONST);

Conclusion

Object.defineProperty helps you make your code more rigorous using native JavaScript. You can protect objects and properties without wasting time on custom code or extra syntax. JavaScript feels the same as you use your objects, but with the delicious sugar of set, get, writable, configurable, and enumerable.

Thanks to ECMAScript, we can add some strictness to a pretty loose language. You may not find all of these techniques to be realistic for a project, but you can abstract some smaller ideas from them to make your code safer and stronger.


Viewing all articles
Browse latest Browse all 21

Latest Images

Trending Articles





Latest Images