Javascript mixins when using the module pattern

后端 未结 3 1558
心在旅途
心在旅途 2020-12-01 20:23

I\'ve been using the module pattern for a while, but recently have started wanting to mix in functions and properties into them to increase code re-use. I\'ve read some goo

3条回答
  •  南笙
    南笙 (楼主)
    2020-12-01 20:54

    Ideally, I'd like to mix-in some methods from other objects as private methods and some as public methods, so that I could call some "extend" function, with a param as "private"/"public". ...

    As it already has been mentioned, there is no way of achieving exactly this goal.

    So, that ... makes the myMixin methods available within myModule by just calling mixinMethod1() and have correct scope, and: ... makes the myMixin methods available within myModule by calling module.mixinMethod1() and have correct scope.

    And referring to scope ... this is a closed address space created by functions. Except for closures, scope only is available during a function's runtime within this function's body. It never ever can be manipulated/spoofed.

    The term one is looking for is context. JavaScript, being in many ways highly dynamic, is build upon late binding (the object/target/context a method is called on gets evaluated/looked up at runtime) and two kinds of delegation. Context gets delegated either automatically by "walking the prototype chain" or explicitly by one of both call methods which every function object does provide - either call or apply.

    Thus JavaScript already at language core level does offer a function based Mixin pattern that is mightier than any of the available extend(s) or mixin implementations for it provides delegation for free and is able of passing around state which almost every of the blamed helpers does lack unless there was effort of implementing this feature again in a rather roundabout fashion (or ass-backwards to put it bluntly).

    Bergi for his explanation already earned the bounties. Within his answer's last paragraph there is a link to resources of mine that already got outdated 3 month after giving the referred talk. Due of not having enough reputation points, I'm not able to comment his answer directly. For this I'll take the chance pointing now to the latest state of my personal research and understanding of »The many talents of JavaScript for generalizing Role Oriented Programming approaches like Traits and Mixins«

    Back again answering the OP's question.

    I'm going to change the two first given code examples from the assumed module pattern and the rather exemplarily provided mixin code base towards a plain constructor function and what I'm meanwhile tempted to call a "proxified" and/or "bicontextual" mixin in order to boil down the mechanics of delegating two different target/context objects at once. Thus demonstrating a pure function based mixin pattern that might come closest to what the OP tries to achieve.

    var MyBicontextualMixin = function (localProxy) {
    
      localProxy.proxifiedAccessible = function () {
        console.log("proxified accessible.");
      };
      this.publiclyAccessible = function () {
        console.log("publicly accessible.");
      };
    };
    
    var MyConstructor = function () {
      var localProxy = {};
      MyBicontextualMixin.call(this, localProxy);
    
      var locallyAccessible = localProxy.proxifiedAccessible;
    
      // call 'em
      locallyAccessible();        // "proxified accessible."
      this.publiclyAccessible();  // "publicly accessible."
    };
    
    (new MyConstructor);
    
    // will log:
    //
    // proxified accessible.
    // publicly accessible.
    

    This special pattern also is the underlying base for composing pure function based Traits that rely on conflict resolution functionality provided by "proxified" Mixins that won't expose this functionality into public.

    And for not ending up that theoretical there will be a "real world example", composing a Queue module out of various reusable mixins that entirely worship the approach of DRY. It also should answer the OP's question about how to achieve encapsulation and exposition build only upon the module pattern and function based mixin composition.

    var Enumerable_first_last_item = (function (global) {
    
      var
        parseFloat = global.parseFloat,
        math_floor = global.Math.floor,
    
      // shared code.
    
        first = function () {
          return this[0];
        },
        last = function () {
          return this[this.length - 1];
        },
        item = function (idx) {
          return this[math_floor(parseFloat(idx, 10))];
        }
      ;
    
      return function () { // [Enumerable_first_last_item] Mixin.
        var enumerable = this;
    
        enumerable.first = first;
        enumerable.last = last;
        enumerable.item = item;
      };
    
    }(window || this));
    
    
    
    var Enumerable_first_last_item_proxified = function (list) {
      Enumerable_first_last_item.call(list);
    
    // implementing the proxified / bicontextual [Enumerable_first_last_item] Mixin.
      var enumerable = this;
    
      enumerable.first = function () {
        return list.first();
      };
      enumerable.last = function () {
        return list.last();
      };
      enumerable.item = function (idx) {
        return list.item(idx);
      };
    };
    
    
    
    var Allocable = (function (Array) {
    
      var
        array_from  = ((typeof Array.from == "function") && Array.from) || (function (array_prototype_slice) {
          return function (listType) {
    
            return array_prototype_slice.call(listType);
          };
        }(Array.prototype.slice))
      ;
    
      return function (list) { // proxified / bicontextual [Allocable] Mixin.
        var
          allocable = this
        ;
        allocable.valueOf = allocable.toArray = function () {
    
          return array_from(list);
        };
        allocable.toString = function () {
    
          return ("" + list);
        };
        allocable.size = function () {
    
          return list.length;
        };
        Enumerable_first_last_item_proxified.call(allocable, list);
      };
    
    }(Array));
    
    
    
    var Queue = (function () {          // [Queue] Module.
    
      var
        onEnqueue = function (queue, type) {
        //queue.dispatchEvent({type: "enqueue", item: type});
        },
        onDequeue = function (queue, type) {
        //queue.dispatchEvent({type: "dequeue", item: type});
        }/*,
        onEmpty = function (queue) {
        //queue.dispatchEvent({type: "empty"});
        }*/,
        onEmpty = function (queue) {
        //queue.dispatchEvent("empty");
        },
    
        Queue = function () {           // [Queue] Constructor.
          var
            queue = this,
            list = []
          ;
          queue.enqueue = function (type) {
    
            list.push(type);
            onEnqueue(queue, type);
    
            return type;
          };
          queue.dequeue = function () {
    
            var type = list.shift();
            onDequeue(queue, type);
    
            (list.length || onEmpty(queue));
    
            return type;
          };
        //Observable.call(queue);       // applying the [Observable] Mixin.
          Allocable.call(queue, list);  // applying the bicontextual [Allocable] Mixin.
        },
    
        isQueue = function (type) {
          return !!(type && (type instanceof Queue));
        },
        createQueue = function () {     // [Queue] Factory.
          return (new Queue);
        }
      ;
    
      return {                          // [Queue] Module.
        isQueue : isQueue,
        create  : createQueue
      };
    
    }());
    
    
    
    var q = Queue.create();
    
    //q.addEventListener("enqueue", function (evt) {/* ... */});
    //q.addEventListener("dequeue", function (evt) {/* ... */});
    //q.addEventListener("empty", function (evt) {/* ... */});
    
    
    console.log("q : ", q);                     // { .., .., .., }
    console.log("q.size() : ", q.size());       // 0
    console.log("q.valueOf() : ", q.valueOf()); // []
    
    "the quick brown fox jumped over the lazy dog".split(/\s+/).forEach(function (elm/*, idx, arr*/) {
      console.log("q.enqueue(\"" + elm + "\")", q.enqueue(elm));
    });
    
    console.log("q.size() : ", q.size());       // 9
    console.log("q.toArray() : ", q.toArray()); // [ .., .., .., ]
    
    console.log("q.first() : ", q.first());     // "the"
    console.log("q.last() : ", q.last());       // "dog"
    console.log("q.item(2) : ", q.item(2));     // "brown"
    console.log("q.item(5) : ", q.item(5));     // "over"
    
    console.log("q.dequeue()", q.dequeue());    // "the"
    console.log("q.dequeue()", q.dequeue());    // "quick"
    console.log("q.dequeue()", q.dequeue());    // "brown"
    console.log("q.dequeue()", q.dequeue());    // "fox"
    console.log("q.dequeue()", q.dequeue());    // "jumped"
    
    console.log("q.size() : ", q.size());       // 4
    console.log("q.toArray() : ", q.toArray()); // [ .., .., .., ]
    
    console.log("q.first() : ", q.first());     // "over"
    console.log("q.last() : ", q.last());       // "dog"
    console.log("q.item(2) : ", q.item(2));     // "lazy"
    console.log("q.item(5) : ", q.item(5));     // undefined
    .as-console-wrapper { max-height: 100%!important; top: 0; }

提交回复
热议问题