Javascript:链接 jQuery 等元素

Javascript: Chaining on elements like jQuery

我正在尝试在一定程度上复制 jQuery 的元素操作。现在我发现 .first() 选择器非常有用。我希望能够像这样链接函数;
getElement(selector).first().hasClass(className);

现在,我的进展有 2 个问题(请注意我的代码示例已最小化,所以请不要对 error-handling 发表评论。)

var getElement = function(selector, parent) {
  ret           = document.querySelectorAll(selector);
  this.element  = ret;
  this.hasClass = function(className) {
    className.replace('.', '');
    if(this.multiple())
    {
      console.log('Cannot use hasClass function on multiple elements');
      return false;
    }
  };

  this.first = function() {
    this.element = this.element[0];
    return this;
  };
  return this;
};

我目前的问题

如果我调用我的函数;

var $test = getElement('.something'); //result: nodelist with .something divs

如果我调用结果中的第一个元素;

$test.first(); //Result: First div, perfect!

但是,现在如果我再次调用 $test,它将用 first() 的结果替换 elements 属性,这意味着我有 "lost"我的旧价值观。我不想丢失它们,我只想要 first() 函数来实现该特定功能。然后我想再次 $test 到 return 所有元素。 此外,调用 first() 现在将以 undefined 结束,因为 this 中只剩下 1 个元素,因为它已从 object.[=29 中删除了旧元素=]

再次尝试

现在,我还尝试通过 returning first-child 而不是整个 class object;[=29= 来稍微改变一下]

this.first = function() {
  return this.element[0];
};

不过,我会 $test.first().hasClass(className); //Returns ERROR, method hasClass undefined

这是因为 .hasClass 存在于原始 this 上,它不再 returned,因为我现在 returning 元素。

我试图从 jQuery 的图书馆中得到一些东西,尽管那让我更加困惑...

我用谷歌搜索了这个主题,但是我找到的所有 'chaining methods' 解决方案似乎都覆盖了 object 的原始值,这不是我想要的即将发生。另一种解决方案实际上要求我一遍又一遍地 re-initiate object,这对我来说似乎效率不高......感谢任何帮助。我假设我正在以完全错误的方式解决这个问题。

-- 如果您能帮助我,请解释您的解决方案为何有效。我真的觉得如果我理解了这一点,我对 javascript 的理解可以进一步扩展。我只需要解决这个结构性(?)问题。

您可以更新 getElement,以便在您向它发送元素时它 returns 再次返回。

var getElement = function(selector, parent) {
  var ret = null
  if (typeof selector === "string") {
    ret = document.querySelectorAll(selector);
  } else {
    ret = selector
  }
  this.element = ret;
  this.hasClass = function(className) {
    className.replace('.', '');
    if (this.multiple()) {
      console.log('Cannot use hasClass function on multiple elements');
      return false;
    }
  };

  this.first = function() {
    this.element = getElement(this.element[0]);
    return this;
  };
  return this;
};

var test = getElement(".foo");
console.log(test.first())
console.log(test.first().hasClass)
<div class="foo">1</div>
<div class="foo">2</div>
<div class="foo">3</div>
<div class="foo">4</div>

您可以使用 .querySelectorAll()、展开元素和 Array.prototype.find(),其中 returns 数组中的第一个匹配项或 undefined

const getElement = (selector = "", {prop = "", value = "", first = false} = {}) => {
    const el = [...document.querySelectorAll(selector)];
    if (first) return el.find(({[prop]:match}) => match && match === value)
    else return el;
};

let first = getElement("span", {prop: "className", value: "abc", first: true});
    
console.log(first);

let last = getElement("span");

console.log(all);
<span class="abc">123</span>
<span class="abc">456</span>

外部函数中的

this 指的是 window / 全局对象。

相反,return ret 变量本身。

inner 函数(成为对象的方法)中,this 以您期望的方式运行。

这是一个替代解决方案,它允许链接,即使在您调用了 first 方法之后:

var getElement = function(selector, parent) {
  var ret = typeof selector == 'string' ? document.querySelectorAll(selector)
                                        : selector;

  ret.hasClass = function(className) {
    if(!this.classList) {
      console.log('Cannot use hasClass function on multiple elements');
      return false;
    } else {
      return this.classList.contains(className);
    }
  };

  ret.first = function() {
    return new getElement(this[0]);
  };
  
  return ret;
};

console.log(getElement('p').length);                   //2
console.log(getElement('p').first().innerHTML);        //abc
console.log(getElement('p').first().hasClass('test')); //true
console.log(getElement('p').first().hasClass('nope')); //fase
console.log(getElement('p').hasClass('test'));         //false (multiple elements)
<p class="test">
  abc
</p>

<p>
  def
</p>

first() 这样的方法不应该修改 this,它应该创建一个新对象和 return。您仅在修改元素的方法中使用 return this; 而不是 returning 从元素派生的信息。

this.first = function() {
    return new getElement(this.element[0]);
};

请注意,您必须使用 new getElement 来创建对象,而不仅仅是 getElement

这也需要对构造函数进行更改,因此它可以接受选择器字符串或元素:

var getElement = function(selector, parent) {
    var ret = typeof selector == "string" ? document.querySelectorAll(selector) : [selector];
    ...
}

您还应该考虑以适当的 OO 方式执行此操作,方法是将方法放在原型中,而不是在每个对象中定义它们。

