简体   繁体   English

Javascript:对象返回自己a.k.a. chaining

[英]Javascript: object return itself a.k.a. chaining

I am trying to build my own little jquery-like library but I'm having a really rough time with creating this chaining pattern. 我正在尝试构建自己的小jquery类库,但是我正在创建这个链接模式的时间非常艰难。 Basically I have one class with a bunch of methods that make it easier to manipulate the document. 基本上我有一个类有一堆方法,可以更容易地操作文档。 Here's an example 这是一个例子

function MF(selector){
    var DO; // Stands for DocumentObject
    this.select = function(selector){
        return document.getElementById(selector);
    }
    if(typeof selector === 'string'){
        DO = this.select(selector);
    }else if(selector instanceof HTMLElement){
        DO = selector;
    }

    this.children = function children(selector){
        return DO.getElementsByClassName(selector);
    }
    return {
        MF: ???
    }
}(null);

I might be wrong in my reflections but what I've come to figure out is that in order to have additional methods for a document object ( html element ) I either need to extend the HTMLElement prototype or pass the element along with my class. 我的反思可能有问题,但我想弄清楚的是,为了获得文档对象(html元素)的其他方法,我需要扩展HTMLElement原型或者将元素与我的类一起传递。 I've chosen the second option. 我选择了第二种选择。 I just can't figure out what to return in my class so that I can have chaining going on. 我只是无法弄清楚我的班级要回归什么,以便我可以继续进行链接。 What I simply aim at, for the sake of this example, is to be able to write the following line of code: 为了这个例子,我简单的目标是能够编写以下代码行:

MF('someDiv').children('someClass');

In a desperate attempt I tried returning a new instance of MF which should not have instances by default and led myself to an infinite loop. 在绝望的尝试中,我尝试返回一个新的MF实例,默认情况下不应该有实例,并导致自己进入无限循环。 I really cannot figure what I'm supposed to return there. 我真的无法想象我应该回到那里。 Any help is greatly appreciated! 任何帮助是极大的赞赏!

return this; will allow access to methods of the Constructor. 将允许访问构造函数的方法。 Do it at the very bottom of the Constructor and the very bottom inside every method that belongs to it, if the method doesn't need to return another value. 如果该方法不需要返回另一个值,请在构造函数的最底部以及属于它的每个方法的最底部执行此操作。

function MF(selector){
  var doc = document;
  this.select = function(selector){
    return doc.getElementById(selector);
  }
  // there are problems with some of your code
  this.someMethod = function(){
    /* do stuff - If you want to access an Element then
      var thisIsNowGlobal = this.select('someId');
      thisIsNowGlobal.innerHTML = 'someText';
      Note, the keyword this is global not var
      If you wrote this.select('someId').innerHTML the property would not exist

      When a property of an Object is assigned to a variable or argument
      the this value changes to the global context.
   */
    return this;
  }
  return this;
}

It looks like you also tried to use the module pattern, and haven't taken advantage of prototypes.. 看起来你也试图使用模块模式,并没有利用原型..

When you want to chain something, you either need to return itself ( this ) within the chainable methods, or return a new instance of your Object . 当你想链接某些东西时,你需要在可链接的方法中返回它自己( this ),或者返回你的Objectnew实例。 In the example below I achieve chaining by using new instances (well the children looked like it needed to be a list so I did an Array of them). 在下面的例子中,我通过使用新实例来实现链接(好像孩子们看起来需要成为一个列表,所以我做了一个数组 )。

var MF = (function () { // module pattern start
    function MF(selector) {
        if (!(this instanceof MF)) return new MF(selector); // always construct
        this.node = null; // expose your DO
        if (typeof selector === 'string') {
            this.node = document.getElementById(selector);
        } else if (selector instanceof HTMLElement) {
            this.node = selector;
        } else {
            throw new TypeError('Illegal invocation');
        }
    }
    MF.prototype = {}; // set up any inheritance
    MF.prototype.select = function (selector) {
        return new MF(document.getElementById(selector)); // returns new instance
    };
    MF.prototype.children = function (selector) {
        var MFs = [],
            nodes = this.node.getElementsByClassName(selector),
            i;
        for (i = 0; i < nodes.length; ++i) {
            MFs[i] = new MF(nodes[i]);
        }
        return MFs; // array of items of new instances
    };
    return MF; // pass refence out
}()); // module pattern end

