Is it possible to using Javascript prototypes with

2019-05-12 12:46发布

问题:

The fact that Javascript uses functions to create objects was confusing to me at first. An example like this is often used to highlight how prototypes work in Javascript:

function Car(){
  this.setModel=function(model){
    this.model=model;
  }
  this.getModel=function(){
    return this.model;
  }
}

function Bus(){}
Bus.prototype=new Car();

var obj=new Bus();
obj.setModel('A Bus');
alert(obj.getModel('A Bus');

Is it possible to use prototypes without using new FunctionName()? I.e. something like this:

var Car={
  setModel:function(model){
    this.model=model;
  },
  getModel:function(){
    return this.model
  }
}

var Bus={
  prototype:Car;
};

var obj=Bus;
obj.setModel('A Bus');
alert(obj.getModel());

This does not use functions and new to create objects. Rather, it creates objects directly.

Is this possible without deprecated features like Object.__proto__ or experimental functions like Object.setPrototypeOf()?

回答1:

Object.create gets you the behavior that you are looking for, but you have to call it instead of new:

// Using ES6 style methods here
// These translate directly to
// name: function name(params) { /* implementation here */ }
var Car = {
 setModel(model) {
    this.model = model;
  },
  getModel() {
    return this.model
  }
};

var Bus = Object.create(Car);

var obj = Object.create(Bus);
obj.setModel('A Bus');
alert(obj.getModel());

Alternatively, you can use the new ES 2015's __proto__ property to set the prototype at declaration time:

var Bus = {
  __proto__: Car
};

// You still need Object.create here since Bus is not a constructor
var obj = Object.create(Bus);
obj.setModel('A Bus');
alert(obj.getModel());

Some additional notes

You should add the methods to Car.prototype not inside of the constructor unless you need the private state (this way there is only one instance of the setModel method, rather than one instance of the method per instance of the class):

function Car() {}
Car.prototype.setModel = function(model) { this.model = model; };
Car.prototype.getModel = function(model) { return this.model; };

You can get around the new Car oddness with Object.create even with constructor functions:

function Bus {}
Bus.prototype = Object.create(Car);


回答2:

Crockford has quite a good chapter on this subject in The Good Parts.

In it he points out one big flaw with using new on constructor functions:

Even worse, there is a serious hazard with the use of constructor functions. If you forget to use the new prefix when calling a constructor function, then this will not be bound to a new object... There is no compile warning, and there is no runtime warning.

(You may already be aware of this, hence your question, but it's worth reiterating)

His proposed solution is to recommend a home rolled Object.create (The Good Parts predates ES5 by a bit, but the idea is the same as the native version mentioned in the other answers).

if (typeof Object.create !== 'function') {
    Object.create = function (o) {
        var F = function () { };
        F.prototype = o;
        return new F();
    };
}

Object.create takes an instance of an object to use as the prototype and returns an instance.

The example used is very similar to your "something like this" example (except Crocky uses mammals and cats instead of cars and buses):

var car = {
  setModel:function(model){
    this.model=model;
  },
  getModel:function(){
    return this.model
  }
};

var bus = Object.create(car);

bus.setModel('A Bus');
alert(bus.getModel());



回答3:

You can use Object.create() to achieve. Here is a crude example of prototypal inheritance using this :

// Car constructor
var Car = function() {};
Car.prototype = {
    setModel: function(){},
    getModel: function(){}
};

// Bus constructor
var Bus = function() {
    Car.call(this); // call the parent ctor
};

Bus.prototype = Object.create(Car.prototype); // inherit from Car

var my_bus = new Bus(); // create a new instance of Bus
console.log(my_bus.getModel());


回答4:

Prototypes allow you to make new object instances from other object instances. The new instance stands on its own and can be changed however you like.

var bus = Object.clone(Car.prototype);
bus.number = '3345';
bus.route = 'Chicago West Loop';
bus.setModel('A bus');


回答5:

To use an instance as prototype in order to inherit shows a lack of understanding what prototype is. Prototype shares members and a mutable instance member of Parent on the prototype of Child gets you very unexpected behavior. Setting Car.prototype to Object.create(Bus.prototype) has already covered by others, if you want to see the role of the constructor and the role of the prototype maybe the following answer can help: https://stackoverflow.com/a/16063711/1641941

To demonstrate what could go wrong let's introduce an instance specific mutable member called passengers we would like Bus to initialize this so we can't use Jeff's example as that only takes care of the prototype part. Let's use constructor functions but create an instance of Bus for Car.prototype.

var Bus = function Bus(){
  this.passengers=[];
}
var Car = function Car(){};
Car.prototype = new Bus()
var car1=new Car();
var car2=new Car();
car1.passengers.push('Jerry');
console.log(car2.passengers);//=['Jerry']
  //Yes, Jerry is a passenger of every car you created 
  //and are going to create

This error could be resolved by shadowing instance properties by re using Parent constructor (Bus.call(this... as provided later) but Car.prototype still has a passengers member that has no business being there.

If you want to protect agains people forgetting "new" you can do the following:

function Car(){
  if(this.constructor!==Car){
    return new Car();
  }
  //re use parent constructor
  Bus.call(this);
}
//Example of factory funcion
Car.create=function(arg){
  //depending on arg return a Car
  return new Car();
};
Car.prototype = Object.create(Bus.prototype);
Car.prototype.constructor = Car;

var car1 = Car();//works
var car2 = new Car();//works
var car3 = Car.create();//works


回答6:

One approach - create a Car and use Object.create to make a copy and then extend Bus functionality as needed

(function () {

  "use strict";
  
   var setModel = function (model) {
    this.model = model;
  };

  var getModel = function () {
    return this.model;
  };

  var iAmBus = function () {
    alert("only a bus!");
  };

  var Car = {
      setModel: setModel,
      getModel: getModel
    };

  var Bus = Object.create(Car);

  // own bus property, not in the car
  Bus.iAmBus = iAmBus;

  var bus = Object.create(Bus);
  bus.setModel('A Bus');
  alert(bus.getModel());

  var car = Object.create(Car);
  car.setModel('A Car');
  alert(car.getModel());

  //inspect bus and car objects, proving the different object structure
  console.log(bus);
  console.log(car);
  console.log(Bus);
  console.log(Car);

}());