var getElement = function(selector, parent) {
  var ret = typeof selector == "string" ? document.querySelectorAll(selector) : [selector];
  this.element = ret;
};

getElement.prototype.hasClass = function(className) {
  className.replace('.', '');
  if (this.multiple()) {
    console.log('Cannot use hasClass function on multiple elements');
    return false;
  }
};

getElement.prototype.first = function() {
  return new getElement(this.element[0])
};

我认为最简单的方法是 return 一个包含您选择的节点的新 class。那将是最简单的解决方案,因为您真的不想改变任何以前的选择器。

我做了一个小例子,使用一些 ES6 使一些事情更容易使用,它还有一个 $ 来启动正在做出的选择。

您会注意到,首先,所做的任何选择都只是调用本机 document.querySelectorAll,但 return 是一个新的 Node class。 firstlast 方法也 return 这些元素。

最后,hasClass 应该对当前节点选择中的所有元素起作用,因此它将迭代当前节点,并检查其中的所有 classes,这个 return这是一个简单的布尔值,因此您不能继续在那里链接方法。

您希望链接的任何方法都应该:

  • returnthis对象(当前节点)
  • return this 对象的一个​​元素作为新节点,因此可以在那里进行任何进一步的操作

const $ = (function(global) {
  class Node extends Array {
    constructor( ...nodes ) {
      super();
      nodes.forEach( (node, key) => {
        this[key] = node;
      });
      this.length = nodes.length;
    }
    first() {
      return new Node( this[0] );
    }
    last() {
      return new Node( this[this.length-1] );
    }
    hasClass( ...classes ) {
      const set = classes.reduce( (current, cls) => {
          current[cls] = true;
          return current;
        }, {} );
      for (let el of this) {
        for (let cls of el.classList) {
          if (set[cls]) {
            return true;
          }
        }
      }
      return false;
    }
  }
  global.$ = function( selector ) {
    return new Node( ...document.querySelectorAll( selector ) );
  };
  
  return global.$;
}(window));

let selector = $('.foo');
let first = selector.first(); // expect 1
console.log(first[0].innerHTML);
let last = selector.last();
console.log(last[0].innerHTML); // expect 4

console.log( first.hasClass('foo') ); // expect true
console.log( first.hasClass('bar') ); // expect false
console.log( selector.hasClass('foo') ); // expect true
console.log( selector.hasClass('bar') ); // expect true
<div class="foo">1</div>
<div class="foo">2</div>
<div class="foo bar">3</div>
<div class="foo">4</div>

以下是我的处理方法:

  1. 创建一个构造函数,比如 Search,负责根据输入查找元素。使用构造函数是正确的 OO 编程,您还可以在原型中定义一次方法,所有实例都可以访问它们。
  2. 确保上下文 (this) 是一个类似数组的对象,具有数字属性和长度,以便您可以轻松地以传统方式遍历每个匹配的元素 (使用 for 循环,[].forEach 等).
  3. 创建一个函数,比如 getElement,它将使用构造函数和 return 结果,而不必一直使用 new 关键字。由于函数 return 是我们的构造函数的一个实例,您可以像往常一样链接您想要的方法。
  4. 方法first使用构造函数创建新实例而不是修改原始实例,因为它的作用是return第一个元素,而不是删除第一个元素以外的所有内容。
  5. 每次你想出一个新的方法,你希望你的对象拥有,你可以简单地将它添加到构造函数的原型中。

片段:

;(function () {
  function Search (value) {
    var elements = [];

    /* Check whether the value is a string or an HTML element. */
    if (typeof value == "string") {
      /* Save the selector to the context and use it to get the elements. */
      this.selector = value;
      elements = document.querySelectorAll(value);
    }
    else if (value instanceof Element) elements.push(value);
      
    /* Give a length to the context. */
    this.length = elements.length;

    /* Iterate over every element and inject it to the context. */
    for (var i = 0, l = this.length; i < l; i++) this[i] = elements[i];
  }

  /* The method that returns the first element in a Search instance. */
  Object.defineProperty(Search.prototype, "first", {
    value: function () {
      return new Search(this[0]);
    }
  });
  
  /* The global function that uses the Search constructor to fetch the elements. */
  window.getElement = (value) => new Search(value);
  
  /* Create a reference to the prototype of the constructor for easy access. */
  window.getElement.fn = Search.prototype;
})();

/* Get all elements matching the class, the first one, and the first's plain form. */
console.log(getElement(".cls1"));
console.log(getElement(".cls1").first());
console.log(getElement(".cls1").first()[0]);
/* ----- CSS ----- */
.as-console-wrapper {
  max-height: 100%!important;
}
<!----- HTML ----->
<div id = "a1" class = "cls1"></div>
<div id = "a2" class = "cls1"></div>
<div id = "a3" class = "cls1"></div>


示例:

在这个例子中,我将一个名为 hasClass 的新方法添加到构造函数的原型中。

/* The method that returns whether the first element has a given class. */
Object.defineProperty(getElement.fn, "hasClass", {
  value: function (value) {
    return this[0].classList.contains(value);
  }
});

/* Check whether the first element has the 'cls2' class. */
console.log(getElement(".cls1").first().hasClass("cls2"));
<!----- HTML ----->
<script src="//pastebin.com/raw/e0TM5aYC"></script>
<div id = "a1" class = "cls1 cls2"></div>
<div id = "a2" class = "cls1"></div>
<div id = "a3" class = "cls1"></div>