Heim > Artikel > Web-Frontend > Eine kurze Diskussion über die interne Framework-Struktur und den Betrieb von jQuery
Gesamtstruktur festlegen
Eine Sandbox erstellen, um private Variablen zu schützen und globale Verschmutzung zu vermeiden (function(window){})(winow)
Fenster als formalen Parameter übergeben Zwei Vorteile:
1. Reduzieren Sie die Umfangssuche
2. Verbessern Sie die Komprimierungseffizienz
Erstellen Sie einen Konstruktor, Funktion Guoqi(){} // Funktion: Jedes A Guoqi-Objekt ist ein Pseudo- Array
Erstellen Sie eine Init-Methode für das Prototypobjekt und geben Sie direkt new Guoqi.prototype.init();
im Konstruktor zurück, um die neuen Schlüsselwörter erfolgreich auszublenden,
Der wichtigste Schritt besteht jedoch darin, Guoqi.prototype.init.prototype = Guoqi.prototype festzulegen
In diesem Fall können alle Instanzen von init die Methoden des Guoqi.prototype-Prototyps verwenden
Endlich , window.Guoqi = window.Q = Guoqi und hängen Sie Guoqi und Q als Schnittstellen an das Fenster, um direkte Aufrufe von außen zu erleichtern
Legen Sie die Länge als Objekt des Prototypobjekts ein und geben Sie im Prototyp den Standardwert ein ist 0. Stellen Sie sicher, dass das Q-Objekt ein Pseudo-Array ist
Ersetzen Sie das Prototypobjekt durch Guoqi.prototype = {}; Hinweis: Das Konstruktorattribut sollte auf den Konstruktor selbst zeigen
Der Reihe nach Um das Schreiben zu erleichtern, müssen wir nicht jedes Mal, wenn wir eine Instanz erstellen, das neue Schlüsselwort verwenden. Es gibt einen kleinen Trick, um die Init-Methode zu bereichern zu beurteilen
Wenn es mit „
Andernfalls (das DOM-Element wird übergeben) wird es wird als Selektor unter Verwendung der qsa-Methode verwendet, um Folgendes zu erhalten: Wenn dem Objekt
eine Zeichenfolge übergeben wird, wird der Fall
übergeben. Wenn das DOM-Objekt in selector.nodeType übergeben wird, dann wird es direkt im Objekt gespeichert this[0] = selector;this .length = 1; Stellen Sie sicher, dass es sich um ein Pseudo-Array handelt
Wenn das Q-Objekt übergeben wird, ist selector.constructor.name = Guoqi (Da Q-Objekte alle mit dem Guoqi-Konstruktor erstellt werden), geben Sie den Selektor direkt zurück.
Wenn die Funktion Funktion übergeben wird, wird vorerst keine Operation ausgeführt. Typ des Selektors === „Funktion“, z als Eingabefunktion in jQuery $(function(){})
Eingehend Wenn es sich um ein Array (Pseudo-Array) handelt, wählen Sie selector.length >=0 und übergeben Sie die Array-Werte an dieses in sequence
Ansonsten gilt, egal was eingegeben wird, this[0] = selector; this length = 1;
Dies stellt sicher, dass wir unabhängig davon, was Q() übergibt, erhalten ein __Pseudo-Array__
Fügen Sie die Funktion der Erweiterungsmethode extension( obj ) in hinzu. Übergeben Sie ein Objekt in extension
Guoqi.extend = Guoqi.prototype.extend = function(){ } Verwenden Sie die Konjunktionssymbol bedeutet das Hinzufügen von Erweiterungsmethoden
extend verwendet das Prinzip der gemischten Vererbung, um die Funktion von obj auf dieses Objekt zu kopieren (Hinweis: Die beiden this-Punkte sind derzeit unterschiedlich) Guoqi.extend({}) ist eine Methode zum Erweitern des Konstruktors. Sie wird als statische Methode bezeichnet. Als Werkzeugmethode verwenden Sie (each, map, next...) zum Hinzufügen die every-Methode an den Konstruktor. Bitte beachten Sie: Dies zeigt auf das aktuell durchquerte Objekt, und sobald der Rückgabewert falsch ist, stoppen Sie die Durchquerung, d. h.if( callback.call( array[i], i, array [i] ) === false ) break;
map-Methode zeigt auf das Fenster. Nur wenn der Rückgabewert nicht undefiniert oder null ist, werden die durchlaufenen Elemente dem zurückgegebenen neuen Array hinzugefügt, andernfalls:
if( callback( array[i], i ) != undefiniert){ newArr. push( callback( array[i], i ) }; {}) Die der Instanz hinzugefügte Methode wird als Instanzmethode bezeichnet, und jedes Instanzobjekt kann
aufrufen, um der Instanz eine Methode hinzuzufügen. Syntax: Q().each(function(){}); Sie müssen also jede Methode an das Q-Instanzobjekt übertragen, z. B. appendTo, every, map
das ist Guoqi .fn.extend({ every : function(callback){ return Guoqi.each(this,callback); } });
Kernmethode zu Array hinzufügen, get
Wenn eine in index übergeben wird, dann unterstützt das DOM-Element
Dann geben Sie einfach die childNodes des div zurück, aber __note__, die Länge der childNodes ändert sich, wenn wir sie auf der Seite rendern
Also müssen wir ein leeres Array erstellen und die DOM-Elemente einfügen childNodes nacheinander in das Array einfügen,
Dann gibt die parseHtml-Funktion ein __reales Array__ zurück, das nach der Konvertierung in ein Q-Objekt
unter der Beurteilung von „
Diese Methode hängt auch vom eingehenden Objekt, DOM-Element, DOM-Objekt, Q-Objekt, Pseudo-Array ... ab, um ein Urteil zu fällen ★ (Voraussetzung ist also die Perfektion der Init-Methode)
Nach der Perfektion konvertieren Sie zunächst den Selektor in ein Q-Objekt ---> Q(Selektor)
Erstellen Sie ein Array objQ und eine Variable tempDom (für die folgende Überlegung des Rückgabewerts)
Führen Sie eine verschachtelte Schleife für diesen und den Selektor aus und klonen Sie diesen[i]. Achten Sie darauf, dass die Ontologie der letzte ist , klonen Sie nicht zu viele
und weisen Sie tempDom weiterhin den Wert zu, um ihn zu speichern, tempDom = j === Q(selector).length-1 ? this[i]:this[ i] .cloneNode(true)
tempDom zum Array objQ hinzufügen,
In Anbetracht der Frage des Rückgabewerts sollte unser Rückgabewert zu allen diesen Elementen im Selektor hinzugefügt werden, einschließlich der geklonten. also sind die Elemente in objQ die Werte, die wir zurückgeben sollten
In diesem Fall wird unsere Kettenprogrammierungskette zerstört, also müssen wir eine __end()_ _-Methode erstellen, um das vorherige Kettenobjekt
Hier müssen wir das aktuelle This speichern, damit der Aufruf der end()-Methode zurückgeben kann, das heißt: objQ in Q Object konvertieren, ein preObj-Attribut hinzufügen, das darauf zeigt, und dann Q(obj) zurückgeben end() Instanzmethode Wenn die Kette zerstört ist, rufen Sie sie auf, um die Kette der oberen Ebene zu finden (kann nur die erste Ebene finden) Wir haben Habe oben viel Arbeit gemacht, daher muss die end()-Methode nur this.preObj || zurückgeben (Hinweis: Wenn es keine vorherige Ebene gibt, geben Sie sich selbst zurück)pushStack()-Instanzmethode ist um den Inhalt des übergeordneten Objekts nach der appendTo-Methode zu speichern. Da es möglicherweise viele Methoden gibt, die durch die Kette beschädigt werden, kapseln wir eine Methode, um komplexe zu implementieren 🎜> ist: function pushStack(arr){ var newObjQ = Q(arr); newObjQ.preObj = this; return newObjQ; direkt von der Instanz aufgerufen werden, da es von dieser in der appendTo-Methode aufgerufen werden sollte, kann das Kettenobjekt der oberen Ebene gefunden werden (obwohl es später durch Ausleihen der Call/Apply-Methode implementiert werden kann, ist dies jedoch problematischer). ) ); Das heißt, es kann nicht direkt als private Funktion in die Sandbox eingefügt werden (z. B. parseHtml). In diesem Fall ist das Objekt der oberen Ebene ein Fenster, was schwieriger zu implementieren ist . prependTo append preprend remove ... Die Implementierung von Instanzmethoden basiert auf dem gleichen Prinzip wie appendTo
Implementierungsmethode: Ursprünglich könnte es mit this.nextElementSibling implementiert werden, es gibt jedoch Kompatibilität Probleme
Also haben wir den Guoqi-Konstruktor erweitert. Die Methode next(dom) durchläuft das dom-Element, um seinen nächsten Knoten zu finden. Wenn nodeType === 1, wird dom direkt zurückgegeben 🎜> ist
um dies zu implementieren.
Implementierung Q(selector).prependTo(this); Die Kette wird nicht zerstört
Implementierung Q(selector).appendTo(this); return this; Die Kette wird nicht zerstört
Implementieren Sie selector[j].insertBefore( tempDom, selector[ j].firstChild );
Hinweis: Diese Methode ist die gleiche wie die appendTo-Methode, die Kette wird zerstört
prependTo-Funktion: Dieses Objekt wird vor den untergeordneten Elementen von hinzugefügt das Selektorobjekt
hängen Sie die untergeordneten Elemente dieses Objekts an, um das Selektorobjekt hinzuzufügen
stellen Sie das untergeordnete Element dieses Objekts voran und fügen Sie das Selektorobjekt vorne hinzu
Methode entfernen findet den übergeordneten Knoten darin und löscht alle darin enthaltenen Elemente, einschließlich dieses selbst
nächste Methode findet dies. Die Kette des nächsten Elements im Objekt (und nicht aller Geschwister) wird zerstört
Dann muss die nächste Instanzmethode nur pushStack ( this.map(function(v){ return Guoqi.next(v) }) );
zurückgeben
Filter(Selektor)-Methode findet den gleichen Wert für dieses Instanzobjekt und Selektorelementfunction next(dom){ while( dom = dom.nextSibling ){ if( dom.nodeType === 1 ){ return dom; } } return null; }
bereitet ein ret-leeres Array vor und führt einen verschachtelten Schleifendurchlauf für dieses und Guoqi(Selektor) durch. Wenn this[i] == Guoqi(selector)[j], fügen Sie es dem Array ret hinzu
unique() Funktion: Dieselben Elemente entfernen
Bereiten Sie ein leeres Array arr vor, rufen Sie die Methode this.toArray() auf, um das Instanzobjekt in das echte Array thisArr zu konvertieren, und durchlaufen Sie dann
if( arr.indexOf( thisArr(i) )== -1 ){ arr.push( thisArr[i]) };
Dann gib Guoqi( arr );
children(selector): Funktion: Wenn keine Parameter übergeben werden, werden alle untergeordneten Elemente gefunden (beschränkt auf die Sohngeneration). Wenn Parameter übergeben werden, wird das zum Selektor passende untergeordnete Element gefunden原理:找到this实例对象中的所有的子元素,组成一个数组,找到selector中的所有元素 组成一个数组,然后寻找两个数组之间相同的元素即可(调用filter方法),注意:涉及到了unique去重的问题
首先
var subList = this.map(function(v){ return Guoqi.map(v.children,function(v1){ return v1 }) }); // 此时 sunList是一个二维数组,即:数组里面套数组
需要把subList 合成一个 一维数组,借用concat方法:var newSub = [].concat.apply([],subList);
然后对 newSub进行去重(需要将数组转换为Q对象才能调用unique方法), var subQ = Guoqi(newSub).unique();
需要对selector进行判断,如果 selector存在的话,那么 return subQ.filter( Guoqi(selector) ); 如果不存在的话,那么直接 return subQ;
find(selector) : 功能:find一般是找指定的元素,在this实例对象的所有后代元素中找,所以一般都传入参数
find方法和children方法一样,只需要把
var subList = this.map(function(v){ return Guoqi.map(__v.querySelectorAll(selector)__,function(v1){ return v1 }) });
用v.querySelectorAll(selector); 来找到所有的后代元素(应该还有更好的办法,欢迎来补充)
nextAll 工具方法 跟next方法如出一辙:
即
function next(dom){ while( dom = dom.nextSibling ){ if( dom.nodeType === 1 ){ return dom; } } return null;}
next,prev,nextAll,prevAll 方法 都常会在 封装实例方法中用到,所以可以封装为工具方法; prevAll,prev 两个方法和next,nextAll 原理一样,所以不再详细赘述
注意:nextAll,prevAll,children 这些链都遭到了破坏,不过可以使用end恢复
nextAll 实例方法 既然有了上面的nextAll的工具方法,那么相对应的实例方法就简单了很多,
只需要把this实例对象的每一个dom元素调用 nextAll(dom),组成一个数组(用map方法简单),
然后把这些数组用concat组合在一起进行__去重(unique)__,进而转换为Guoqi对象即可
siblings 实例方法
有了prevAll 和 nextAll方法,siblings就变得简单多了,只要两者组合在一起即可
var nextSiblings = this.nextAll().toArray(); var prevSiblings = this.prevAll().toArray();
返回 Guoqi( prevSiblings.concat( nextSiblings ) );
事件操作 模块
on 事件的实现 (我们选择先实现on事件,是因为on事件是通用的,实现on事件之后,其他的具体事件都可以用on事件来实现)
on 事件 语法:Guoqi.fn.extend(on: function( 事件列表,callback ){ });
事件列表 可以有实现多个事件 中间用空格隔开
这就意味着 我们要对this实例对象进行遍历,也要对事件列表进行遍历
添加事件的时候,我们选择使用 addEventListener("事件名",callback);
各个事件的实现
首先我们可以从控制台打印出来所有的事件
创建一个DOM对象div,for( var k in div ){ if( k.slice(0,1) === "on" ){ arr.push(k) } }; 这样就把所有事件放到arr数组中去了
循环遍历数组中每个值,只保留事件名的部分,即:v = v.slice(2);
然后添加在原型上:Guoqi.fn[v] = function(callback){ return this.on(v,callback) };
css 样式操作模块的实现
首先我们需要考虑css的参数情况 语法:Guoqi.fn.css = function( name,value){ }
字符串( typeof name === "string" ),即:需要获取实例对象的值:一般情况下,我们获取的是实例对象中第一个元素的值
(this[0] / this.get(0)) 可以直接 return this[0].style[name] ,但是值得注意的是,这样只能获取行内样式的值
所以 我们还需要 || window.getComputedStyle(this[0])[name]; 但是getComputedStyle在低版本的IE浏览器中(currentStyle)不支持,
如果考虑严谨的话,可以封装一个getStyle获取样式的函数
只有一个参数 即:value == undefined 可能是对象,也可能是一个字符串
function getStyle(dom,style){ return dom.currentStyle?dom.currentStyle[style]: window.getComputedStyle(dom)[style]}
这样的话,只需要循环遍历this对象,即:this[i].style[name] = value; 并且要把this实例对象返回回去,实现链式编程
对象 不仅需要循环遍历this实例对象,还需要遍历name这个键值对的值:做到for( var k in name ){ this[i].style[k] = name[k] };
注意要返回 this实例对象,便于链式编程
有两个参数 name value 设置单个样式
属性 样式操作模块的实现 (与css实现原理相似)
hasClass(className) 判断实例对象 有没有该类名,
实现:我们需要分别对this实例对象进行遍历,和他们的所有的类名进行遍历,
为了方便操作,需要把实例对象的类名转换为数组,并去除前后空格(遍历检查),然后使用__indexOf()__方法,若为-1,则返回false, >=0或者!=-1则为true;
即:var classNameValues = this(指的是实例对象中的dom).className.trim().split(" "); classNameValus.indexOf(className) >=0 --->true
可以使用some方法,(只要有一个满足条件直接放回true) 即:return this.toAArray().some(function(v){ return v.trim().split(" ")indexOf(className) >= 0 };
addClass(className) 添加类名
又需要分清况讨论了,若是没有类名:即classNameValues.length == 0,则直接添加 this.className = className;
若是有class,但是没有该类名,则需要追加;即:if( classNameValues.indexOf(className) == -1 ){this.className += " "+classNaame};__(注意要用空格分隔开)__;
若是已经有该类名了,则什么都不需要做(不能重复添加)
addClass可是直接用数组来实现简单一些:只要if( classNameValues.indexOf(className) == -1 ){ classNameValues.push(className) };this.className = classNameValues.join(" ");
removeClass(className) 删除类名
就是把指定的类名给删除掉,需要进行循环遍历所有类名数组 classNameValues,然后用splice来把指定的类名从数组中给截取掉;
即var index;(来记录查到指定类名的索引)
js while( (index= classNameValues.indexof(className))>=0 ){ classNameValues.splice(index,1);} this.className = classNameValues.join(" ");
还可以使用 map方法; 即:this.className = classNameValues.map(function(v){if(v != className){return v;}}).join(" "); 利用map方法产生一个新数组,简单一些
toggleClass(className) 切换类名
直接进行判断
if(this.hasClass(className)) { this.removeClass(className) }else{ this.addClas s(className) };
对上面的代码实现复用,减少代码冗余; 如果有该类名的话,直接删除,没有类名的话,就添加
attr(name,value) 对属性样式的设置 和css 原理一样:还是要分情况:
一个参数时,类型为字符串,获取属性值:用getAttribute(name);
一个参数时,类型为对象,数值多个属性值 ,循环遍历该对象用setAttribute(k,name[k])来进行设置
两个参数时,设置单个属性值,直接进行设置即可:setAttribute(name,value);
prop(name,value) 与上述情况一样,分类进行考虑,
但是注意的是。prop针对的是input标签这些单属性的值,值为布尔类型,例如disabled,checked,selected
设置和获取的时候不用setAttribute,getAttribute,直接进行赋值即可,this[name] = value;
注:如果对其进行赋值,例如disabled,不论赋值为true还是false,都不可编辑,除非移除该属性removeProp
removeAttr与removeProp,个人认为实现原理一样,都是使用removeAttribute
即:遍历this实例对象,然后进行 this.removeAttribute(name);
入口函数,即init方法中selector传入的数函数的情况
方法一:直接使用window.addEventListener("load",selector); 可以实现累加,开辟不同的入口函数,互不影响
方法二:利用函数的技巧:记录下来window.onload的值,进行判断如果是一个函数的话,则他执行,在执行selector传入的,否则的话,直接把selector赋值给window.onload
即:
var oldFn = windiw.onload ; if(typeof oldFn == "function"){ window.onlad = function(){ oldFn();selector(); }}else{ window.onload = selector(); }
方法三:利用数组的技巧:建立一个私有数组,var loads = []; 直接把一个个的selector给push到数组中去,
然后定义一个
window.onload = function(){ Guoqi.each(loads,function(){ this(); }) } // 把数组中的selector依次执行