Reputation: 21945
My end goal is being able to do something like this:
MyVar(parameter).functionToPerform();
Silly enough, even after reading up on how variables are declared, looking at the jQuery code, ... I still can't get my head around it.
This is what I've tried so far, but it fails:
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
Upvotes: 10
Views: 8041
Reputation: 32082
As I write this, Squeegy's answer has the highest number of votes: 7. Yet it is wrong because __proto__
is non-standard and is not supported by Internet Explorer (even version 8). However, getting rid of __proto__
does not get it working either in IE 6.
This (somewhat simplified) is the way jQuery actually does it (even try it on IE 6), and it also includes examples of static methods and method chaining. For all the details of how jQuery does it, of course, you will have to check the jQuery source code yourself.
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!');
Note that if you need "private" data, you will have to put instance methods inside MyClass.init
(with a variable declared just inside that function) as this.print = function() { ... };
instead of using MyClass.init.prototype
.
Upvotes: 19
Reputation: 12027
The solutions provided so far don't seem to reflect jQuery's exact structure (or jQuery changed over time).
I recently wanted to create an object (similar to jQuery) and came across this question. So here's my answer:
// 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();
Upvotes: 0
Reputation: 367
I've recently worked on an exercise that asked to re-create a JQuery like library using ES6 only.
From the points above I was able to create the instantiation logic that makes it possible to call methods on selectors e.g class names.
When the instance of $T is called with a selector a new instance of the $TLib is created on the particular selector which will contain all of the methods that can be used on the selector with the elements in context.
I've added a couple of methods which are chainable in the example below which allows you to add a CSS class to an element and remove the same class in one call e.g:
$T('.class-selector').addClass('green').removeClass('green);
For those wanting to bootstrap something similar:
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;
}
}
Upvotes: 2
Reputation: 10015
First off, jQuery uses a pattern which is closer to a Monad, a Factory, or a combination of both. Nevertheless, here's what I've been using in my projects because the pattern, itself, is so loosely coupled to whatever class you'd like to utilize:
;(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
Basically, you can use it as a function, an object, and use the new
keyword to construct another unique object/function. You can use this to enforce an arbiter method (default, like the find
method above), or use different methods based upon what parameters are input. For instance, you can do something like:
var elementsList = N('#id1')('#id2')('#otherSpecialElement').each(fn);
-- OR --
var general = N('.things');
var specific = general('.specific')('[data-more-specific]').show();
The above would, for instance, accumulate a nodelist of multiple elements (1st expression), or drill down to one specific element (2nd).
Hope this helps
Upvotes: 1
Reputation: 187034
jQuery()
is both a module with global methods, and a constructor. It automatically calls a constructor if it needs to. If we are not called with a new
keyword, then this
will not have been constructed with MyClass
. We can detect that and call the function in constructor mode instead. Once we do that, then this
will be an instance of MyClass
and we can start adding stuff to it.
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>');
Upvotes: 13
Reputation: 179086
every time you call $
in jQuery it returns a new jQuery.init
object. The jQuery.init
object has functions that are then called.
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...
I just typed this on the fly, so it may need some tweaking
This would allow you to call test('azerty').move();
. More importantly: I hope you can see the general structure being used.
Edit to add:
To continue chaining functions like in jQuery, make sure you return the this
object at the end of each function call:
test.a.prototype.move = function()
{
//move function defined for the a object
return this;
};
Upvotes: 1
Reputation: 82913
You can pass the 'azerty' value as parameter to MyClass constructor and change the test('azerty').move() to 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>
Upvotes: 0
Reputation: 3384
when you do
var test = new MyClass()
you create an object that has two attributes move
and print
. You object test
is no more a function because of the new
statement. So calling test()
is wrong.
Upvotes: 1