开发者

subclass.prototype = new superclass() vs. subclass = new superclass()

I've been instantiating subclasses in javascript using

object = new class ()

but I notice some people instantiate using

object.prototype = new class ()

Question: What's the difference? To me it seems like the latter is respecting the inheritance chain more because if class () has a bunch of "this.variable = x" statements, and object is something you want to inherit from it rather than an instance of class, you are accurately assigning those variables to object's prototype rather than to the object itself as in the former case. So in effect it's开发者_JS百科 like this?

object = new class () |vs.| subclass.prototype = new superclass ()

However, functionally in the program both are the same?

Side question: Also I'm a bit unclear as to what the new operator actually does. It seems to me to do something like just create an empty object and assign it's proto property?


The code samples in your question reflect a couple of misunderstanding. Let's address them first:

  • class is a reserved keyword in Javascript. You cannot use class as the name of any variable or function. This is not because the Javascript language makes any use of the keyword, but because it was planned for a possible future use.
  • there are no real classes in Javascript. What you may have seen, is different flavors of attempts to simulate class inheritance by using the available inheritance mechanism in Javascript, which is based on prototype objects, sharing properties with linked instances
  • very important: the prototype property used in this inheritance mechanism is set on a function, not directly on objects

Quoting Douglas Crockford in Chapter 5, Inheritance, of JavaScript: The Good Parts:

Instead of having objects inherit directly from other objects, an unnecessary level of indirection is inserted such that objects are produced by constructor functions.

(...)

When a function is invoked with the constructor invocation pattern using the new prefix, this modifies the way in which the function is executed.

Douglas Crockford then explains how the new operator could be implemented as a JavaScript function. This function makes use of several other functions defined in the book, so I rewrote it in a (somewhat) simpler form below:

function createNew(constructor) {
  // a function to explain the new operator:
  //   var object = createNew(constructor);
  // is equivalent to
  //   var object = new constructor();
  //
  // param: constructor, a function
  // return: a new instance of the "constructor" kind of objects

  // step 1. create a new empty object instance
  //         linked to the prototype of provided constructor  
  var hiddenLink = function(){};
  hiddenLink.prototype = constructor.prototype;
  var instance = new hiddenLink(); // cheap trick here: using new to implement new

  // step 2. apply the constructor the new instance and get the result
  var result = constructor.apply(object); // make this a reference to instance within constructor

  // step 3. check the result, and choose whether to return it or the created instance
  if (typeof result === 'object') {
    return object;
  } else {
    return instance;
  } 
}

In simple English, if you call new constructor(), where constructor is a function, the operator creates a new object with a link to inherit properties from the constructor, applies the constructor function to it, and returns either the value returned by the constructor, or the new object in case the constructor returned something else which is not an object.

At any time, before or after creating new instances using a custom constructor, you may modify the prototype (object) on the constructor (function):

function constructor(){}  // the most simple constructor function: does nothing
var before = new constructor();
var different = new constructor();
different.answer = "So long, and thanks for all the fish";

constructor.prototype = {};             // set an empty object to the prototype property
constructor.prototype.answer = 42;      // create a new property on prototype object
constructor.prototype.answer = Math.PI; // replace an existing property

var after = new constructor();

Through the hidden link added to all objects created using this constructor (see "cheap trick" in createNew), the properties of the prototype object can be accessed on all these instances, unless overridden by properties defined on the objects directly.

before.answer === Math.PI; // true
after.answer === Math.PI;  // true
different.answer === "So long, and thanks for all the fish"; // true

Using this newly acquired knowledge, how would you create a new "class" of objects that inherit all the properties of arrays, together with a new method empty() to remove all elements?

First, there are no classes in Javascript, so in order to create a new "class", I have to define a new constructor function. Let's call it CustomArray, with a capital C to follow the convention that constructor functions should start with a capital.

function CustomArray(){}

I can now create custom instances:

var myArray = new CustomArray(); 

Second, I want instances created with CustomArray to inherit Array properties:

myArray.prototype = new Array(); // WRONG EXAMPLE: we must set CustomArray.prototype
CustomArray.prototype = Array;   // WRONG EXAMPLE: prototype expects an object, Array is a function
CustomArray.prototype = new Array(); // OK, BUT: the simpler form [] should be used instead
CustomArray.prototype = [];

