Skip to content

Instantly share code, notes, and snippets.

@stepheneb
Last active January 8, 2020 05:34
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stepheneb/4684394 to your computer and use it in GitHub Desktop.
Save stepheneb/4684394 to your computer and use it in GitHub Desktop.
JavaScript mixin patterns

JavaScript mixin patterns

Four different ways JavaScript variables and closures can be used to implement different mix-in patterns.

Each implementation creates a mixin that creates property getter functions for model objects.

By model object I mean a function that takes properties (keys and values)( and returns an object that:

  1. Doesn't allow direct access to the properties in the new model object.
  2. The properties are only available via getter and setter functions on the model object that mediate access.
    m1 = new Model1({ a:1, b:3, c:3 });
    a = m1.get('a'));
    => 1

source: gist.github.com/4684394

demo: bl.ocks.org/stepheneb/4684394

20130131

<html>
<head>
<title>JavaScript Mixin Experiments</title>
<style type="text/css">
body { font: 16px sans-serif; }
p { font-size: 1.5em;
margin-left: 1.0em }
rect { fill: #fff; }
ul {
list-style-type: none;
margin: 0.5em 0em 0.5em 0em;
width: 100%; }
ul li {
margin: 0em;
padding: 0em 1em; }
</style>
</head>
<body>
<p>JavaScript Mixin Experiments</p>
<ul id="results"></ul>
<script>
resultsList = document.getElementById('results');
</script>
<script src='mixin.js' type='text/javascript'></script>
</body>
</html>
function log(result) {
var li;
if (typeof(resultsList) === "undefined") {
console.log(result);
} else {
li = document.createElement("li");
li.textContent = result;
resultsList.appendChild(li);
}
}
log("Different ways of using a mixin to create property getter functions for model objects ...");
log("The model object properties are encapsulated and only accessible via the getter functions.");
log("See mixin.js for implementation details.");
// ************************************************
//
// Standard getter method accessing variables in closure (no use of mixin).
//
function Model1(options) {
var myOptions = options;
this.get = function(prop) {
return myOptions[prop];
};
}
log("----");
log("Standard getter method accessing variables in closure (no use of mixin).");
log("m1 = new Model1({ a:1, b:3, c:3 });");
log("a = m1.get('a'));");
m1 = new Model1({ a:1, b:3, c:3 });
log("a = " + m1.get("a"));
// ************************************************
//
// Standard getter mixin accessing variables saved as properties in Model object
//
// See: section 4. Adding Caching
// http://javascriptweblog.wordpress.com/2011/05/31/a-fresh-look-at-javascript-mixins/
//
//
var addMixin2 = (function() {
function get(prop) {
return this.options[prop];
}
return function() {
this.get = get;
return this;
};
})();
function Model2(options) {
addMixin2.call(Model2.prototype);
this.options = options;
}
log("----");
log("Standard getter mixin addMixin2() accessing variables saved as properties in Model object.");
log("m2 = new Model2({ a:4, b:5, c:6 });");
log("a = m2.get('a'));");
m2 = new Model2({ a:4, b:5, c:6 });
log("=> " + m2.get("a"));
// ************************************************
//
// Getter mixin accessing closure variables with the help of function in Model object
//
var addMixin3 = (function() {
function get(prop) {
return this.getPrivate()[prop];
}
return function() {
this.get = get;
return this;
};
})();
function Model3(options) {
addMixin3.call(Model3.prototype);
var myOptions = options;
this.getPrivate = function() {
return myOptions;
};
}
log("----");
log("Getter mixin addMixin3() accessing closure variables with the help of function in Model object.");
log("m3 = new Model3({ a:7, b:8, c:9 });");
log("a = m3.get('a'));");
m3 = new Model3({ a:7, b:8, c:9 });
log("=> " + m3.get("a"));
var Model3a = function(options) {
addMixin3.call(Model3a.prototype);
var myOptions = options;
this.getPrivate = function() {
return myOptions;
};
};
log("Make a second model object with different initial variables.");
log("m3a = new Model3({ a:1, b:3, c:3 });");
log("a = m3a.get('a'));");
m3a = new Model3({ a:1, b:3, c:3 });
log("=> " + m3a.get("a"));
// ************************************************
//
// Mixin accessing it's own closure variables
//
// 'count' is scoped to the function mixed in to the model object
// 'total' is scoped to the mixin itself
//
var addMixin4 = (function() {
var total = 0;
function counter(num) {
var count = num;
return function() {
total++;
return count++;
};
}
function totalCount() {
return function() {
return total;
};
}
return function(num) {
this.counter = counter(num);
this.totalCount = totalCount();
return this;
};
})();
function Model4(num) {
addMixin4.call(Model4.prototype, num);
}
var Model4a = function(num) {
addMixin4.call(Model4a.prototype, num);
};
log("----");
log("Mixin accessing it's own closure variables that keeps track of both how many ");
log("times the mixin is called from each created function and also the total number");
log("times the mixin has been called from all functions that use it.");
log("In this case two different Model creation functions are created, Model4()");
log("and Model4A() and each of them are extended with the same mixin: addMixin4().");
log("m4 = new Model4(23);");
log("m4a = new Model4a(17);");
m4 = new Model4(23);
m4a = new Model4a(17);
log("function call counter tests ...");
log("m4 function counter starting at 23 ...");
log("m4.counter(); => " + m4.counter());
log("m4.counter(); => " + m4.counter());
log("m4.counter(); => " + m4.counter());
log("m4.counter(); => " + m4.counter());
log("total count by all uses of mixin: " + m4.totalCount());
log("m4a counter starting at 17 ...");
log("m4a.counter(); => " + m4a.counter());
log("m4a.counter(); => " + m4a.counter());
log("m4a.counter(); => " + m4a.counter());
log("total count by all uses of mixin addMixin4(): " + m4.totalCount());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment