Autor Zpráva
Suta
Profil
Jelikož mě zajímá, jak funguje jQuery vevnitř, pustil jsem se do rozboru jeho zdrojového kódu. jQuery má dost komplikovaný kód, ale s trochou snahy se dá rozluštit základní schéma. Bohužel, ani po dlouhém hledání jsem nenašel vysvětlení, jak "konkrétně" jQuery funguje a jak je postaven, tak jsem se do jeho rozboru pustil sám.

Přidávám kód, který jsem z jQuery dostal a prosím zkušenější o vysvětlení částí, u kterých ještě tápu. Otázky jsou nastíněny v závěru hlavního příspěvku pod kódem.

Pozn.: kód je co nejvíce zestručněn, obsahuje pouze hlavní funkční prvky bez jakýchkoliv ošetření.

<script type="text/javascript">
// 1.
(function() {

var jQuery = (function() {

// 2.
var jQuery = function( selector ) {
		// 3.
		return new jQuery.fn.init( selector );
	}

// 4.
jQuery.fn = jQuery.prototype = {
	init: function( selector ) {
	        var elem = // zpracujeme selektor a vytahneme id elementu
		      elem = document.getElementById(elem);
		      
		// 5. ( tipuji že this===jQuery.fn)
		
		// 6. tady je stěžejní část; pokud this ukazuje na metodu fn() (tedy na funkci),
		// jakým způsobem je pak docíleno uložení elementu do indexu pole ? ( this[0])

                this.length = 1;
                this[0] = elem;

		this.context = document;
		this.selector = "#test";
		return this;
	}
};

// 7.
jQuery.fn.init.prototype = jQuery.fn;

jQuery.extend = jQuery.fn.extend = function() {
	var target = this;
        var options = arguments[0];
        
        // 8. přidáme vlastní metody (rozšíříme základní objekt jQuery)

        for ( var name in options ) {
            var src = options[ name ];

            target[ name ] = src;
	}

	return target;
};

// 9. vytvoříme globální objekt jQuery (případně $)
return (window.jQuery = window.$ = jQuery);

})();

jQuery.fn.extend({

        // 10. přidáme vlastní metody
	addClass: function( value ) {

                var classNames = value;
		var elem = this[0];
                elem.className = value;

                return this;
	},
	dalsiMetoda: function( xxx ) {
	        // ...
	}
});


})();
</script>


1. Vytvořením soukromého bloku nebudeme špinit globální kontext
2. Definujeme lokální kopii jQuery
3. jQuery object je aktuálně pouze rozšiřující init constructor
4. Proč je hlavní metoda jQuery.fn.init přiřazena také do do prototypu jQuery ?
7. Nerozumím.

V kódu je celkem 10. zarážek, za jakékoliv připomínky a vysvětlení jakékoliv z nich budu moc vděčný.
ah01
Profil
Upozorňuji, že abys pochopil všechny aspekty, musíš mít dobré znalosti JavaScriptu, zejména prototypové dědičnosti a věcí s tím spojených (co je to prototype objektu, co dělá new, jak funguje instanceof atd.).

1.
Správně.

2.
To je definice funkce jQuery. To je ta funkce, kterou voláš, když ve svém skriptu zavoláš $(...). Viz bod 9.

3.
jQuery object je aktuálně pouze rozšiřující init constructor
To jsi trochu nepřesně přeložil komentář ze zdrojového kódu. Ono tam spíš stojí: „jQuery objekt je vlastně jen ‚vylepšený‘ konstruktor init

Ve chvíli kdy voláš $(...), vytvoří se nová instance objektu jQuery.fn.init (bod 3). Prototype tohoto objektu je jQuery.fn (bod 7).

4.
Teď nevím přesně na co se ptáš. Proč je konstruktor init v objektu jQuery.fn? Nebo proč je jQuery.fn = jQuery.prototype?

První otázkou si nejsme jistý. Možná to má nějaký hlubší smysl, ale každopádně mi připadá logické, aby konstruktor byl součástí objektu.

