Создание объекта jQuery типа "$"
Моя конечная цель - сделать что-то вроде этого:
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
Ответы
Ответ 1
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/
Ответ 2
Как я пишу это, ответ 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!');
Обратите внимание: если вам нужны данные "private", вам нужно будет поместить методы экземпляра внутри MyClass.init
(с переменной, объявленной только внутри этой функции), как this.print = function() { ... };
вместо MyClass.init.prototype
.
Ответ 3
когда вы делаете
var test = new MyClass()
создается объект с двумя атрибутами move
и print
. Объект test
больше не является функцией из-за оператора new
. Поэтому вызов test()
неверен.
Ответ 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
Вы можете передать значение "azerty" в качестве параметра в конструктор MyClass и изменить тест ( "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>
Ответ 6
[Возможно] Самое элегантное решение
Во-первых, jQuery использует шаблон, который ближе к Monad, Factory или их комбинации. Тем не менее, вот то, что я использовал в своих проектах, потому что сам шаблон настолько слабо связан с любым классом, который вы хотели бы использовать:
;(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();
Выше было бы, например, накапливать нодлист из нескольких элементов (1-е выражение) или переходить к одному конкретному элементу (2-й).
Надеюсь, что это поможет
Ответ 7
Недавно я работал над упражнением, которое попросило воссоздать JQuery-подобную библиотеку, используя только ES6.
В приведенных выше пунктах я смог создать логику создания экземпляров, которая позволяет вызывать методы для селекторов, например, имена классов.
Когда экземпляр $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;
}
}