jQuery
jQuery原理:
jQuery通过立即执行函数封闭作用域,将全局作用域window和所有的方法通过参数的方式传入到这个模块当中,形式大致是这样的:
1 (function(global, factory){ 2 factory(global); 3 })(window, function(window){ 4 // 所有的功能代码都放在这里面 5 })
这种倒置代码运行顺序的方式是立即执行函数(IIFE)的一种变化的用途,将需要运行的函数放在第二位,在立即执行函数执行之后当作参数传递进去,这种模式也被称为UMD(Universal Module Definition)。
init函数:
在jQuery当中init函数的作用是将传入各种形式的参数构造成jQuery对象,例如我们使用$(‘#demo‘)来获取DOM元素的时候,就是将id为demo的dom元素转变成jQuery对象,下面是init 函数的实现转变过程的实现:
1 // 这只是一个简单的模拟实现而已 2 (function(global, factory){ 3 factory(global); 4 })(window, function(){ 5 // 将jQuery和$暴露到全局作用域下 6 window.jQuery = window.$ = jQuery; 7 // 为了实现无new操作,jQuery就借用了一下init函数的劳动成果 8 function jQuery(str){ 9 return new jQuery.prototype.init(str); 10 } 11 // 实际上的功能函数,实现创建jQuery对象。 12 jQuery.prototype.init = function(str){ 13 this[0] = document.getElementById(str); 14 this.length = 1; 15 } 16 }) 17 console.log(jQuery(‘demo‘));//div#demo被选出
可以看出当我们使用$( )来选择dom的元素的时候,其实并不是jQuery这个函数在工作,而是他背后的init的功劳, 并且还将这个init函数收入麾下,放在了自己的prototype上面,但是问题出来了,虽然抢占了init构造函数的功劳,但是我们知道当我们使用jQuery对象的时候,要使用的是对象上面的方法啊,但是这里的init.prototype是执行Object的,上面并没有自定义的方法。所以jQuery做出了更加无耻的事情,就是强行让init构造函数的prototype指向自己prototype。
jQuery.prototype.init.prototype = jQuery.prototype
终于jQuery达到了自己的目的,为了实现不new构造出对象,借用了一个init函数,为了构造出来的对象能使用自己原型上面的方法,将init构造函数的prototype指向自己的prototype上面。
init函数的选择原理:
在jQuery当中,可以像css选择器一般的选择dom元素,使用方法很灵活,是jQuery内部对传入的参数做了很大的文章
init方法有三个参数: $( ‘ ‘, ‘ ‘, ‘ ‘);
selector : $() 当中的第一个参数
context : $() 当中的第二个参数
root : $() 当中的第三个参数(默认的是$(document))
实现机理:
1. 如果 $( ) 中第一个参数传递的是 空 ,直接返回本身 ---> $( ) / $(null) / $(undefined) / $(false) 2. 如果 $( ) 中第一个参数传递的是字符串 (1) 如果字符串是一个标签且长度大于或等于三 ---> $(‘<div></div>‘) 创建标签的模式 传递到一个长度为三match数组的第二位 ---> match[‘‘,‘<div></div>‘,‘‘]
(2) 否则进入一个正则匹配 *如果字符串中含有合法的标签 --->$(‘<div/>123‘)根据正则表达式提取出合法的标签, 传入到match数组的第二位,match[‘‘, ‘<div/>‘, ‘‘] *如果字符串是以‘#‘开头,证明是一个id,且后面跟着一个合法的字符串 --->$(‘#id‘) 根据正则表达式提取这个合法的字符串,传递到match数组的第三位 ---> match[‘‘,‘‘,‘id‘] (3) 如果match数组不为空 *若match数组第二位有值,生成dom,如果$()有第二个参数,且第二个参数是一个对象, 遍历第$() 两个参数,根据键名和键值,添加属性或者内容 ---> $(‘<div>1234‘,{id: ‘id‘, html: ‘123‘}) *若match数组有第三位值,根据元素的getElementById方法选择dom,包装成jQuery对象 (4) 否则如果match数组为空,且$()没有第二个参数,或者第二个参数为jQuery的时候,--->$(‘li‘) / $(‘li‘, $(‘ul‘)) 在document或者第二个参数下,根据$(xxx).find()方法寻找这个字符串。 (5) 如果match数组为空,且$()第二个参数为字符串或者原生的dom的时候, ---> $(‘li‘,‘ul‘) / $(‘li‘, dom) 调用自身的init方法,把第二个参数包装成jQuery对象,在这个对象下面根据$(xxx).find()方法寻找这个字符串 3. 如果 $() 中第一个参数传递的是原生的dom节点,直接包装成jQuery对象 ---> $(this) // 通过nodetype来判断是不是dom 4. 如果 $() 第一个参数的传递的是一个函数,将参数传递给document.ready() 5. 如果什么也不是,就包装成一个数组返回。
extend方法:
该方法用来向jQuery原型上面或者jQuery对象上面添加方法,没有形参,方法根据传入的参数来判断进行了什么操作。
这个函数本质是合并对象的方法,只是当只传一个对象的时候就合并到jQuery对象或者jQuery原型上面去了。并且根据第一个参数的boolean值来确定是通过深度克隆还是浅克隆来实现合并过程。
这个方法同时定义在jQuery和jQuery.fn(即jQuery.prototype)上面
jQuery.extend = jQuery.fn.extend = function(){ //... }
1. 当参数中只有一个对象的时候 (1).如果第一个参数是true(默认是false并且不写)那么合并操作的目标对象就是this并且实现了深克隆(这里调用这个方法的可以是jQuery.fn或者jQeury)。 (2).如果第一个参数没有传,那么合并操作的的目标对象是this并且是浅克隆。 jQuery和jQuery.fn上面都定义了extend方法,这里通过函数内部this来区分是向那个对象中添加方法 2. 如果参数中有多个对象 (1). 我们选取第一个对象为合并操作的目标对象(当第一个对象是boolean(默认是false并且可以不写)),后面的对象依次向该对象中合并键值对 ①. 第一个参数为true的时候,采用深克隆 ②. 第一个是对象(或者false)的时候,采用浅克隆
(源码的判断方式并非如此,这只是个人觉得好理解的方式)
深克隆:
function deepClone(target, origin){ var target = target || {}; for(prop in origin){ if(origin.hasOwnProperty(prop)){ if (typeof origin[prop] === ‘object‘) { target[prop] = (Object.prototype.toString.call(origin[prop]) === ‘[Array object]‘ ? [] : {}); deepClone(target[prop], origin[prop]); } else { target[prop] = origin[prop]; } } } return target; }