【问题标题】:Trying to implement something similar to jQuery尝试实现类似于 jQuery 的东西
【发布时间】:2017-06-08 14:44:52
【问题描述】:

我正在尝试实现类似于 jQuery 的东西。
基本上,每当我对 DOM 元素做某事时,我都想使用包装对象的方法,而不是直接操作 DOM。

这是我目前所拥有的

HTML:

<a href='#!'></a>

JS:

var dd = function(selector)
{
    return new dd.prototype.constructor(selector);
}
dd.prototype =
{
    constructor: function(selector)
    {
        var nodes = document.querySelectorAll(selector);

        for(var i = 0; i < nodes.length; i++)
            this[i] = nodes[i];

        this.length = nodes.length;
        return Array.prototype.slice.call(this);
    },
    addClass: function(cl)
    {
        alert('a');
    }
};

var a = dd('[href="#!"]');
a.addClass('asd');

问题:

  • 我在浏览器控制台中收到错误消息,提示 a.addClass is not a function
  • dd('a') instanceof dd === false$('a') instanceof $ === true,所以我知道我的逻辑在这里有些错误。

很遗憾,我不太明白自己做错了什么。

【问题讨论】:

  • 考虑使用 es6 类。它比使用prototype 简单得多。你的构造函数看起来很可疑,我很确定你不应该返回任何东西。 a = dd() 也应该是 a = new dd()
  • @Halcyon 我会使用 es6 类,但浏览器对它们的支持还不够好。构造函数应该返回一个数组,该数组包含数字索引上的 DOM 元素,并有一个 proto 包含我想使用的方法(基于 jQ 的做法)。目前我的数组看起来不错,但 proto 不包含 addClass。第一个函数定义返回一个新对象(或者至少应该返回),这样我就不必每次在对象中包装一个元素时都使用 new 。
  • 如果你想创建类似的东西,看看 jQuery 的源代码 - 你可以在那里学到很多东西。
  • @wostex 干杯,我一直在这样做,但我想目前对我来说有点复杂。

标签: javascript jquery html


【解决方案1】:

虽然重新发明轮子不是一个好主意, 以下内容应该对您有所帮助。

考虑到jQuery 支持自定义构建, 所以,首先,看看这里https://github.com/jquery/jquery#how-to-build-your-own-jquery

var dd = (function() {
  function dd(selector) {
    // class call check
    if(!(this instanceof dd)) {
      return new dd(selector);
    }

    Array
      .from(document.querySelectorAll(selector), (el, i) => {
        this[i] = el;
      })
    ;
    
    Object
      .defineProperty(this, 'length', {
        get: () => Object.keys(this).length
      })
    ;
  }
  
  dd.prototype.forEach = function(cb) {
    Object
      .keys(this)
      .forEach(i => {
        cb(this[i], i)
      })
    ;
    
    return this;
  }
  
  dd.prototype.addClass = function() {
    this.forEach(el => el.classList.add(...Array.from(arguments)));

    
    return this;
  }
  
  dd.prototype.removeClass = function() {
    this.forEach(el => el.classList.remove(...Array.from(arguments)));
    
    return this;
  }

  return dd;
})();

var odd = dd('strong');
var even = dd('span');

window.setTimeout(() => odd.addClass('foo'), 2000);
window.setTimeout(() => even.addClass('baz'), 3000);

console.log('odd', odd.length);
console.log('even', even.length);
.cntr strong,
.cntr span {
  display: inline-flex;
  width: 20px;
  height: 20px;
  transition: 250ms all linear;
  margin: 2px;
  border: 1px solid cyan;
  background: lightseagreen;
}

.cntr .foo { background: yellow; }
.cntr .baz { background: orange; }
<section class="cntr">
	<strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span>
</section>

【讨论】:

    【解决方案2】:

    不确定您要达到什么目标,但让我们看看现在出了什么问题。

    1) 你在 constructor 上调用 new 然后返回 return Array.prototype.slice.call(this); 所以你在 a 中得到的是一个数组。显然数组不知道你的函数addClass
    2) 如果您注释掉数组切片部分,您将返回一个对象,其中this 设置为constructor,但addClass 没有添加到constructorprototype 对象。为此,您需要添加addClass,如下所示:

    dd.prototype.constructor.prototype.addClass = function (cl) {
                alert('a');
            }
    

    通过这些更改,您将收到警报,并希望可以进一步进行。 所以总的来说这些变化:

    var dd = function(selector) {
      return new dd.prototype.constructor(selector);
    }
    dd.prototype = {
      constructor: function(selector) {
        var nodes = document.querySelectorAll(selector);
    
        for (var i = 0; i < nodes.length; i++)
          this[i] = nodes[i];
    
        this.length = nodes.length;
        //return Array.prototype.slice.call(this);
      },
      //addClass: function (cl) {
      //    alert('a');
      //}
    };
    dd.prototype.constructor.prototype.addClass = function(cl) {
      alert('a');
    }
    var a = dd('[href="#!"]');
    a.addClass('asd');
    &lt;a href='#!'&gt;&lt;/a&gt;

    【讨论】:

    • 注释掉 return 使我无法通过 dd('a')[0] 直接引用第一个匹配的 DOM 元素。这是我发现在 jQ 中非常有用的功能,所以我会尽量保留它。 (据我所知)return 与 jQ 的工作方式类似,但 jQ 也以某种方式将这些自定义方法添加到数组中。 j11y.io/jquery/#v=git&fn=init 有 jQ 构造方法的源代码,但遗憾的是我不太明白他们是如何做到的。
    • @user6003859 请注意,您可以在此实现中使用a[0] 访问节点a。您还可以在 var a 中使用 length 属性。你的 for 循环使它表现得像数组。
    • 哦,该死。我的错。我想这可以解决我现在的问题。谢谢。
    • @user6003859 你没有选择我的答案,对吧?我可以知道原因吗?
    • 是的,再次抱歉。我已经在提交的答案中解释了为什么这样做。
    【解决方案3】:

    您正在混合类和对象工厂。你不能同时拥有它。

    任你选择(jQuery 风格):

    function dd(selector) {
        return {
            addClass: function () {/**/}
        };
    }
    
    var a = dd('[href="#!"]');
    a.addClass('asd');
    

    或者你这样做:

    function dd(selector) {
        this.selector = selector;
    }
    dd.prototype.addClass = function () {/**/};
    
    var a = new dd('[href="#!"]');
    a.addClass('asd');
    

    请注意,这不会像 jQuery 那样自动生成 a instanceof Array。你需要一些黑魔法才能做到这一点。

    【讨论】:

    • function dd(selector) { if(!(this instanceof dd)) { return new dd(selector); } 这是一个混合类和工厂的例子,javascript 支持这种行为。
    猜你喜欢
    • 2011-01-14
    • 2019-09-09
    • 2013-05-08
    • 1970-01-01
    • 2015-10-20
    • 2020-09-28
    • 1970-01-01
    • 1970-01-01
    • 2018-12-04
    相关资源
    最近更新 更多