如何使用可调用方法创建对象,但我也可以像函数一样调用它

时间:2017-04-25 13:36:49

标签: javascript design-patterns

我经常使用d3.js并且最近一直在考虑他们的linearScale函数。

它允许你这样做:

var x = d3.scaleLinear()
    .domain([10, 130])
    .range([0, 960]);

x(20); // 80
x(50); // 320

据我所知:

  • x保存d3.scaleLinear()
  • 的返回值
  • x是类似对象的,因为我们可以调用方法'range'和'domain'
  • x也是类似函数的,因为我们可以将它称为'x(20)'并获得返回值

我不需要知道有关d3如何实现这一点的任何具体内容,d3只是我想到的第一个允许我这样做的API示例。

我有兴趣在javascript中实现类似的东西,并希望以最简单的形式知道必要的设计模式。

我曾尝试查看d3的源代码,但是有很多额外的功能,而且它分成了多个文件 - 这让我很难找到我正在寻找的东西,特别是当我不喜欢的时候我知道那是什么。任何帮助将不胜感激。

2 个答案:

答案 0 :(得分:2)

首先要注意的是,JavaScript函数只是可以附加属性的对象,这些属性可以包含其他函数。

基本上scaleLinear()返回一个函数,该函数又用作API对象。

附加到API对象/函数的函数用于获取或设置封装闭包创建的内部状态。

附加到API对象/函数的函数通常返回API实例以允许链接调用。

希望下面的例子能说明如何开发这样的东西。

function Greet() {
  var person = '';
  var message = 'Hello';

  var api = function () {
    console.log(message + ' ' + person);
    return api;
  };

  api.person = function (value) {
    // if nothing is passed into function act as getter
    if (!arguments.length) return person; 
    // otherwise set the value
    person = value;
    // return the api function for more chaining or calling
    return api;
  };

  api.message = function (value) {
    if (!arguments.length) return message; 
    message = value;
    return api;
  };

  return api;
}
    
var greeter = Greet().person('Daniel');

greeter();  // 'Hello Daniel'

greeter.message('Whaddup').person('Martha')(); // 'Whaddup Martha'

我只记得Mike Bostock(d3的创建者)撰写了一篇博客文章,他详细介绍了这种模式: https://bost.ocks.org/mike/chart/

答案 1 :(得分:0)

我发现this tutorial非常实用

我有这种方法的样板

function getChart(params) {
    // exposed variables
    var attrs = {
        svgWidth: 400,
        svgHeight: 400,
        marginTop: 5,
        marginBottom: 5,
        marginRight: 5,
        marginLeft: 5,
        data: null
    };


    /*############### IF EXISTS OVERWRITE ATTRIBUTES FROM PASSED PARAM  #######  */

    var attrKeys = Object.keys(attrs);
    attrKeys.forEach(function (key) {
        if (params && params[key]) {
            attrs[key] = params[key];
        }
    })


    //innerFunctions
    var updateData;


    //main chart object
    var chart = function (selection) {
        selection.each(function () {

            //calculated properties
            var calc = {}

            calc.chartLeftMargin = attrs.marginLeft;
            calc.chartTopMargin = attrs.marginTop;

            calc.chartWidth = attrs.svgWidth - attrs.marginRight - calc.chartLeftMargin;
            calc.chartHeight = attrs.svgHeight - attrs.marginBottom - calc.chartTopMargin;

            //drawing
            var svg = d3.select(this)
                .append('svg')
                .attr('width', attrs.svgWidth)
                .attr('height', attrs.svgHeight)
            // .attr("viewBox", "0 0 " + attrs.svgWidth + " " + attrs.svgHeight)
            // .attr("preserveAspectRatio", "xMidYMid meet")


            var chart = svg.append('g')
                .attr('width', calc.chartWidth)
                .attr('height', calc.chartHeight)
                .attr('transform', 'translate(' + (calc.chartLeftMargin) + ',' + calc.chartTopMargin + ')')





            // smoothly handle data updating
            updateData = function () {


            }


        });
    }


    //exposed variable funcs
    chart.data = function (value) {
        if (!arguments.length) return attrs.data;
        attrs.data = value;
        if (typeof updateData === 'function') {
            updateData();
        }
        return chart;
    }

    chart.width = function (value) {
        if (!arguments.length) return attrs.svgWidth;
        attrs.svgWidth = value;
        return chart;
    }

    chart.height = function (value) {
        if (!arguments.length) return attrs.svgHeight;
        attrs.svgHeight = value;
        return chart;
    }


    return chart;
}

然后你可以像这样调用图表