ホームページ  >  記事  >  ウェブフロントエンド  >  jQuery の内部フレームワーク構造と動作に関する簡単な説明

jQuery の内部フレームワーク構造と動作に関する簡単な説明

高洛峰
高洛峰オリジナル
2016-11-05 16:50:53976ブラウズ

全体的なアーキテクチャを確立する

プライベート変数を保護し、グローバルな汚染を回避するためのサンドボックスを作成します (function(window){})(winow)

window を仮パラメータとして渡すことの 2 つの利点:

1. ドメイン検索の影響を軽減します。

2. 圧縮効率を向上させる

コンストラクター関数 Guoqi(){} を作成します。 // 機能: 各 Guoqi オブジェクトは疑似配列です

コンストラクター内のプロトタイプ オブジェクトの init メソッドを作成します 新しい Guoqi.prototype を直接返します。 init();

新しいキーワードを正常に非表示にすることができます

しかし、最も重要なステップは Guoqi.prototype.init.prototype = Guoqi.prototype を設定することです

この場合、init すべてのインスタンスは、 Guoqi.prototype プロトタイプ

最後に、window.Guoqi = window.Q = Guoqi とし、外部からの直接呼び出しを容易にするために、Guoqi と Q をインターフェイスとしてウィンドウにハングします

プロトタイプ オブジェクトとして長さを使用するプロトタイプに配置されたオブジェクト、デフォルト値は 0 です。Q オブジェクトが疑似配列であることを確認してください

プロトタイプ オブジェクトを置き換えるメソッドを使用してください。 Guoqi.prototype = {}; 注: コンストラクター属性はコンストラクター自体を指す必要があります

と書くと、インスタンスを作成するたびに new キーワードを指定する必要はありません。init メソッドを強化するには、Q オブジェクトに複数のパラメータを渡すことができ、それが ' で始まるかどうかを判断する必要があります。

それ以外の場合 (DOM 要素が渡される)、qsa メソッドを使用してオブジェクトを取得するセレクターとして使用されます

場合文字列が渡される場合は状況が異なります

DOM オブジェクトが selector.nodeType で渡される場合、オブジェクトは this[0] = selector;this.length = 1; であることを確認してください。疑似配列

Q オブジェクトが渡された場合、 selector.constructor .name = Guoqi (Q オブジェクトはすべて Guoqi コンストラクターを使用して作成されるため)、 selector

で渡された関数を直接返す場合は、操作を行わないでくださいとりあえず typeof selector === "関数"、jQuery のエントリなど 関数 $(function(){})

が配列 (疑似配列) を渡す場合、selector.length >=0, pass配列の値を this に順番に代入します

それ以外の場合は、 this[0] = selector; this.length = 1;

これにより、Q() が何を渡しても、 __疑似配列__を取得します

拡張メソッドの関数を追加します extend( obj ) extend にオブジェクトを渡します

Guoqi.extend = Guoqi.prototype.extend = function(){ } 接続記号を使用して extend メソッドの追加を表しますそれぞれコンストラクターとコンストラクター プロトタイプに

extend は混合継承を使用します 原理は、obj の関数をこのオブジェクトにコピーすることです (注: この時点では 2 つの this 点は異なります)

Guoqi.extend({}) メソッドコンストラクターを拡張すると、ツールメソッドとして静的メソッドと呼ばれます。各メソッドをコンストラクターに追加するには、(each、map、next...)

を使用します。注意してください:これは、走査された現在のオブジェクトを指し、一度リターンされます。値が false の場合、トラバーサルは停止します。つまり、

if( callback.call ( array[i], i, array[i] ) === false ) Break;

map メソッドが window を指している場合のみです。戻り値が未定義または null ではない場合、返された新しい配列に追加されます。それ以外の場合:

if( callback( array[i], i ) != unknown){ newArr.push( callback( array[i], i ) ) }; 最後に __return newArr__;

Guoqi .prototype.extend({}) インスタンスに追加されたメソッドは、インスタンス メソッドと呼ばれます。 : Q().each(function(){}); したがって、appendTo、each、map などの各メソッドを Q インスタンス オブジェクトに転送するには

つまり Guoqi.fn.extend({ each : function(callback) { return Guoqi.each(this,callback); }});

コア メソッドを Array、get に追加します

インデックスが渡されると、インデックス値に対応する DOM 要素が返されます


負の数の受け渡しをサポートします次に、後ろから前に数えます。例: -1 が渡された場合、オブジェクトは長さ 1 の DOM 要素になります。

toArray 関数: 取得した Q オブジェクト (疑似配列) を DOM 要素の真の配列として返します。

get(index) 関数: インデックスが渡されない場合、DOM 要素の真の配列が返されます。 Array

DOM 操作モジュール

まず、サンドボックスで関数 parseHtml(str){} を定義して Q を作成します受信タグに基づいたオブジェクト

作成するときは、自由に div タグを作成し、作成したいものを配置できます。タグを div に追加し、div.innerHTML = str;

その後、 div の childNodes を返すだけですが、__note__、ページ上にレンダリングすると childNodes の長さが変わります