Then, for example, you can chain like.. 然后,例如,您可以链接...

MF(document.body).children('answer')[0].children('post-text')[0].node;

You can do this pretty easily. 你可以很容易地做到这一点。 Bear in mind that when you return this then if this has methods defined on it, then you can call them sequentially. 请记住,当你return this它时,如果this有定义的方法,那么你可以按顺序调用它们。

var MyUtilThing = function(){};
MyUtilThing.prototype.doStuff = function doStuff (){ // give it a name, helps in debugging
  // do your thing
  console.log('doing stuff');
  return this; // this is your instance of MyUtilThing
}

var thing = new MyUtilThing();
thing.doStuff().doStuff().doStuff(); // etc

One way around having to explicitly create instances is do this in your constructor. 必须明确创建实例的一种方法是在构造函数中执行此操作。

var MyUtilThing = function(selector){
  var F = function(){};
  F.prototype = MyUtilThing.prototype;
  var toReturn = new F();
  toReturn.initialize(selector);
  return toReturn;
};

MyUtilThing.prototype.initialize = function initialize(selector){
  this.selector = selector;
};

MyUtilThing.prototype.doStuff = function doStuff (){ // give it a name, helps in debugging
  // do your thing
  console.log('doing stuff to', this.selector);
  return this; // this is your instance created in the constructor (the blank function with the same prototype as MyUtilThing)
}

var thing = MyUtilThing('div'); // no use of new here!
thing.doStuff().doStuff().doStuff(); // etc

But, getting into some slightly heavy territory there. 但是,在那里进入一些稍微沉重的领土。 Best bet is just to try and understand exactly how this is used in JS, and you'll get a long way. 最好的办法就是尝试和了解究竟如何this是JS使用,你会得到一个很长的路要走。

Traditionally the way jQuery enables chaining is by creating a wrapper object for every type of return value. 传统上,jQuery启用链接的方式是为每种类型的返回值创建一个包装器对象。 For example in your case it pays to create your own wrapper for HTMLElement : 例如,在您的情况下,为HTMLElement创建自己的包装器是HTMLElement

function HTMLElementWrapper(element) {
    if (element instanceof HTMLElementWrapper) return element;
    this.element = element;
}

Now that you have an HTMLElementWrapper you can refactor your MF function as follows: 现在您有了一个HTMLElementWrapper您可以按如下方式重构您的MF功能:

function MF(selector) {
    return new HTMLElementWrapper(typeof selector === "string" ?
        document.getElementById(selector) : selector);
}

The MF function now returns an HTMLElementWrapper object which has two methods select and children : MF函数现在返回一个HTMLElementWrapper对象,它有两个方法selectchildren

HTMLElementWrapper.prototype.select = function (selector) {
    return new HTMLElementWrapper(this.element.getElementById(selector));
};

HTMLElementWrapper.prototype.children = function (selector) {
    return new NodeListWrapper(this.element.getElementsByClassName(selector));
};

Ofcourse for the children function to work you'll need to create a NodeListWrapper constructor: Ofcourse为children功能工作,你需要创建一个NodeListWrapper构造函数:

function NodeListWrapper(list) {
    if (list instanceof NodeListWrapper) return list;
    this.list = list;
}

Now you can chain methods as follows: 现在您可以按如下方式链接方法:

MF("someDiv").select("something").children("someClass");

To chain methods after .children("someClass") you need to add those methods to NodeListWrapper.prototype . 要在.children("someClass")之后链接方法,您需要将这些方法添加到NodeListWrapper.prototype

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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