Druhá otázka má celkem jasnou odpověď – aby fungoval operátor instanceof nad jQuery objektem:
jQuery("#něco") instanceof jQuery === true;
To je důsledkem bodu 7 jQuery.fn.init.prototype = jQuery.fn => jQuery.fn.init.prototype = jQuery.protptype


5.
Tipuješ špatně.
this instanceof jQuery.fn.init
neboli
this instanceof jQuery
(viz vysvětlení k bodu 4)

6.
pokud this ukazuje na metodu fn()
Neukazuje, viz bod 5.

jakým způsobem je pak docíleno uložení elementu do indexu pole
Na tom není nic zvláštního. Proč by se prvek objektu nemohl jmenovat 0, 1, 2, … To je normální chování JS. Objekt jQuery (jeho instance) se tváří jako pole, i když jím není. Následující kód bude normálně fungovat:
var divs = $("div");
for(var i = 0; i < divs.length; i++)
{
  divs[i].…
}

7.
viz bod 3

8.
viz http://api.jquery.com/jQuery.extend/

9. a 10.
Správně.
Suta
Profil
ah01:
Nejprve velké díky ohledně doplnění mých nejasností, vážím si tvého času.

A1.
Mohl by být úplně první řádek kódu knihovny jQuery zapsán bez deklarace (v tebou uvedeném odkazovaném kódu funkce jQuery není zabalena do soukromého bloku)? Tedy místo
(function() { var jQuery = (function() { // vnitřní kód jQuery }) () })()

použít pouze
(function() { ... })()

bez přiřazení do var? Podle mě by se jednalo o totéž, protože hned po této deklarace je definována další - lokální proměnná var jQuery (ta, která hraje roli hlavního konstruktoru), která je v závěru bloku exponována do globálního oboru.

A2.
K mé otázce číslo 6:
jakým způsobem je pak docíleno uložení elementu do indexu pole ? ( this[0])

Ano, máš pravdu, prvek objektu je skutečně možné pojmenovat pomocí čísla, nevěděl jsem o této možnosti u objektů.
mujObjekt[0] = // funguje
mujObjekt.0 = // (nula) - chyba


A3.
Záhadné pro mě totiž bylo explicitní nastavení vlastnosti length na hotnotu 1 [řádek 141-143 v tebou odkazovaném zdroji] :
// Otherwise, we inject the element directly into the jQuery object
this.length = 1;
this[0] = elem;


Vlastnost length u objektu má hodnotu undefined (i po zápisu mujObjekt[2] = "xxx", protože se v podstatě jedná o přiřazení vlastnosti). Chápu tedy správně, že
this.length = 1

na řádku 141 je vytvoření nové vlastnosti length pro objekt? (tedy odlišné vlastnosti jako je length u pole, která značí počet prvků).
var xx = {};
    xx[0] = "test";
    xx[1] = "test";
    xx[2] = "test";
    xx.context = document;
    // xx.length === undefined;
    xx.length = 3; // toto chápu jako pomocnou vlastnost pro uložení počtu objektů definovaných pomocí číselného indexu


A4.
Toto je můj odhad, jakým způsobem pracuje jQuery a jakým je docílo řetězení dotazů pomocí tečkové notace. Prosím o opravení, pokud se pletu.
var mujObjekt = {
    a: function() {
        this[0] = "a"; // mujObjekt[0] == "a";
        return this; // kontext nastavíme na mujObjekt
    },
    b: function() {
        this[0] = "b"; // mujObjekt[0] == "b";
        return this; // kontext nastavíme na mujObjekt
    }
}
mujObjekt.a().b().a().b();


ah01:
Upozorňuji, že abys pochopil všechny aspekty, musíš mít dobré znalosti JavaScriptu, zejména prototypové dědičnosti a věcí s tím spojených (co je to prototype objektu, co dělá new, jak funguje instanceof atd.).

Studiem objektového programování v javascriptu se bavím více než rok (jako koníček). Problémem pro mě byl nedostatek kvalitních zdrojů. Za tu dobu jsem koupil 6 knih o javascriptu, dva týdny zpátky se mi však do ruky dostala kniha "Profesionální javascript pro webové vývojáře" (stála 910,- Kč). Nic lepšího o javascriptu v ČR neexistuje. Jediná kniha, která vysvětluje, jak se mají správně definovat funkce, jak funguje prototype, constructor, uzávěry... Zodpověděla mi otázky, které jsem půl roku složitě hledal na internetu, a zodpověděla je v kompaktní, přehledné, a obsáhlé formě. Doporučuju všem, kdo to myslí s javascriptem vážně.

Za jakékoliv další postřehy k výše uvedenému předem velké díky!
ah01
Profil
A1.
Nemohl, přestalo by totiž fungovat jQuery.noConflict (kód), respektive noConflict by fungovalo, ale po jeho zavolání by přestalo fungovat všechno ostatní ;). Ono je tam těch lokálních proměnných jQuery víc. Musíš si všímat v jakém kontextu.
(function () {
  // core.js
  var jQuery = (function () {
    // definice jQuery objektu 
    var jQuery = function( selector ) {
        return new jQuery.fn.init( selector );
    };
    // původní obsah window.jQuery a window.$ 
    // co kdyby byl na stránce jiný framework používající identifikátor „$“ 
    var _jQuery = window.jQuery,
        _$ = window.$; 
    
    // tady je ta vlastní funkcionalita, např.:
    noConflict: function (deep) {
      window.$ = _$;
      if (deep) window.jQuery = _jQuery;
      return jQuery;
    },…
    
    // umístíne jQuery do globálního kontextu (jQuery a $) a taky jej vrátíme
    return (window.jQuery = window.$ = jQuery);
  })();  

  // další součásti - animace, ajax,... závysející na 
  jQuery
  
})();
Kdyby jsi jQuery jako lokální proměnnou (tu zvýrazněnou v kódu) nedeklaroval, tak by se ve zbytku frameworku používala globální proměnná window.jQuery. Ale ta po zavolání noConflict(true) přestane existovat (resp. bude v ní to, co bylo před tím, než jsi vložil jQuery do stránky – _jQuery)

A3.
Ano, přesně tak. Tváří se to jako pole, ale pole to není, takže length musíš nastavovat sám.

A4.
Ano, klíčové je return this;. Říká se tomu chaining (řetězení). Viz např. Maintaining Chainability na stránce věnované vývoji puginů pro jQ.


Suta:
Kniha JavaScript pro webové vývojáře, Programujeme profesionálně – „Doporučuju všem, kdo to myslí s javascriptem vážně.

Nemůžu než souhlasit! Četl jsem první vydání této knihy (2005) a až s ní mě začal JavaScript opravdu bavit. U nás vyšel loni překlad druhého vydání. Internet a Google je fajn zdroj informací, ale kniha je kniha!
Suta
Profil
Rád bych poprosil o zhodnocení způsobu vytváření elementů, který mě napadl.

Vytvářím aplikaci, která je 100% dynamická. Veškerý kód HTML je tedy vytvářen dynamicky pomocí javascriptu = každý element ve stránce je vytvořen pomocí metody document.createElement. Vytvářím-li pro elementy jazyka HTML speciální metody a vlastnosti, můžu je tradičně aplikovat několika způsoby.

1. Zavolám klasickou funkci, které zašlu element, ta s ním provede požadovanou operaci. Vyřazeno!
2. Rozšířím nativní objekt javascriptu Object. Vyřazeno!
3. Rozšířím element o danou metodu. Vyřazeno!
4. Použiji některou z javascriptových knihoven, nebo vytvořím vlastní na principu některé z rozšířených knihoven. Nevyřazeno, nicméně:

Napadl mě tento přístup. Vytvářím-li všechny elementy pomocí metody document.createElement, vytvoření metod, které by mohly veškeré elementy používat bych mohl provést pomocí prototypu takto:

// definice konstruktoru
function MyDocumentCreateElement = function(tagName) {
    this.el = document.createElement(tagName);
}

// vytvoření metod
MyDocumentCreateElement.prototype.fadeOut = function() {
    // pracuj s this.el
}

// vytvoření elementu
var myDiv = MyDocumentCreateElement("div");

// zavolání metody
myDiv.fadeOut();


Samozřejmě by veškeré elementy neměly přístup ke všem metodám. Vytvořil bych speciální strukturu dědičnosti, kdy by určité mnou definované speciální typy elementů dědily všechny společné metody a navíc měly přístup ke svým specifickým metodám. Tedy stejný princip, jakým datový typ v javascriptu přistupuje k metodám konstruktoru (objekt typu Array má přístup k metodám konstruktoru Array a zároveň sdílí všechny metody definované v hlavním objektu Object).

Tento přístup mi přijde odlehčenější než např. způsob, jež používá jQuery (není myšleno jako kritika, jQuery a drtivá většina podobných frameworků musí pracovat s elementy, jež jsou od začátku začleněny v kódu HTML).

Prosím o postřehy, pozitivní či negativní dopady k výše uvedenému.
Witiko
Profil
Suta:
1. Zavolám klasickou funkci, které zašlu element, ta s ním provede požadovanou operaci. Vyřazeno!
Rozdíl mezi tímto a Tebou na konci nabízeným řešením je marginální.

1. způsob (ten vyřazený)
// vytvoření metod
var fadeOut = function(el) {
    // pracuj s el
}

// vytvoření elementu
var myDiv = document.createElement("div");
// zavolání metody
fadeOut(myDiv);


2. způsob
// definice konstruktoru
function MyDocumentCreateElement = function(tagName) {
    this.el = document.createElement(tagName);
}

// vytvoření metod
MyDocumentCreateElement.prototype.fadeOut = function() {
    // pracuj s this.el
}

// vytvoření elementu
var myDiv = MyDocumentCreateElement("div");

// zavolání metody
myDiv.fadeOut();


Momentálně jediný rozdíl je v tom, že tvoříš dvě nové instance objektu namísto jedné. A ano, nedochází k zanesení jmenného prostoru.

Prosím o postřehy, pozitivní či negativní dopady k výše uvedenému.
Není na tom moc ke komentování. Jde o standardně používaný postup, který je omotaný v OOP. Prototypování celému modelu v příkladu, který ukazuješ, nepřináší žádné výhody.
Suta
Profil
Witiko:
Ano, v této jednoduché verzi by bylo zbytečné tvořit speciální konstruktor. Nicméně u velkého projektu je objektový přístup neocenitelný, přijde mi to, jako bychom se pustili do debaty o tom, zda je objektově orientovaný přístup výhodnější než klasický přístup pomocí funkcí. Podle mě záleží na složitosti projektu.

Už teď si dokážu představit desítky situací, s nimiž bych při použití klasické funkce měl problém, nebo bych musel stejný případ řešit mnohem složitěji.

Chtěl jsem vědět o negativech, o nichž možná nevím. Vytvoření objektu "navíc" mi přinese více výhod.
Witiko
Profil
Suta:
Už teď si dokážu představit desítky situací
Tak je tu nastiň, pak se můžeme bavit o adekvátnosti a případných problémech daného postupu.

Vaše odpověď

Mohlo by se hodit

Neumíte-li správně určit příčinu chyby, vkládejte odkazy na živé ukázky.
Užíváte-li nějakou cizí knihovnu, ukažte odpovídajícím, kde jste ji vzali.

Užitečné odkazy:

Odkud se sem odkazuje


Prosím používejte diakritiku a interpunkci.

Ochrana proti spamu. Napište prosím číslo dvě-sta čtyřicet-sedm: