问题
While learning D3.js, I have come across the blog post explaining the main design-pattern behind it's reusable units of code. I have reproduced the relevant bit of code below. The way the pattern is presented below is exactly the way it is used in D3 codebase and plug-ins (example).
My one issue with this code is that it has so much copy-paste for the properties. JavaScript being a functional language, I thought I'd be able to re-factor the boilerplate code out, but I can't think of a way to do it. The arguments
and value
parameters are easy to pass to a common function, but I can't find a way to preserve a reference to the width
and height
properties.
function chart() {
var width = 720, // default width
height = 80; // default height
function my() {
// generate chart here, using `width` and `height`
}
my.width = function(value) {
if (!arguments.length) return width;
width = value;
return my;
};
my.height = function(value) {
if (!arguments.length) return height;
height = value;
return my;
};
return my;
}
The fact that this is how it's done in the actual D3 codebase, makes me wonder if re-factoring is possible, but I'm hoping that it's simply a matter of this not being a high priority issue (and new contributors are doing it this way, because that's how it was done before).
What I'm looking for is basically replacing every accessor's body with:
my.height = function(value) {
return getSet(arguments, value, whatever);
};
There is still some boilerplate for the call, but at least the logic is centralized and can be updated in only one place, if needed.
回答1:
If you define getSet
in the scope of chart
, it will have access to the closed over variables too. The problem is, you can't access those variables by name string (unless you use some kind of eval
).
You could avoid that by wrapping all your private variables in an object untested:
function chart() {
var props = {
width: 720, // default width
height: 80 // default height
}
function my() {
// generate chart here, using `width` and `height`
}
my.height = function(value) {
// Call getSet with current my instance as this,
// 'height' as the first argument, then value
return getSet.apply(this, arguments.slice().unshift('height'));
};
// Works just like your current accessors, on the props object
function getSet(property, value) {
if (arguments.length > 1) return props[property];
props[property] = value;
return this;
}
return my;
}
The problem is this is not much shorter than writing several similar accessors for each property. Your current accessors make the private variables virtually public, so why not just ditch them and use public variables instead?
回答2:
An alternative solution to that already provide is to define a 'property' function which returns a function e.g.
function property (v) {
return function (_) {
if(!arguments.length)
return v;
v = _;
return this;
};
}
So you can say:
function chart() {
chart.width = property.call(chart, 500);
chart.height = property.call(chart, 500);
chart.render = function() {
//render logic goes here
};
return chart;
}
I cannot take full credit for this - I think the source was actually Mike Bostock (but I cannot find a link to the original post)
I use this 'property' function in many of my modules - it saves a lot of annoying typing. You can easily augment it to emit events when the incoming value changes.
来源:https://stackoverflow.com/questions/14940040/looking-for-a-way-to-refactor-d3-js-style-method-chaining-pattern