そのため、空の配列を作成し、 DOM 要素を childNodes に配置し、それらを配置する必要があります

その後、parseHtml 関数は __real array__ を返します。これは init メソッドの '

appendTo(selector) インスタンスメソッド関数: このオブジェクトを追加しますセレクターの子要素に

このメソッドも、受信オブジェクト、DOM要素、DOMオブジェクト、Qオブジェクト、疑似配列...に基づいて判断します(つまり、前提はinitメソッドです完璧です)

完成後、まずセレクターを Q オブジェクトに変換します ---> Q(selector)

配列 objQ と変数 tempDom を作成します (次の戻り値のため)

これとセレクターを考慮してネストされたループを作成し、これをクローンします[i]、クローンの数に注意し、オントロジーが最後のものであることを確認し、あまりにも多くのクローンを作成しないでください

。そして常にこの値を tempDom に代入して保存します。tempDom = j === Q( selector).length-1 ? this[i]:this[i].cloneNode(true)

tempDom を配列 objQ に再度追加します。

戻り値の問題を考慮すると、戻り値は次のようになります。この要素はすべて追加されます。クローンされたものを含むセレクターなので、objQ の要素が返すべき値です

この場合、チェーンプログラミングチェーンが破棄されるため、前のチェーンを返す_ _end()__ メソッドを作成する必要がありますobject

ここで、end() メソッドを呼び出して return

できるように、現在の this を保存する必要があります。つまり、objQ を Q オブジェクトに変換し、それに与えます。 this を指す preObj 属性を追加して、Q を返します。 (obj)

end() インスタンス メソッド。チェーンが破棄されたら、それを呼び出して上位レベルのチェーンを検索します (1 つのレベルのみが見つかります)

us 上記の作業は非常に充実していたので、end() メソッドを追加しました。 this.preObj || this を返すだけで済みます (注: 上位レベルがない場合は自分自身を返します)

pushStack() インスタンス メソッド、appendTo メソッド その後、上位レベルのオブジェクトの内容が保存されます。チェーンによって破損するメソッドが多数ある可能性があるため、再利用の効果を実現するメソッドをカプセル化します。 return newObjQ; }

注: このメソッドはインスタンスによって直接呼び出されませんが、appendTo メソッドで this によって呼び出される必要があるため、this の上位チェーン オブジェクトを見つけることができます (ただし、後で実装することもできます) call/apply メソッドを借用するとさらに面倒になります);

つまり、プライベート関数 (parseHtml など) としてサンドボックスに直接配置することはできません。この場合、上位レベルのオブジェクトは window です。 , そして実装される さらに面倒


prependTo append preprend delete... インスタンスメソッドの実装はappendToと同じ原理に基づいています

実装メソッド: 本来はthis.nextElementSiblingを使って実装できますが、互換性の問題があります

そこで、Guoqi コンストラクターを拡張しました。 next(dom) メソッドは dom 要素をループし、nodeType === 1 の場合は dom を直接返し、そうでない場合は null を返します。 .each(function(){ this.parentNode.removeChild( this ); })

implement Q(selector).prependTo(this); return this; チェーンは破棄されません

implement Q(selector).appendTo( this); これを返します; チェーンは破棄されません Destroyed

selector[j].insertBefore(tempDom, selector[j].firstChild);

注: このメソッドは appendTo メソッドと同じです。破棄される

prependTo 関数: このオブジェクトはセレクター オブジェクトに追加されます

このオブジェクトの子要素を追加してセレクター オブジェクトを前面に追加します

このオブジェクトの子要素を先頭に追加してセレクター オブジェクトを前面に追加します

remove メソッドは、this 内の親ノードを見つけて、このノード自体を含むすべての要素を削除します。

next メソッドは、このオブジェクト内の次の要素を見つけます (すべての兄弟ではなく) チェーンは破棄されます

function next(dom){
        while( dom = dom.nextSibling ){
            if( dom.nodeType === 1 ){
                return dom;
            }
        }
        return null;
    }

。次に、次のインスタンス メソッドは、pushStack( this.map(function (v){ return Guoqi.next(v) }) );

filter(selector) メソッドがこのインスタンス オブジェクトの同じ要素を見つけて、 selector

このために ret の空の配列を準備し、Guoqi(selector ) がネストされたループの走査を実行します。 this[i] == Guoqi(selector)[j] の場合、それを配列 ret に追加します

最後に、ret 配列を変換します。インスタンス オブジェクトに変換して返します: return Guoqi(ret);

unique() 関数: 同一の要素を削除します


空の配列 arr を準備し、this.toArray() メソッドを呼び出し、インスタンス オブジェクトを実数の配列 thisArr に変換します。そしてそれをループします

if( arr.indexOf( thisArr (i) )== -1 ){ arr.push(thisArr[i]) };

Then return Guoqi( arr );

children( selector): 機能: パラメーターが渡されない場合は、すべての子要素が検索されます (息子世代のみ)、パラメーターが渡される場合は、セレクターに一致する子要素が検索されます

原理:找到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依次执行


声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。