reusable javascript objects, prototypes and scope

后端 未结 2 603
说谎
说谎 2021-01-31 06:41
MyGlobalObject;

function TheFunctionICanUseRightAwaySingleForAllInstansesAndWithoutInstanse() {
    function() {
        alert(\'NO CONSTRUCTOR WAS CALLED\');
    }
};
         


        
相关标签:
2条回答
  • 2021-01-31 06:55

    Variation 1 - Mixin

    function SomeType() {
        var priv = "I'm private";
        this.publ = "I'm public";
        this.action = function() {
            return priv + this.publ;
        };
    }
    
    var obj = new SomeType();
    

    With this method you are creating a new object every time you call new SomeType(), creating all its methods and adding all this method to the new object. Every time you create an object.

    Pros

    • It looks like classical inheritance so it's easy to understand to Java-C#-C++-etc people.
    • It can have private variables per instance since you have one function closure per each object you create
    • It allows multiple inheritance, also known as Twitter-mixins or functional mixins
    • obj instanceof SomeType will return true

    Cons

    • It consumes more memory as more objects you create because with each object you are creating a new closure and creating each of it's methods again.
    • Private properties are private, not protected, subtypes can't access them
    • No easy way to know if a object has some Type as superclass.

    Inheritance

    function SubType() {
        SomeType.call(this);
        this.newMethod = function() {
            // can't access priv
            return this.publ;
        };
    }
    
    var child = new SubType();
    

    child instanceof SomeType will return false there is no other way to know if child has SomeType methods than look if it has them one by one.

    Variation 2 - Object literal with prototyping

    var obj = {
        publ: "I'm public",
        _convention: "I'm public too, but please don't touch me!",
        someMethod: function() {
            return this.publ + this._convention;
        }
    };
    

    In this case you are creating a single object. If you are going to need only one instance of this type it can be the best solution.

    Pros

    • It's quick and easy to understand.
    • Performant

    Cons

    • No privacy, every property is public.

    Inheritance

    You can inherit a object prototyping it.

    var child = Object.create(obj);
    child.otherMethod = function() {
        return this._convention + this.publ;
    };
    

    If you are on a old browser you will need to garantee Object.create works:

    if (!Object.create) {
        Object.create = function(obj) {
            function tmp() { }
            tmp.prototype = obj;
            return new tmp;
        };
    }
    

    To know if a object is a prototype of another you can use

    obj.isPrototypeOf(child); // true
    

    Variation 3 - Constructor pattern

    UPDATE: This is the pattern ES6 classes are sugar syntax of. If you use ES6 classes you are following this pattern under the hood.

    class SomeType {
        constructor() {
            // REALLY important to declare every non-function property here
            this.publ = "I'm public";
            this._convention = "I'm public too, but please don't touch me!";
        }
        someMethod() {
            return this.publ + this._convention;
        }
    }
    
    class SubType extends SomeType {
        constructor() {
            super(/* parent constructor parameters here */);
            this.otherValue = 'Hi';
        }
        otherMethod() {
            return this._convention + this.publ + this.otherValue;
        }
    }
    

    function SomeType() {
        // REALLY important to declare every non-function property here
        this.publ = "I'm public";
        this._convention = "I'm public too, but please don't touch me!";
    }
    
    SomeType.prototype.someMethod = function() {
        return this.publ + this._convention;
    };
    
    var obj = new SomeType();
    

    You can re-assign the prototype insteadd of adding each method if you are not inheriting and remember to re-assign the constructor property:

    SomeType.prototype = {
        constructor: SomeType,
        someMethod = function() {
            return this.publ + this._convention;
        }
    };
    

    Or use _.extend or $.extend if you have underscore or jquery in your page

    _.extend(SomeType.prototype, {
        someMethod = function() {
            return this.publ + this._convention;
        }
    };
    

    The new keyword under the hood simply does this:

    function doNew(Constructor) {
        var instance = Object.create(Constructor.prototype);
        instance.constructor();
        return instance;
    }
    
    var obj = doNew(SomeType);
    

    What you have is a function than has no methods; it just has a prototype property with a list of functions, the new operator means to create a new object and use this function's prototype (Object.create) and constructor property as initializer.

    Pros

    • Performant
    • Prototype chain will allow you to know if a object inherits from some type

    Cons

    • Two-step inheritance

    Inheritance

    function SubType() {
        // Step 1, exactly as Variation 1
        // This inherits the non-function properties
        SomeType.call(this);
        this.otherValue = 'Hi';
    }
    
    // Step 2, this inherits the methods
    SubType.prototype = Object.create(SomeType.prototype);
    SubType.prototype.otherMethod = function() {
        return this._convention + this.publ + this.otherValue;
    };
    
    var child = new SubType();
    

    You may think it looks like a super-set of Variation 2... and you'll be right. It's like variation 2 but with a initializer function (the constructor);

    child instanceof SubType and child instanceof SomeType will return both true

    Curiosity: Under the hood instanceof operator does is

    function isInstanceOf(obj, Type) {
        return Type.prototype.isPrototypeOf(obj);
    }
    

    Variation 4 - Overwrite __proto__

    When you do Object.create(obj) under the hood it does

    function fakeCreate(obj) {
        var child = {};
        child.__proto__ = obj;
        return child;
    }
    
    var child = fakeCreate(obj);
    

    The __proto__ property modifies directly the object's hidden [Prototype] property. As this can break JavaScript behaviour, it's not standard. And the standard way is preferred (Object.create).

    Pros

    • Quick and performant

    Cons

    • Non-standard
    • Dangerous; you can't have a hashmap since the __proto__ key can change the object's prototype

    Inheritance

    var child = { __proto__: obj };
    obj.isPrototypeOf(child); // true
    

    Comment questions

    1. var1: what happens in SomeType.call(this)? Is 'call' special function?

    Oh, yes, functions are objects so they have methods, I will mention three: .call(), .apply() and .bind()

    When you use .call() on a function, you can pass one extra argument, the context, the value of this inside the function, for example:

    var obj = {
        test: function(arg1, arg2) {
            console.log(this);
            console.log(arg1);
            console.log(arg2);
        }
    };
    
    // These two ways to invoke the function are equivalent
    
    obj.test('hi', 'lol');
    
    // If we call fn('hi', 'lol') it will receive "window" as "this" so we have to use call.
    var fn = obj.test;
    fn.call(obj, 'hi', 'lol');
    

    So when we do SomeType.call(this) we are passing the object this to function SomeCall, as you remember this function will add methods to object this.

    2. var3: With your "REALLY define properties" do you mean if I use them in functions? Is it a convention? Because getting this.newProperty without it being defined at the same level with other member functions is not a problem.

    I mean any property your object will have that is not a function must be defined on the constructor, not on the prototype, otherwise you will face one of the more confusing JS problems. You can see it here, but it's outside of the focus of this question.

    3. Var3: what happens if I don't re-assign constructor?

    Actually you might not see the difference and this is what makes it a dangerous bug. Every function's prototype object has a constructor property so you can access the constructor from an instance.

    function A() { }
    
    // When you create a function automatically, JS does this:
    // A.prototype = { constructor: A };
    
    A.prototype.someMethod = function() {
        console.log(this.constructor === A); // true
        this.constructor.staticMethod();
        return new this.constructor();  
    };
    
    A.staticMethod = function() { };
    

    It's not a best practice because not everybody knows about it, but sometimes it helps. But if you reassign the prototype...

    A.prototype = {
        someMethod = function() {
            console.log(this.constructor === A); // false
            console.log(this.constructor === Object); // true
            this.constructor.staticMethod();
            return new this.constructor();  
        }
    };
    

    A.prototype is a new object, a instance of Object than prototypes Object.prototype and Object.prototype.constructor is Object. Confusing, right? :P

    So if you overwrite the prototype and don't reset the "constructor" property, it will refer to Object instead of A, and if you try to use the "constructor" property to access some static method you may get crazy.

    0 讨论(0)
  • 2021-01-31 07:10

    I usually settle with returning an object with functions as properties:

    var newCat = function (name) {
    return {name: name, purr: function () {alert(name + ' purrs')}};
    };
    
    var myCat = newCat('Felix');
    myCat.name; // 'Felix'
    myCat.purr(); // alert fires
    

    You can have inheritance by calling the newCat function and extend the object you get:

    var newLion = function (name) {
        var lion = newCat(name);
        lion.roar = function () {
            alert(name + ' roar loudly');
        }
        return lion;
    }
    

    If you want a global cats object:

    var cats = (function () {
    
    var newCat = function (name) {
        return {
            name: name,
            purr: function () {
                alert(name + ' is purring')
            }
        };
    };
    
    return {
        newCat: newCat
    };
    }());
    

    Now you can call:

    var mySecondCat = cats.newCat('Alice');
    
    0 讨论(0)
提交回复
热议问题