Third, I want all instances created with CustomArray to have the empty() method:

function empty(){
    // empty this array by setting its length to 0
    // function to be called in the context (this) of an array
    this.length = 0;
}
CustomArray.prototype.empty = empty; // set the function named empty to the property "empty" of the prototype

Finally, I can rewrite the whole example in a more concise way:

function CustomArray(){}
CustomArray.prototype = [];
CustomArray.prototype.empty = function(){ this.length = 0; }

I can now create a custom array, set a couple of values and empty it:

var myArray = new CustomArray();
myArray[0] = "abc";
myArray[1] = "def";
myArray[2] = "ghi";
myArray.empty();

The issue is: the above code does not work as expected. Why? Because unlike in regular arrays, setting values in our custom array does not automagically increase the length property of the array. Likewise, calling empty() only sets the length property of our custom array to 0, it does not delete all the values within.

Besides, we could not use the array literal syntax to initialize our custom array:

var myArray = ["abc","def","ghi"]; // this creates a regular array

All in all, it is important to understand how Javascript inheritance works, but you may often find it less useful than expected and there are simpler ways to achieve the same result, for example by using builder functions instead of constructors. We can solve the problem by using a builder function customizeArray to extend regular arrays:

function customizeArray(array){
  array.empty = function(){
    this.length = 0;
  }; 
}
var myArray = ["abc","def","ghi"];
customizeArray(myArray);
myArray.empty();

This code works as expected because myArray is a regular array in this case, extended with a new method named empty. The main advantage and drawback of this approach in comparison with using prototypes is that it modifies only the selected instance, and if you were working with lots of similar objects at the same time, setting this extra property would use more memory than setting a single shared property on a common prototype.

To avoid that, you may be tempted to modify the Array prototype directly:

Array.prototype.empty = function(){
  this.length = 0;
};
var myArray = ["abc","def","ghi"];
myArray.empty();

It works, but I would advise against it: you are attaching a custom property to every array instance, including all those created by those fancy libraries, plugins, frameworks, advertising and analytics scripts, all the code on your page. Needless to say that it may break something in a place where you cannot fix it.

Edit: Interesting post on kangax's blog as a follow-up: "How ECMAScript 5 still does not allow to subclass an array"


The difference is that when you do:

var subclass = new superclass();

you are creating an instance of superclass. subclass is just variable. You are not creating a sub-class (ie. making subclass inherit superclass). In the latter example, assuming subclass is a function, you are saying that all new instances of subclass should inherit (ie. sub-class) superclass.

So:

function superclass() {this.stuff="stuff";}
function subclass() {}
subclass.prototype = new superclass();
alert(new subclass().this); // pops up "stuff"

is prototypical inheritance.

As for the new operator, it is used for creating an instance of built-in objects and user defined types. A user defined type is simply a function.

Edit: When I wrote above that subclass inherits supertype using prototypical inheritance, I mean that all new instances of subclass inherit from one particular instance of superclass, not from the superclass type/function itself.


Sharing a quick demo of Javascript inheritance after reading the mozilla doc

function Employee (name, dept) {
    this.name = name || "";
    this.dept = dept || "";
}

function Programmer (name, projs) {
    Employee.call(this, name, "programming");
    this.projects = projs || [];
}
Programmer.prototype = new Employee;

// demo dynamic inheritance
Employee.prototype.leave = 10;

var johnny = new Programmer("Johnny", ["C#","Java"]);
alert("name: " + johnny.name + "\n"
      + "dept: " + johnny.dept + "\n"
      + "projects: " + johnny.projects + "\n"
      + "leave: " + johnny.leave);

var mary = new Programmer("Mary", ["Javascript","Java"]);
alert("name: " + mary.name + "\n"
      + "dept: " + mary.dept + "\n"
      + "projects: " + mary.projects + "\n"
      + "leave: " + mary.leave);

alert("changing leave of all staff to 8");
Employee.prototype.leave = 8;
alert("Johnny leave: " + johnny.leave); // 8
alert("Mary leave: " + mary.leave); // 8

alert("cannot batch move staff to another department");
Employee.prototype.dept = "sales";
alert("Johnny dept: " + johnny.dept); // programming
alert("Mary dept: " + mary.dept); // programming

Demo in jsfiddle Demo with more debug in jsfiddle

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