简体   繁体   中英

The difference between implicit and explicit mixins

Examples are taken from You Don't Know JS this & Object Prototypes ( here is the specific chapter, scroll down a bit or just search for the word "mixin", "explicit" or "implicit", the articles should show up).

In that book, a "mixin" is explained as "mixing in the behavior of different objects" (paraphrasing).

However, I can't comprehend the difference between Explicit and Implicit mixins.

Here is an explicit mixin:

// vastly simplified `mixin(..)` example:
function mixin( sourceObj, targetObj ) {
    for (var key in sourceObj) {
        // only copy if not already present
        if (!(key in targetObj)) {
            targetObj[key] = sourceObj[key];
        }
    }

    return targetObj;
}

var Vehicle = {
    engines: 1,

    ignition: function() {
        console.log( "Turning on my engine." );
    },

    drive: function() {
        this.ignition();
        console.log( "Steering and moving forward!" );
    }
};

var Car = mixin( Vehicle, {
    wheels: 4,

    drive: function() {
        Vehicle.drive.call( this );
        console.log( "Rolling on all " + this.wheels + " wheels!" );
    }
} );

And here is an implicit mix-in:

var Something = {
    cool: function() {
        this.greeting = "Hello World";
        this.count = this.count ? this.count + 1 : 1;
    }
};

Something.cool();
Something.greeting; // "Hello World"
Something.count; // 1

var Another = {
    cool: function() {
        // implicit mixin of `Something` to `Another`
        Something.cool.call( this );
    }
};

Another.cool();
Another.greeting; // "Hello World"
Another.count; // 1 (not shared state with `Something`)



The only difference I can find is that the first example uses a function to copy all the behavior of Vehicle into Car , while the second example only borrows a single method. So I think in the first example, Vehicle 's behavior is explicitly copied into Car , while in the second example, only a method is referenced with its this bound to Another , therefore an implicit mix-in ? Is this where the difference lies ? if that is the difference, then the first example contains an example of an Implicit Mix-in as well, since it also references and binds the this of one of Vehicle 's methods. If my assumptions are correct, then IMO, the first example should be called a "Full mix-in" and the latter should be called "Partial mix-in", since these names fit the behaviors better.

I searched online and aside from the You Don't Know JS chapter, only 1 article showed up, and that one didn't mention "implicit mix-in" or "explicit mix-in".

So, what is the difference ?

I agree with your statement that in the first example, Vehicle 's behavior is explicitly copied into Car . That's why it could be called an explicit mixin. The programmer explicitly states that she is going to reuse methods from the Vehicle object.

I wouldn't however call this a full mixin , because the mixin function does not make a full copy of the Vehicle methods, but only copies methods that are not present in the Car object.

In the second example, the programmer doesn't openly or explicitly express that she is going to extend (= the name for the mixin function in some popular libraries) the Another object. The only way to find out that the Another object is reusing behavior from Something is by looking at how the different methods are defined. It's only inside the definitions (hence implicit ) that you find out that the object borrows methods from another object.

I think of it like this...

  • A mixin could be called ex plicit if the extend operation is clearly ex pressed in the code.
  • A mixin could be called im plicit if a method of another object is used in side the definition of a method.

I will leave each of my three upvotes untouched cause all Q & A have a thoughtful mind in common. But thinking over and over again any of the examples, Q and A, I will contribute my 2cents.

As I see it, Kyle's/@Keith's first example already is unique due to the property copying mixin function which uses a guard.

On one hand this is not what one would expect from a mixin since most mixin based composition (in theory and practice) just overwrites already existing behavior (the last applied behavior wins).

On the other hand there comes the advantage of being in control of the target object's implementation of a behavior that otherwise would be overwritten ( drive from the given first example). This sole simple guard trick creates a special pattern that does not meet at all the functionality/mechanics of a classic mixin pattern/approach ... let's invent the term fill-in for it, for it leaves existing behavior untouched but fills the other gaps of not yet existing behavior.

