Home >Web Front-end >JS Tutorial >A brief discussion on the internal framework structure and operation of jQuery

A brief discussion on the internal framework structure and operation of jQuery

高洛峰
高洛峰Original
2016-11-05 16:50:531047browse

Establish the overall structure

Create a sandbox to protect private variables and avoid global pollution (function(window){})(winow)

Two advantages of passing window as a formal parameter:

1. Reduce the effect Domain search

2. Improve compression efficiency

Create a constructor, function Guoqi(){} // Function: Each Guoqi object is a pseudo array

Create an init method on the prototype object in the constructor Directly return new Guoqi.prototype.init();

You can successfully hide the new keyword,

But the most critical step is to set Guoqi.prototype.init.prototype = Guoqi.prototype

In this case, init All instances can use the methods on the Guoqi.prototype prototype

Finally, window.Guoqi = window.Q = Guoqi and hang Guoqi and Q as interfaces on the window to facilitate direct calls from the outside

use length as the prototype object An object, placed in the prototype, the default value is 0, ensure that the Q object is a pseudo array

Use the method of replacing the prototype object Guoqi.prototype = {}; Note: the constructor attribute should point to the constructor itself

For the convenience of our writing , you don’t need to carry the new keyword every time you create an instance. There is a little trick to enrich the init method. Multiple parameters can be passed in the Q object and need to be judged. If it starts with '

Otherwise (the DOM element is passed in), it will be used as a selector using the qsa method to get the object

If the string is passed in, then the situation will be different

If the DOM object is passed in selector.nodeType, then it is stored directly in the object this[0] = selector;this.length = 1; Make sure it is a pseudo array

If the Q object is passed in, selector.constructor .name = Guoqi (because Q objects are all created using the Guoqi constructor), if you directly return the function passed in by selector

, do not do the operation for the time being typeof selector === "function", such as the entry in jQuery If the function $(function(){})

passes in an array (pseudo-array), selector.length >=0, pass the value of the array into this in turn

Otherwise, it doesn’t matter what is passed in , this[0] = selector; this.length = 1;

This ensures that no matter what Q() passes in, we get a __pseudo array__

Add the function of the extension method extend( obj ) Pass in an object in extend

Guoqi.extend = Guoqi.prototype.extend = function(){ } Use the conjunction symbol to represent adding extend methods to the constructor and constructor prototype respectively

extend uses mixed inheritance The principle is to copy the function of obj to this object, (note: the two this points are different at this time)

Guoqi.extend({}) The method that extends the constructor is called a static method, as a tool method Use (each, map, next...)

to add the each method to the constructor. Please note: this points to the current object traversed, and once the return value is false, the traversal will stop; that is,

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

map method this points to window. Only when the return value is not undefined or null, will it be added to the returned new array. The elements that have been traversed, otherwise:

if( callback( array[i], i ) != undefined){ newArr.push( callback( array[i], i ) ) }; Finally __return newArr__;

Guoqi .prototype.extend({}) The method added to the instance is called an instance method. Each instance object can call the method added to the instance. Syntax: Q().each(function(){}); So to Transfer the each method to the Q instance object, such as appendTo, each, map

That is, Guoqi.fn.extend({ each : function(callback){ return Guoqi.each(this,callback); }});

Add core methods toArray, get

If an index is passed in, then the DOM element corresponding to the value of index is returned


Supports passing in negative numbers, then counting from the back to the front, for example: if -1 is passed in, then the object is length- 1 DOM element

toArray Function: Return the obtained Q object (pseudo array) as a true array of DOM elements

get(index) Function: If no index is passed in, then a true array of DOM elements will be returned Array

DOM operation module

First define a function parseHtml(str){} in the sandbox to create a Q object based on the incoming tags

When creating, you can create div tags at will and put what we want to create Tag, added to div, div.innerHTML = str;

Then just return the childNodes of the div, but __note__, the length of the childNodes will change as we render it on the page

So, we need to create an empty array and put the DOM elements in the childNodes , and put them into the array in turn,

Then, the parseHtml function returns a __real array__, which is called under the '

appendTo(selector) instance Method function: Append this object to the child element of the selector

This method also makes a judgment based on the incoming object, DOM element, DOM object, Q object, pseudo array... (so the premise is the init method Perfect)

After perfection, first convert the selector into a Q object ---> Q(selector)

Create an array objQ, and a variable tempDom, (for the sake of the following return value)

Consider this and selector Nested loops, and clone this[i], pay attention to the number of clones, ensure that the ontology is the last one, do not clone too many

, and constantly assign the value of this to tempDom to save it, tempDom = j === Q(selector).length-1 ? this[i]:this[i].cloneNode(true)

Add tempDom to the array objQ again,

Consider the return value issue, our return value should be, All this elements added to the selector, including cloned ones, so the elements in objQ are the values ​​we should return

In this case, our chain programming chain will be destroyed, so we have to create a_ _end()__ method to return the previous chain object

Here, we need to save the current this so that the end() method can be called to return

that is: convert objQ into a Q object and give it Add a preObj attribute pointing to this, and then return Q(obj)

end() instance method. When the chain is destroyed, call it to find the upper level chain (only one level can be found)

us The above work has been very substantial, so the end() method only needs to return this.preObj || this ; (Note: If there is no previous level, return yourself)

pushStack() instance method, which is the appendTo method Later, the content of the upper-level object is saved. Because there may be many methods that will be damaged by the chain, we encapsulate a method to achieve the effect of reuse. That is:

function pushStack(arr){ var newObjQ = Q( arr); newObjQ.preObj = this; return newObjQ; }

Note: Although this method will not be called directly by the instance, because it should be called by this in the appendTo method, the upper chain object of this can be found ( Although it can be implemented later by borrowing the call/apply method, it will be more troublesome);


That is to say, it cannot be directly placed in the sandbox as a private function (such as parseHtml). In this case, the upper-level object is window, and it is implemented More troublesome

prependTo append preprend remove... The implementation of instance methods is based on the same principle as appendTo

Implementation method: Originally it could be implemented using this.nextElementSibling, but there are compatibility issues

So we extended the Guoqi constructor The next(dom) method loops through the dom element and looks for its next node. If nodeType === 1, return dom directly; otherwise, return null;

that is,

implements this.each(function(){ this. parentNode.removeChild( this ); })

implement Q(selector).prependTo(this); return this; the chain will not be destroyed

implement Q(selector).appendTo(this); return this; the chain will not be destroyed Destroyed

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

Note: This method is the same as the appendTo method, the chain will be destroyed

prependTo function: this object is added to the selector object

append the child element of this object to add the selector object to the front

prepend the child element of this object to add the selector object to the front

remove method finds the parent node in this and deletes all this elements in it, including this itself The

next method finds the next element in this object (rather than all siblings). The chain will be destroyed

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

Then the next instance method only needs to return pushStack( this.map(function (v){ return Guoqi.next(v) }) );


filter(selector) method finds the same element for this instance object and selector


Prepare a ret empty array, for this and Guoqi(selector ) perform nested loop traversal. If this[i] == Guoqi(selector)[j], add it to the array ret

Finally, convert the ret array into an instance object and return it: return Guoqi(ret);

unique() function: remove identical elements

Prepare an empty array arr, call this.toArray() method, convert the instance object into a real array thisArr, and then loop through it

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

Then return Guoqi( arr );

children(selector): Function: If no parameters are passed in, all children will be found Element (only for the son generation), if parameters are passed in, the child element matching the selector will be found

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


Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn