Using “Object.create” instead of “new”

后端 未结 15 2154
隐瞒了意图╮
隐瞒了意图╮ 2020-11-22 06:08

Javascript 1.9.3 / ECMAScript 5 introduces Object.create, which Douglas Crockford amongst others has been advocating for a long time. How do I replace new

相关标签:
15条回答
  • 2020-11-22 06:46

    With only one level of inheritance, your example may not let you see the real benefits of Object.create.

    This methods allows you to easily implement differential inheritance, where objects can directly inherit from other objects.

    On your userB example, I don't think that your init method should be public or even exist, if you call again this method on an existing object instance, the id and name properties will change.

    Object.create lets you initialize object properties using its second argument, e.g.:

    var userB = {
      sayHello: function() {
        console.log('Hello '+ this.name);
      }
    };
    
    var bob = Object.create(userB, {
      'id' : {
        value: MY_GLOBAL.nextId(),
        enumerable:true // writable:false, configurable(deletable):false by default
      },
      'name': {
        value: 'Bob',
        enumerable: true
      }
    });
    

    As you can see, the properties can be initialized on the second argument of Object.create, with an object literal using a syntax similar to the used by the Object.defineProperties and Object.defineProperty methods.

    It lets you set the property attributes (enumerable, writable, or configurable), which can be really useful.

    0 讨论(0)
  • 2020-11-22 06:46

    Object.create is not yet standard on several browsers, for example IE8, Opera v11.5, Konq 4.3 do not have it. You can use Douglas Crockford's version of Object.create for those browsers but this doesn't include the second 'initialisation object' parameter used in CMS's answer.

    For cross browser code one way to get object initialisation in the meantime is to customise Crockford's Object.create. Here is one method:-

    Object.build = function(o) {
       var initArgs = Array.prototype.slice.call(arguments,1)
       function F() {
          if((typeof o.init === 'function') && initArgs.length) {
             o.init.apply(this,initArgs)
          }
       }
       F.prototype = o
       return new F()
    }
    

    This maintains Crockford prototypal inheritance, and also checks for any init method in the object, then runs it with your parameter(s), like say new man('John','Smith'). Your code then becomes:-

    MY_GLOBAL = {i: 1, nextId: function(){return this.i++}}  // For example
    
    var userB = {
        init: function(nameParam) {
            this.id = MY_GLOBAL.nextId();
            this.name = nameParam;
        },
        sayHello: function() {
            console.log('Hello '+ this.name);
        }
    };
    var bob = Object.build(userB, 'Bob');  // Different from your code
    bob.sayHello();
    

    So bob inherits the sayHello method and now has own properties id=1 and name='Bob'. These properties are both writable and enumerable of course. This is also a much simpler way to initialise than for ECMA Object.create especially if you aren't concerned about the writable, enumerable and configurable attributes.

    For initialisation without an init method the following Crockford mod could be used:-

    Object.gen = function(o) {
       var makeArgs = arguments 
       function F() {
          var prop, i=1, arg, val
          for(prop in o) {
             if(!o.hasOwnProperty(prop)) continue
             val = o[prop]
             arg = makeArgs[i++]
             if(typeof arg === 'undefined') break
             this[prop] = arg
          }
       }
       F.prototype = o
       return new F()
    }
    

    This fills the userB own properties, in the order they are defined, using the Object.gen parameters from left to right after the userB parameter. It uses the for(prop in o) loop so, by ECMA standards, the order of property enumeration cannot be guaranteed the same as the order of property definition. However, several code examples tested on (4) major browsers show they are the same, provided the hasOwnProperty filter is used, and sometimes even if not.

    MY_GLOBAL = {i: 1, nextId: function(){return this.i++}};  // For example
    
    var userB = {
       name: null,
       id: null,
       sayHello: function() {
          console.log('Hello '+ this.name);
       }
    }
    
    var bob = Object.gen(userB, 'Bob', MY_GLOBAL.nextId());
    

    Somewhat simpler I would say than Object.build since userB does not need an init method. Also userB is not specifically a constructor but looks like a normal singleton object. So with this method you can construct and initialise from normal plain objects.

    0 讨论(0)
  • 2020-11-22 06:50

    There is really no advantage in using Object.create(...) over new object.

    Those advocating this method generally state rather ambiguous advantages: "scalability", or "more natural to JavaScript" etc.

    However, I have yet to see a concrete example that shows that Object.create has any advantages over using new. On the contrary there are known problems with it. Sam Elsamman describes what happens when there are nested objects and Object.create(...) is used:

    var Animal = {
        traits: {},
    }
    var lion = Object.create(Animal);
    lion.traits.legs = 4;
    var bird = Object.create(Animal);
    bird.traits.legs = 2;
    alert(lion.traits.legs) // shows 2!!!
    

    This occurs because Object.create(...) advocates a practice where data is used to create new objects; here the Animal datum becomes part of the prototype of lion and bird, and causes problems as it is shared. When using new the prototypal inheritance is explicit:

    function Animal() {
        this.traits = {};
    }
    
    function Lion() { }
    Lion.prototype = new Animal();
    function Bird() { }
    Bird.prototype = new Animal();
    
    var lion = new Lion();
    lion.traits.legs = 4;
    var bird = new Bird();
    bird.traits.legs = 2;
    alert(lion.traits.legs) // now shows 4
    

    Regarding, the optional property attributes that are passed into Object.create(...), these can be added using Object.defineProperties(...).

    0 讨论(0)
  • 2020-11-22 06:50

    You could make the init method return this, and then chain the calls together, like this:

    var userB = {
        init: function(nameParam) {
            this.id = MY_GLOBAL.nextId();
            this.name = nameParam;
            return this;
        },
        sayHello: function() {
            console.log('Hello '+ this.name);
        }
    };
    
    var bob = Object.create(userB).init('Bob');
    
    0 讨论(0)
  • 2020-11-22 06:51

    TL;DR:

    new Computer() will invoke the constructor function Computer(){} for one time, while Object.create(Computer.prototype) won't.

    All the advantages are based on this point.

    Sidenote about performance: Constructor invoking like new Computer() is heavily optimized by the engine, so it may be even faster than Object.create.

    0 讨论(0)
  • 2020-11-22 06:53

    I prefer a closure approach.

    I still use new. I don't use Object.create. I don't use this.

    I still use new as I like the declarative nature of it.

    Consider this for simple inheritance.

    window.Quad = (function() {
    
        function Quad() {
    
            const wheels = 4;
            const drivingWheels = 2;
    
            let motorSize = 0;
    
            function setMotorSize(_) {
                motorSize = _;
            }
    
            function getMotorSize() {
                return motorSize;
            }
    
            function getWheelCount() {
                return wheels;
            }
    
            function getDrivingWheelCount() {
                return drivingWheels;
            }
            return Object.freeze({
                getWheelCount,
                getDrivingWheelCount,
                getMotorSize,
                setMotorSize
            });
        }
    
        return Object.freeze(Quad);
    })();
    
    window.Car4wd = (function() {
    
        function Car4wd() {
            const quad = new Quad();
    
            const spareWheels = 1;
            const extraDrivingWheels = 2;
    
            function getSpareWheelCount() {
                return spareWheels;
            }
    
            function getDrivingWheelCount() {
                return quad.getDrivingWheelCount() + extraDrivingWheels;
            }
    
            return Object.freeze(Object.assign({}, quad, {
                getSpareWheelCount,
                getDrivingWheelCount
            }));
        }
    
        return Object.freeze(Car4wd);
    })();
    
    let myQuad = new Quad();
    let myCar = new Car4wd();
    console.log(myQuad.getWheelCount()); // 4
    console.log(myQuad.getDrivingWheelCount()); // 2
    console.log(myCar.getWheelCount()); // 4
    console.log(myCar.getDrivingWheelCount()); // 4 - The overridden method is called
    console.log(myCar.getSpareWheelCount()); // 1
    

    Feedback encouraged.

    0 讨论(0)
提交回复
热议问题