代码之家  ›  专栏  ›  技术社区  ›  NoobishPro

Javascript:链接jQuery等元素

  •  2
  • NoobishPro  · 技术社区  · 7 年前

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

    现在,关于我取得的进展有两个问题(请注意,我的代码示例被最小化了,所以请不要对错误处理发表评论。)

    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 同样,它将取代 elements 结果为的属性 first() ,这意味着我“失去”了旧的价值观。我不想失去他们,我只想 第一个() 特定功能的功能。那我要 $测试 再次返回所有元素。 此外,回顾 第一个() 现在将结束 undefined ,因为其中只剩下1个元素 this 因为它已经从对象中删除了旧元素。

    另一次尝试

    现在,我还试图通过返回第一个子对象而不是整个类对象来扭转局面;

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

    但是,我会的 $test.first().hasClass(className); //Returns ERROR, method hasClass undefined

    这是因为 .hasClass 存在于原件上 ,不再返回,因为我现在正在返回元素。

    我试图从jQuery的库中获取一些东西,但这让我更加困惑。。。

    我在google上搜索过这个主题,但我找到的所有“链接方法”解决方案似乎都覆盖了对象的原始值,这不是我想要的。另一个解决方案实际上需要我一遍又一遍地重新启动对象,这对我来说似乎不是很有效。。。非常感谢您的帮助。我想我做这件事的方式是完全错误的。

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

    6 回复  |  直到 7 年前
        1
  •  2
  •   Barmar 0___________    7 年前

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

    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])
    };
    
        2
  •  2
  •   Rick Hitchcock    7 年前

    this 在外部函数中,指的是窗口/全局对象。

    相反,请返回 ret 变量本身。

    内部的 函数(成为对象的方法), 按你期望的方式行事。

    这里有一个替代解决方案,它允许链接,即使在您调用 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>
        3
  •  1
  •   Angel Politis    7 年前

    以下是我的做法:

    1. 创建构造函数,比如 Search ,任务是根据输入查找元素。使用构造函数是正确的面向对象编程,您还可以在原型中定义一次方法,并且所有实例都可以访问这些方法。
    2. 确保上下文( this )是一个类似于数组的对象,具有数字属性和长度,因此可以用传统的方式轻松迭代每个匹配的元素 (使用 for 循环, [].forEach etc) .
    3. 创建一个函数,比如 getElement ,它将使用构造函数并返回结果,而不必使用 new 关键字一直存在。由于该函数返回构造函数的一个实例,因此可以像通常那样链接所需的方法。
    4. 方法 first 使用构造函数创建新实例,而不是修改原始实例,因为它的作用是返回第一个元素,而不是删除除第一个元素以外的所有元素。
    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>
        4
  •  1
  •   Angel Politis    7 年前

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

    我举了一个小例子,使用一些ES6使一些事情更容易处理,它还有一个 $ 启动正在进行的选择。

    您会注意到,首先,所做的任何选择都只是调用本机 document.querySelectorAll 但返回一个新 Node 班二者都 first last 方法还返回这些元素。

    最后一点 hasClass 应该处理当前节点选择中的所有元素,因此它将迭代当前节点,并检查其中的所有类,这将返回一个简单的bool,因此您无法继续在那里链接方法。

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

    • 回来 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>
        5
  •  -1
  •   Angel Politis    7 年前

    您可以更新 getElement 因此,当您向其发送元素时,它会再次返回。

    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>
        6
  •  -2
  •   guest271314    7 年前

    您可以使用 .querySelectorAll() 、扩展元素和 Array.prototype.find() ,返回数组中的第一个匹配项,或 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>