【问题标题】:Creating a jQuery like "$" object创建一个类似“$”对象的jQuery
【发布时间】:2010-12-29 17:06:25
【问题描述】:

我的最终目标是能够做这样的事情:

MyVar(parameter).functionToPerform();

够傻了,即使在阅读了如何声明变量、查看 jQuery 代码之后……我仍然无法理解它。

这是我迄今为止尝试过的,但失败了:

var MyClass = function(context) {
this.print = function(){
    console.log("Printing");
}

this.move = function(){
    console.log(context);
}
};

var test = new MyClass();
test.print(); // Works
console.log('Moving: ' + test('azerty').move() ); // Type property error

【问题讨论】:

标签: javascript function


【解决方案1】:

在我写这篇文章时,Squeegy 的回答获得了最高票数:7。但这是错误的,因为__proto__ 是非标准的,并且不受 Internet Explorer(甚至版本 8)的支持。但是,去掉 __proto__ 并不能让它在 IE 6 中运行。

这(有些简化)是 jQuery 实际执行的方式(even try it on IE 6),它还包括静态方法和方法链接的示例。对于 jQuery 是如何做到这一点的所有细节,当然,你必须自己检查jQuery source code

var MyClass = function(context) {
    // Call the constructor
    return new MyClass.init(context);
};

// Static methods
MyClass.init = function(context) {
    // Save the context
    this.context = context;
};
MyClass.messageBox = function(str) {
    alert(str);
};


// Instance methods
MyClass.init.prototype.print = function() {
    return "Printing";
};
MyClass.init.prototype.move = function() {
    return this.context;
};

// Method chaining example
MyClass.init.prototype.flash = function() {
    document.body.style.backgroundColor = '#ffc';
    setInterval(function() {
        document.body.style.backgroundColor = '';
    }, 5000);
    return this;
};


$('#output').append('<li>print(): '+ MyClass().print() +'</li>');
$('#output').append('<li>flash().move():'+ MyClass('azerty').flash().move() +'</li>');
$('#output').append('<li>context: '+ MyClass('azerty').context +'</li>');
MyClass.messageBox('Hello, world!');

请注意,如果您需要“私有”数据,则必须将实例方法放入 MyClass.init(在该函数内声明一个变量)作为 this.print = function() { ... };,而不是使用 MyClass.init.prototype

【讨论】:

    【解决方案2】:

    jQuery() 既是具有全局方法的模块,又是构造函数。如果需要,它会自动调用构造函数。如果我们没有用new 关键字调用,那么this 将不会用MyClass 构造。我们可以检测到这一点并在构造函数模式下调用该函数。一旦我们这样做了,那么this 将成为MyClass 的一个实例,我们可以开始向它添加东西。

    var MyClass = function(context) {
        // if the function is called without being called as a constructor,
        // then call as a constructor for us.
        if (this.__proto__.constructor !== MyClass) {
            return new MyClass(context);
        }
    
        // Save the context
        this.context = context;
    
        // methods...
        this.print = function() {
            return "Printing";
        }
    
        this.move = function() {
            return this.context;
        }
    };
    
    $('#output').append('<li>print(): '+ MyClass().print() +'</li>');
    $('#output').append('<li>move():'+ MyClass('azerty').move() +'</li>');
    $('#output').append('<li>context: '+ MyClass('azerty').context +'</li>');
    

    http://jsfiddle.net/rvvBr/1/

    【讨论】:

    • 现在我明白了。它仍然需要实例化,但这可以从内部完成。很酷,谢谢!
    • 乐于助人!此外,我刚刚用一种更好的方法更新了我的答案,通过实际检查this 原型的构造函数来检测它是否被称为构造函数。应该更加健壮。
    • 尽管它按我想要的方式工作,但我刚刚意识到(因为它正在为每个 MyClass() 调用实例化)它对于我的目的来说相当慢。你生活和学习:P
    【解决方案3】:

    当你这样做时

    var test = new MyClass()
    

    您创建一个具有两个属性moveprint 的对象。由于new 语句,您的对象test 不再是一个函数。所以调用test()是错误的。

    【讨论】:

    • 如果您尝试实例化test 对象,则调用test().move() 是错误的,但是链接函数调用(jQuery 实现的关键)是非常可能的。
    【解决方案4】:

    每次在 jQuery 中调用 $ 时,它都会返回一个新的 jQuery.init 对象。 jQuery.init 对象具有随后被调用的函数。

    function test(type)
    {
      switch (type)
      {
        case 'azerty':
          return new type.a();
        case 'qwerty':
        default:
          return new type.b();
      }
    }
    
    test.a = function()
    {
      //a object defined
    };
    
    test.a.prototype.move = function()
    {
      //move function defined for the a object
    };
    
    etc...
    

    我只是即时输入的,因此可能需要一些调整

    这将允许您致电test('azerty').move();。更重要的是:我希望你能看到所使用的一般结构。

    编辑添加:

    要继续像 jQuery 中那样链接函数,请确保在每个函数调用结束时返回 this 对象:

    test.a.prototype.move = function()
    {
      //move function defined for the a object
      return this;
    };
    

    【讨论】:

      【解决方案5】:

      [可能] 最优雅的解决方案

      首先,jQuery 使用的模式更接近于 MonadFactory 或两者的组合。不过,这是我在项目中一直使用的,因为该模式本身与您想使用的任何类的耦合非常松散:

      ;(function (undefined) {
          if (undefined) return;
          var ENV = this;
      
          var Class = function Class() {
              var thus = this;
      
              function find(data) {
                  console.log('@find #data', data);
                  return this;
              }
      
              function show(data) {
                  console.log('@show #data', data);
                  return this;
              }
      
              // export precepts
              this.find = find;
              this.show = show;
      
              return this;
          };
      
          var Namespace = ENV['N'] = new (function Namespace(Class) {
              var thus = this;
      
              var Ns = Class.apply(function Ns(data) {
      
                  if (this instanceof N) {
                    return new Namespace(Class);
                  }
      
                  return Ns.find.apply(Ns, arguments);
              });
      
      
              return Ns;
          })(Class);
      
      }).call(window || new function Scope() {});
      
      var n = N('#id').show(450);
      var m = new N();
      
      m('#id')('.curried').show('slow');
      
      console.log(n !== m);  // >> true
      

      基本上,您可以将其用作函数、对象,并使用new 关键字构造另一个独特的对象/函数。您可以使用它来强制执行 arbiter 方法(默认,如上面的 find 方法),或者根据输入的参数使用不同的方法。例如,您可以执行以下操作:

      var elementsList = N('#id1')('#id2')('#otherSpecialElement').each(fn);
      

      -- 或者--

      var general = N('.things');
      var specific = general('.specific')('[data-more-specific]').show();
      

      例如,上面将累积多个元素的节点列表(第一个表达式),或向下钻取到一个特定元素(第二个)。

      希望对你有帮助

      【讨论】:

        【解决方案6】:

        我最近进行了一项练习,要求仅使用 ES6 重新创建类似 JQuery 的库。

        根据以上几点,我能够创建实例化逻辑,从而可以在选择器上调用方法,例如类名。

        当使用选择器调用 $T 的实例时,会在特定选择器上创建一个新的 $TLib 实例,该实例将包含可以在选择器上使用的所有方法以及上下文中的元素。

        我在下面的示例中添加了几个可链接的方法,它们允许您将 CSS 类添加到元素并在一次调用中删除相同的类,例如:

        $T('.class-selector').addClass('green').removeClass('green);
        

        对于那些想要引导类似的东西的人:

        const $T = (selector) => {
          // returns the HTML elements that match the selector
          const elements = document.querySelectorAll(selector);
          return new $TLib(elements, selector);
        };
        
        class $TLib {
          constructor (elements) {
            this._elements = elements;
          }
        
          addClass (className) {
            this._elements.forEach((element) => element.classList.add(className));
            return this;
          }
        
          removeClass (className) {
            this._elements.forEach((element) => element.classList.remove(className));
            return this;
          }
        
          toggleClass (className) {
            this._elements.forEach((element) => {
              const classList = element.classList;
              (classList.contains(className)) ? classList.remove(className) : classList.add(className);
            });
            return this;
          }
        
        }
        

        【讨论】:

          【解决方案7】:

          您可以将 'azerty' 值作为参数传递给 MyClass 构造函数,并将 test('azerty').move() 更改为 test.move()

          <script type="text/javascript">
              var MyClass = function(context) { 
                  this.print = function(){     
                      console.log("Printing"); 
                  }  
                  this.move = function(){     
                  console.log(context); 
                  return context;
                  } 
              };  
              var test = new MyClass('azerty'); 
              test.print(); // Works 
              console.log('Moving: ' + test.move() ); // Type property error 
          </script>
          

          【讨论】:

            【解决方案8】:

            到目前为止提供的解决方案似乎并没有反映 jQuery 的确切结构(或 jQuery 随时间变化)。

            我最近想创建一个对象(类似于 jQuery)并遇到了这个问题。所以这是我的答案:

            // dummy text element 
            var p = document.createElement("p");
            p.innerText = "Lorem ipsum...";
            
            
            // jQuery-like object
            var CustomObject = function (element) {
                return new CustomObject.prototype.init(element);
            };
            
            // constructor
            CustomObject.prototype.init = function (element) {
                this.el = element;
                this.text = element.innerText;
            };
            
            // instance methods
            CustomObject.prototype.log = function () {
                console.log(this.text);
                // by returning "this" at the end of instance methods
                // we make these methods chainable
                return this;
            };
            
            CustomObject.prototype.add2body = function (delay) {
                document.body.appendChild(this.el);
                return this;
            };
            
            // all the instance methods are added to CustomObject, not to CustomObject.init (constructor)
            // calling CustomObject() returns a CustomObject.init object, not CustomObject object
            // so by default, instance methods are not accessible
            // to fix this, you need to assign the prototype of CustomObject to CustomObject.prototype.init
            CustomObject.prototype.init.prototype = CustomObject.prototype;
            
            // testing
            var obj = CustomObject(p).add2body().log();

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2020-11-06
              • 2017-03-07
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多