创建一个类似于jQuery的"$"对象

10

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

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

2
test(...) 不同于 new test(...)。 :) - bzlm
我写了一篇文章,描述了创建这种简单库的概念:http://dfsq.info/site/read/writing-your-own-jquery - dfsq
请看我的答案,其中提到了类似于jQuery的简单JavaScript库 - cieunteung
8个回答

19

在我写这篇文章的时候,Squeegy的答案得到了最高票数:7。然而,它是错误的,因为__proto__是非标准的,在Internet Explorer中也不受支持(即使是版本8)。然而,即使去掉__proto__,在IE 6中仍无法使其正常工作。

这个(有点简化的)方法是jQuery实际上所使用的方式(即使在IE 6上也可以尝试一下),它还包括静态方法和方法链的示例。当然,如果想了解jQuery的所有细节,请自行查看jQuery源代码

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

13

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/


现在我明白了。它仍然需要被实例化,但是可以从内部完成。非常酷,谢谢! - Jelle De Loecker
1
很高兴能够帮忙!另外,我刚刚更新了我的答案,使用更好的方法来检测它是否被调用为构造函数,实际上是通过检查this的原型的构造函数。这应该更加健壮。 - Alex Wayne
尽管它按照我的意愿工作,但我刚刚意识到(因为它被实例化为每个MyClass()调用),对于我的目的来说它相当慢。你活着就要学习:P - Jelle De Loecker

2

最近我做了一个练习,要求使用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;
  }

}

1

【可能是】最优雅的解决方案

首先,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关键字构造另一个唯一的对象/函数。您可以使用此方法来强制执行仲裁者方法(默认值,如上面的find方法),或根据输入的参数使用不同的方法。例如,您可以这样做:
var elementsList = N('#id1')('#id2')('#otherSpecialElement').each(fn);

-- OR --

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

例如,上述代码将会积累一个多个元素的节点列表(第一个表达式),或者深入到一个特定的元素中(第二个表达式)。
希望这可以帮助您。

1

当你这样做时

var test = new MyClass()

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


1
如果您想实例化一个test对象,那么调用test().move()是错误的。然而,链接函数调用(jQuery实现的关键)是完全可能的。 - zzzzBov

1
每次在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;
};

0

你可以将'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>

0

到目前为止提供的解决方案似乎不反映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();


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接