HOW DOES INSTANCEOF WORK?

Một phần của tài liệu reliable javascript how to code safely in the world s most dangerous language spencer richards 2015 07 13 Lập trình Java (Trang 160 - 166)

Listings 3-4 and 3-5 rely on this instanceof Marsupial returning false. The JavaScript instanceof operator inspects the prototype chain of the left-side operand, seeking the prototype of the right-side operand. If the right-side operand’s prototype is found, the left-side operand is

considered to be an instance of the right-side operand.

When a constructor function is executed with the new keyword, JavaScript creates a new empty object, links the new object’s prototype to the

prototype property of the constructor function, and executes the constructor function with this being the new object.

When new is omitted, none of those automatic steps occur. The

constructor function is not bound to a new object when executed; in Listings 3-4 and 3-5 it is bound to the global object. Additionally, the

prototype assignment doesn’t occur, and thus instanceof returns false.

Figure 3.1 shows the error that is output to the console when

var slider = Marsupial('Slider', true) is executed.

Figure 3.1

An alternative to throwing an error when new is not used is to automatically create an instance of an object using new and return that instead, as shown in Listing 3-5.

LISTING 3-5: Automatically create an instance with new (code filename: New Pattern\newpattern_03.js)

function Marsupial(name, nocturnal){

if(!(this instanceof Marsupial)){

return new Marsupial(name, nocturnal);

}

this.name = name;

this.isNocturnal = nocturnal;

}

var slider = Marsupial('Slider', true);

console.log(slider.name); // 'Slider'

Listing 3-5 executes the Marsupial function without using new, but instead of throwing an error as in Listing 3-4, a Marsupial is created with the new

keyword and returned. Using this mechanism, the programmer calling the function need not worry about whether Marsupial should be invoked with the

new keyword; new is automatically used if it has been omitted.

On the surface, the automatic use of the new keyword seems helpful, but

really all it does is allow the programmer to get away with mistakes. Consider the following:

var jester = Marsupial('Jester', false);

var merlin = new Marsupial('Merlin', false);

Both invocations of Marsupial produce an object as if they had been preceded by new even though only one of them is. If your team has adopted the

convention of naming constructor functions with an uppercase first letter, the creation of the jester instance appears to be incorrect.

Consistency begets reliability, and as such we prefer the protection

mechanism presented in Listing 3-4. Throwing an exception when new is omitted ensures that all Marsupial objects will be instantiated in the same way, contributing to a more consistent and reliable codebase. Additionally, when coupled with test-driven development, any exception generated via the omission of new will be identified immediately.

NOTE Automatic semicolon insertion, a JavaScript feature that also lets programmers get away with mistakes and allows for codebases to

become inconsistent, is considered by Douglas Crockford to be one of the awful parts of JavaScript that shouldn’t be relied upon (JavaScript: The Good Parts by Douglas Crockford, O’Reilly Media, 2008). Automatic new insertion, while perhaps not awful, should be avoided for the same

reasons.

The new object creation pattern also allows you to create function properties that are defined once and made available to all instances. Listing 3-6 shows how properties can be added to each instance of an object by defining them directly on the new object in the constructor function. The listing also

illustrates that each object instance has its own copy of the function.

LISTING 3-6: Adding a function directly to the new object (code filename: New Pattern\newpattern_04.js)

function Marsupial(name, nocturnal){

if(!(this instanceof Marsupial)){

throw new Error("This object must be created with new");

}

this.name = name;

this.isNocturnal = nocturnal;

// Each object instance gets its own copy of isAwake this.isAwake = function(isNight){

return isNight === this.isNocturnal;

} }

var maverick = new Marsupial('Maverick', true);

var slider = new Marsupial('Slider', false);

var isNightTime = true;

console.log(maverick.isAwake(isNightTime)); // true console.log(slider.isAwake(isNightTime)); // false // each object has its own isAwake function

console.log(maverick.isAwake === slider.isAwake); // false

Function properties may also be added to the constructor function’s

prototype. Defining functions on the prototype of the constructor function has the added benefit of limiting the number of copies of the function to one, reducing the memory footprint, and increasing performance when creating a large number of object instances.

Listing 3-7 shows how to add a function property to the constructor function’s prototype. The listing also illustrates that each object instance shares the

implementation of the function.

LISTING 3-7: Adding a function to the constructor function’s prototype (code filename: New

Pattern\newpattern_05.js)

function Marsupial(name, nocturnal){

if(!(this instanceof Marsupial)){

throw new Error("This object must be created with new");

}

this.name = name;

this.isNocturnal = nocturnal;

}

// Each object instance shares one copy of isAwake Marsupial.prototype.isAwake = function(isNight){

return isNight === this.isNocturnal;

}

var maverick = new Marsupial('Maverick', true);

var slider = new Marsupial('Slider', false);

var isNightTime = true;

console.log(maverick.isAwake(isNightTime)); // true console.log(slider.isAwake(isNightTime)); // false // the objects share a single instance of isAwake

console.log(maverick.isAwake === slider.isAwake); // true

To illustrate the performance gains that can be realized by utilizing the constructor function’s prototype, we’ve created a sample on

http://jsperf.com which pits the version of Marsupial that doesn’t use the

prototype against the version that does. You can run the comparison in your own browser by visiting http://jsperf.com/performance-prototype-vs-non- prototype, but to satisfy any immediate curiosity you have, Figure 3.2 shows the results of running the test in Chrome version 40 on OSX 10.10.

Figure 3.2

As Figure 3.2 shows, the version of Marsupial that uses the constructor’s

prototype to share a single copy of the isAwake function between all object instances is more than 90 percent faster than the version that creates a copy of the isAwake function for each object instance.

Table 3.4 summarizes how the new object creation pattern stacks up in terms of the SOLID and DRY principles.

Table 3.4 SOLID and DRY Summary for Objects Created with new PRINCIPLE RESULT

Single

Responsibility

Certainly possible, but it’s up to you to make sure that the objects you create are responsible for one thing, and one

thing only. The ability to inject dependencies into constructor functions helps with this.

Open/Closed Yes! The upcoming sections about inheritance illustrate how objects created with new may be extended.

Liskov

Substitution

Yes, through judicious use of inheritance

Interface Segregation

Yes, again through use of inheritance and other code-sharing patterns

Dependency Inversion

A resounding yes. Dependencies may be injected into constructor functions with ease.

Don’t Repeat Yourself

The new object creation pattern results in very DRY code.

Unfortunately, we haven’t found a good way to use AOP with this pattern. This is a significant disappointment because AOP would be handy to encapsulate new enforcement. The reason AOP and new don’t mix is that new creates an object that inherits from the prototype of the object being created. If that object has been wrapped with an aspect, then the aspect’s prototype, not the object’s, will be used. However, nothing prevents you from using AOP to decorate the functions of the

new’d object’s prototype.

Một phần của tài liệu reliable javascript how to code safely in the world s most dangerous language spencer richards 2015 07 13 Lập trình Java (Trang 160 - 166)

Tải bản đầy đủ (PDF)

(696 trang)