And still talking about the advantage of controlling the target object's behavior ... Kyle's approach does remain appealing ... because ... with classic mixins one can not resolve conflicts of equally named methods/behavior, that's what traits are for. Traits do provide functionality/operators for composition that one buys with some overhead.

Conclusion:

The pattern of Kyle's 1st example could be referred to as (conflict avoiding) fill-in . In addition this example does implement an otherwise conflicting method - drive - that partially uses explicit delegation - thus it borrows another object's method by explicitly calling/invoking this method within the context of another object( ... the delegate).

The provided 2nd example then obviously is neither close to any form of mixins nor to the above labeled fill-in. It is just code that again uses explicit delegation via one of both JavaScript's call methods ... call / apply .

A classic/real mixin will always be used explicitly. Every mixin does carry behavior that either explicitly will be mixed into another object (object and copy based approach) or that explicitly will be applied to an other object (function based mixins/ Flight Mixins that are applied to objects via call / apply ).


Addendum

... or that explicitly will be applied to an other object (function based mixins/Flight Mixins that are applied to objects via call/apply).

Does this mean apply and call actually result in a sort of application mixin ? If so, doesn't that make the 2nd example a sort of mix-in as well ?

Thanks for asking this question. While writing the above, I did hope someone will point exactly to that.

There is a difference in writing ...

 // generic template ... generic implementation of a `last` list method function getLastItemOfAppliedListContext() { return this[this.length - 1]; } var arr = [9, 8, 7], str = 'foo bar'; console.log('arr : ', arr); // delegation ... invoke the above template within an explicitly applied context console.log('getLastItemOfAppliedListContext.call(arr) : ',getLastItemOfAppliedListContext.call(arr)); console.log('str : ', str); // delegation ... invoke the above template within an explicitly applied context console.log('getLastItemOfAppliedListContext.call(str) : ',getLastItemOfAppliedListContext.call(str)); 
 .as-console-wrapper { max-height: 100%!important; top: 0; } 

... or following an approach like that ...

 function withGetLastListItem() { // function based mixin approach … this.last = function () { // … providing a generic implementation … return this[this.length - 1]; // … of a `last` list method / behavior. } } var list = {length: 3, 0: 'foo', 1: 'bar', 2: 'baz'}, arr = [9, 8, 7], str = 'foo bar'; // does create a closure each ... withGetLastListItem.call(list); // ... over `list`, withGetLastListItem.call(arr); // ... over `arr`, withGetLastListItem.call(String.prototype); // ... and over `String.prototype` // ... applying to each a very own enclosed `last` method/behavior. console.log('list : ', list); console.log('list.last() : ', list.last()); console.log('arr : ', arr); console.log('arr.last() : ', arr.last()); console.log('str : ', str); console.log('str.last() : ', str.last()); console.log('list.last : ', list.last); console.log('arr.last : ', arr.last); console.log('str.last : ', str.last); // will always be `false` due to each type carrying a very // own enclosed version of the same `last` implementation console.log('(list.last === arr.last) ? ', (list.last === arr.last)); console.log('(list.last === str.last) ? ', (list.last === str.last)); 
 .as-console-wrapper { max-height: 100%!important; top: 0; } 

The first provided code block is an example of straightforward method delegation. The second example uses delegation in many ways. Firstly a function based mixin is a container that hosts at least one behavior or a set of many behaviors. Secondly a function based mixin has to be applied to a type, thus creating a closure over this type that now owns this behavior and also provides the context within it's behavior will be invoked.

Summary:

... Does this mean apply and call actually result in a sort of application mixin ? ...

Yes, apply and call are the methods for applying a function based mixin to a type/object ...

... If so, doesn't that make the 2nd example a sort of mix-in as well ?

... but no, the second example (Kyle's so called 'implicit mixin') is as far away from a mixin as can be. The entire part of providing and applying additional behavior via an own abstraction is missing. This example just demonstrates how a method is going to be reused within the context of another object.

Function based mixins need to be delegated via apply / call in order to take effect at an object. But apply ing/ call ing a method directly does make this delegation not just yet a mixin.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM