简体   繁体   中英

Is this kind of chaining possible in JavaScript?

I'm working on a file manager framework similar to elFinder . My current code works fine but now I want to make it look better and add chaining (I'm not sure if it's chaining or decorator pattern).

Here is a sample of what I want to do:

function UI() {}

UI.prototype.folders = function(){
    return [];
}

UI.prototype.folders.prototype.getSelectedFolder = function(){
   return {};
}

Calling UI.folders() should return an array of folder objects. So if you call UI.folders() you would get something similar to this:

[
    Object { name="folder1", selected=false }, 
    Object { name="folder2", selected=false }, 
    Object { name="folder3", selected=true }
]

And calling UI.folders().getSelectedFolder() would filter the results from UI.folders() and will return:

Object { name="folder3", selected=true }

Is this possible? Is it right to say "chaining" in this case or it's "decorative pattern"?
If it's not - is there another more appropriate way to do it?

Any help wold be really appreciated!

The code in your question isn't reflective of a proper implementation, but to answer your direct questions, yes, this...

UI.folders().getSelectedFolder()

...would be an example of method chaining.


A decorator pattern is different. If you have a set of methods, and each one should always first invoke some common function, you can create a decorator that will return a function that first calls the common one, then the actual one...

function foo() {
    console.log('I\'m foo, and I\'m first, and I was given these args:', arguments);
}

function decorateWithFoo(decorated) {
    return function () {
        foo.apply(this, arguments);
        decorated.apply(this, arguments);
    };
}

So you can use decorateWithFoo to create a function that always invokes foo first...

  // create and decorate bar()
var bar = function(a,b) {
    console.log('I\'m bar, and I was called after "foo", and was given args:', a, b);
};
bar = decorateWithFoo(bar);

bar(123, 456); // this will first call `foo()`, then (the original) `bar()`.

  // create and decorate baz()
var baz = function(a,b) {
    console.log('I\'m baz, and I was called after "foo", and was given args:', a, b);
};
baz = decorateWithFoo(baz);

baz(123, 456); // this will first call `foo()`, then (the original) `baz()`.

Some languages have built in syntax for creating decorators. JavaScript currently does not.


If you find yourself using decorators in different ways, you could create another function that sets up the initial decorator function...

function generateDecorator(decorator) {
    return function (decorated) {
        return function () {
            decorator.apply(this, arguments);
            decorated.apply(this, arguments);
        };
    };
}

So our original decoreateWithFoo could have been set up like this...

function foo() {
    console.log('I\'m foo, and I\'m first, and I was given these args:', arguments);
}

var decorateWithFoo = generateDecorator(foo);

To make this work properly, you need to make your folders method be a function that returns an object that inherits from an array.:

UI.prototype.folders = function(){
    // must return an object that inherits from an array
    // that has the additional methods on it you want like getSelectedFolder()
}

The are a few different ways to solve this. The primary goal is that when you call a function you get an object/function back that is the same type of object with different properties. I'm not a fan of the prototype usage so I would do it like this (this is one way to solve it):

var FolderList = function ()
{
    var _folders = [];
    folders.pop({ name: "folder1", selected: false });
    folders.pop({ name: "folder2", selected: true });
    folders.pop({ name: "folder3", selected: false });

    // prevent other programers from changing _folders
    // which would break this, so just use a function
    this.folders = function ()
    {
        return _folders;
    }

    this.selectedFolders = function ()
    {
        var tmpFolders = [];
        for (var folderIndex = 0; 
             folderIndex < this._folders.length; 
             folderIndex++)
        {
            if (this._folders[folderIndex].selected)
            {
                tmpFolders.pop(_folders[folderIndex]);
            }
        }
        _folders = tmpFolders;
        return this;
    }

    this.addFolder = function (folder)
    {
        _folders.pop(folder);
        return this;
    }
};

var folderList = new FolderList();
folderList.selectedFolders()
          .addFolder({ name: "folder1", selected: false })
          .addFolder({ name: "folder3", selected: true })
          .selectedFolders();

// array of 2 objects, folder2 and folder3
var arrayOfSelectedFolder = folderList.folders();  

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