简易版jquery
最近写了一个简易版的jquery github地址:https://github.com/jiangzhenfei/Easy-Jquery
完成的方法:
1.$('#id')
2.extend扩展方法,在JQuery或者原型上扩展更多的方法,该方法也是扩展jq插件的方法
3.pushStack方法,该方法是$('#id').find('div').end(),找到jq对象的关联对象和返回上一个对象的基础
4.实现一些基本的工具方法,$.each,$.each,$.map,$.grep,$.merge
5.实现获取自定义属性,获取和设置样式的方法
6.实现简易的Callback
7.实现简易的Defferd延迟对象
8.实现数据缓存Data
全部代码:
/** * 设置当前激活的tab页顺序 * * @param { selector } 指的是当前的选择器 * $(''#app li) 指的就是在id为app的元素内部所有的li集合,返回是数组 */ var $ = function(selector){ return new $.prototype.init(selector) } $.prototype={ length:0, constructor: $, init:function(selector){ //不存在selector if(!selector){ return this; } //是对象时候相当于触发domLoad if(this.isFunction(selector)){ this.ready(selector) return; } let doms = document.querySelectorAll( selector ) let i = doms.length; while( i-- ){ this[ i ] = doms[ i ] } return this; }, /*判断是否为对象 (简单对象)*/ isObject:function (value){ return Object.getPrototypeOf( value ) === Object.prototype }, /*判断是否为数组 */ isArray:function (value){ return Array.isArray( value ) }, isFunction(value){ return typeof value === 'function' }, /** * 最核心的方法,可以在原型上扩展其他方法,jq的·插件就是靠他扩展的 * @param {*} obj1 * @param {*} obj2 * 两个参数则是将后一个对象合并到前一个 * 一个参数代表将对象拷贝到$的原型或者实例上 */ extend(obj1,obj2){ var target; var clone; var copy; var src; target = obj2 ? obj1 : this; if(!obj2){ obj2 = obj1; } for(var name in obj2){ copy = obj2[name] src = target[name] if(typeof copy === 'object'){//如果需要复制的属性是对象类型 if(this.isObject(copy)){ clone = src && isObject(src) ? src : {} }else if(this.isArray(copy)){//当属性为数组的时候 clone = src && this.isArray(src) ? src : [] } target[name] = this.extend(clone,copy) }else{ if(copy){ target[name] = copy } } } return target; }, /** * 循环jq的元素,callback实现循环中需要的操作 */ each:function (callback){ for (var i = 0;i<this.length;i++){ callback(this[i]) } }, /** * 3种情况 * 1.css('width','200px') 设置一个 * 2.css({'width':'200px','height':'300px'}) 设置一群 * 3.css('width') 获取 */ css:function(key,value){ return $.access(this, key, value, function(elem,key,value){ //value存在就设置属性,不存在就获取属性 return value ? $.setStyle(elem,key,value) : $.getStyle(elem,key) }) }, /** * 3种情况 * 1.attr('data-id','name') 设置一个 * 2.attr({'data-key':'0000','data-id':'name'}) 设置一群 * 3.attr('data-id') 获取 */ attr(key,value){ return $.access(this, key, value, function(elem,key,value) { return value ? $.setAttr(elem,key,value) : $.getAttr(elem,key) }) }, /** *设置元素的width,没有参数且选择器选择元素唯一时候返回元素的width */ width:function(params){ if(params){ this.each(function(item){ item.style['width'] = params + 'px' }) }else if(!params && this.length===1){ return this.getStyle(this[0],'width') } }, /** * 入栈操作,栈的最上方的最先出来,返回一个新的jq对象,保存前一个 * $('#app').pushStack(document.querySelector('li')).css({background:'red'}) * 那么此时只是li颜色变化 * 使用end()方法,回到前一个jq对象 */ pushStack: function( elems ) { var ret = $.merge( this.constructor(), elems ); ret.prevObject = this; return ret; }, /** * 返回前一个jq对象,现在栈的底下一个栈 */ end: function() { if(this.prevObject){ return this.prevObject }else{ return this; } }, /** * 循环jq对象自身,对每个匹配到的元素做操作 */ map: function( callback ) { return this.pushStack( $.map( this, function( elem, i ) { return callback.call( elem, i, elem ); } ) ); }, ready: function(event){ var domReady = new Defferd() domReady.push(event) document.addEventListener('DOMContentLoaded', function() { domReady.done() }) } } $.prototype.init.prototype = $.prototype; $.extend = $.prototype.extend; /** * 解析html */ $.extend({ parseHTML:function(ele){ //匹配<li>111</li><li>222</li> var regx = /<(\w+)>(\w+)<\/\w+>/g; var obj={} var i=0; while(true){ var item = regx.exec(ele) console.log(item) if(!item){ break }else{ obj[i] = document.createElement(item[1]) obj[i].innerHTML = item[2] i++; } } return obj; } }) $.extend({ grep: function( elems, callback, inv ) { var ret = [], retVal; inv = !!inv;//转成真正的布尔类型,对否反选的操作 for ( var i = 0, length = elems.length; i < length; i++ ) { // 这里callback的参数列表为:value, index,与each的习惯一致 retVal = !!callback( elems[ i ], i ); // 是否反向选择 if ( inv !== retVal ) { ret.push( elems[ i ] ); } } return ret; }, }) /** * jq真正的遍历方法,上面的each是我写的比较简单 * 可以将每个回调函数的this指向遍历的元素本身,于是可以在回调内部使用this */ $.extend({ jqEach: function( object, callback) { var name; for ( name in object ) { //将this的指向指为每个遍历的元素对象 //所以在遍历dom元素时候,内部可以使用$(this)来生成相应的jq对象 if ( callback.call( object[ name ], name, object[ name ] ) === false ) { break; } } }, }) /** * map方法,返回的是对数组的每一项做处理,‘ * 返回一个新的数组为处理后的数组 * jq内部可以处理对象,这里做了简化不能处理对象,只是处理数组 */ $.extend({ map: function( arr, callback) { var i = 0; var ret = []; var length = arr.length; for ( ; i < length; i++ ) { // 执行callback,参数依次为value, index, arg value = callback( arr[ i ], i); // 如果返回null,则忽略(无返回值的function会返回undefined) if ( value != null ) { ret[ ret.length ] = value; } } return ret; } }) $.extend({ merge: function( first, second ) { var len = +second.length, j = 0, i = first.length; for ( ; j < len; j++ ) { first[ i++ ] = second[ j ]; } first.length = i; return first; }, }) $.extend({ access: function( elems, key,value ,fn) { //console.log(key) var i = 0; var length = elems.length; /** * key是对象 * { * width:200px; * background:'red' * } */ if($.prototype.isObject(key)){ for (var k in key){ console.log(k,key[k]) $.access(elems,k,key[k],fn) } return elems; } /** * key ,value都存在,给每个元素执行方法,比如css每个元素绑定样式 */ if(value){ for(;i<length;i++){ fn(elems[i],key,value) } return elems } /** * value不存在就是获取,比如获取样式 */ return length > 0?fn(elems[0],key) : undefined; }, }) $.extend({ //获取样式 getStyle:function (element, property){ var proValue = null; if (!document.defaultView) { proValue = element.currentStyle[property]; } else { proValue = document.defaultView.getComputedStyle(element)[property]; } return proValue; }, //设置样式 setStyle: function (ele,key,value) { ele.style[key] = value; }, //获取自定义属性 getAttr: function (element, property) { return element.getAttribute(property); //获取 }, //设置自定义属性 setAttr: function (ele,key,value) { ele.setAttribute(key,value) } }) /** * 延迟对象 * push是在对象放入方法 * done是执行该对象队列中所有的方法 */ // function Defferd(){ // this.events = [] // this.push = function(event){ // this.events.push(event) // } // this.done = function(){ // for(var i = 0; i < this.events.length; i++){ // this.events[i]() // } // } // } /** * 回调对象实现 -- 在很多时候需要控制一系列的函数顺序执行。那么一般就需要一个队列函数来处理这个问题 * list保存回调函数 * fire执行回调函数 * 与正真的jq callback差距很大,包括可以停止,可以清除等等 */ $.extend({ Callback: function (option) { option = option || {} var list = [] var self = { add: function ( fn ) { if( Object.prototype.toString.call( fn )==='[object Function]' ){ /* 不存在unique,直接push 存在unique,看后面是否成立 */ if( !option.unique || !list.includes(fn)){ list.push( fn ) } } console.log(this) return this; }, fire: function ( args ){ list.forEach( function ( fn ) { fn( args ) } ) } } return self; } }) /** * 延迟对象 * var d = $.Defferd() * d.done(function(e){}).fail(function(e){}) * d.resolve('haha') * */ $.extend({ Defferd: function () { var tuples = [ ['resolve','done',Callback()], ['reject','fail',Callback()] ] var defferd = {} tuples.forEach( function ( tuple ) { var list = tuple[2] defferd[tuple[1]] = list.add defferd[tuple[0]] = list.fire } ) return defferd; } }) $.extend({ error: function ( msg ) { throw new Error( msg ) } }) /** * 数据缓存 */ function Data(){ this.cache = {}//存储数据的地方 this.expando = 'JQuery'+ Math.random() } Data.uid = 1; //获取元素对应的key,key是元素跟this.cache的映射 Data.prototype.key = function( owner ){ var unlock = owner[ this.expando ] if( !unlock ){//key不存在则给元素加自定义属性'JQuery'+ Math.random() = ‘key’ unlock = Data.uid++ owner[ this.expando ] = unlock; } if( !this.cache[ unlock ] ){//在cache中的key不存在则分配一个空对象 this.cache[ unlock ] = {} } return unlock;//返回对应的key } /** * * @param {HTMLEle} owner html元素 * @param {string} key 存储的key * @param {string} value 存储的value */ Data.prototype.set = function( owner,key,value ){ var unlock = this.key( owner ) var cache = this.cache[ unlock ] if( typeof key === 'string'){ cache[ key ] = value } } /** * * @param {HTMLEle} owner html元素 * @param {string} key 获取的key,可以不存在则返回该元素所有缓存 */ Data.prototype.get = function( owner,key ){ var unlock = this.key( owner ) var cache = this.cache[ unlock ] if( key ){ return cache[ key ] } return cache; } /** * * @param {HTMLEle} owner html元素 * @param {string} key 设置或者获取的key,跟value一同不存在则返回该元素所有缓存 * @param {string} value 设置的value,不存早则就是内部调用get,存在内部调用set */ Data.prototype.access = function( owner,key,value ){ if( value ){ return this.set( owner, key, value ) } return this.get( owner, key ) } var data_user = new Data() //给JQ添加该工具方法 $.extend({ data: data_user.access.bind( data_user ) }) $.extend({ error: function ( msg ) { throw new Error( msg ) } })
出处:https://www.cnblogs.com/zhenfei-jiang/p/9546351.html
======================================================
前面的话
虽然jQuery已经日渐式微,但它里面的许多思想,如选择器、链式调用、方法函数化、取赋值合体等,有的已经变成了标准,有的一直影响到现在。所以,jQuery是一个伟大的前端框架。前端世界日新月异,由于实在是没有时间去精读源码,于是自己封装一个简易版本的jQuery,来梳理jQuery的核心思路
基本构架
由于火柴的英文是match,应该将这个简单框架称为mQuery。使用面向对象的写法来写mQuery,构造函数是Mquery(),调用$()方法,将根据Mquery()构造函数,创建一个实例对象
//构造函数 function Mquery(arg){}
function $(arg){ return new Mquery(arg); }
jquery几大特征:
1、通过$()选择的元素都是一个集合,即使仅仅是一个元素
因此,创建一个elements属性为一个数组,去接收获取的元素
//构造函数 function Mquery(arg){ //保存所选择的元素 this.elements = []; }
2、链式调用
所以,原型函数要返回this,以实现链式调用的效果
$函数
$函数根据参数类型的不同,用途也不同
1、参数为函数时,则直接运行
$(function(){ console.log(1) })
2、参数为对象时,则把DOM对象转换为$对象
$(document.body)
3、参数为字符串时,则根据字符串选择出元素,并转换为$对象
$('#box')
下面根据以上三个分类,来编写Mquery构建函数
//事件绑定兼容写法 function _addEvent(target,type,handler){ if(target.addEventListener){ target.addEventListener(type,function(e){ //如果事件函数中出现 return false;则阻止默认事件和阻止冒泡 if(typeof handler == 'function' && handler() === false){ e.preventDefault(); e.cancelBubble = true; } },false); }else{ target.attachEvent('on'+type,function(event){ if(typeof handler == 'function' && handler() === false){ event.cancelBubble = true; event.returnValue = false; } return handler.call(target,event); }); } } //将类数组转换成数组 function _toArray(arrayLike){ return Array.prototype.slice.call(arrayLike); }
//构造函数 function Mquery(arg){ //保存所选择的元素 this.elements = []; switch(typeof arg){ //当参数是函数时,如$(function(){})(),直接运行里面的代码 case 'function': _addEvent(window,'load',arg); break; //当参数是字符串时,选择元素 case 'string': this.elements = _toArray(document.querySelectorAll(arg)); break; //当参数是DOM对象时,将DOM对象转换为$对象 case 'object': if(arg.constructor == Array){ this.elements = arg; }else{ this.elements.push(arg); } break; } }
HTML、CSS及特性设置
下面来介绍常用的HTML、CSS及特性设置
【HTML】
对于文本内容来说,一般地,有三种方法:html()、text()和val()。本文只实现最常用的html()方法
当html()方法没有参数时,表示获取内容;有一个参数时,表示设置内容
//HTML获取与设置 Mquery.prototype.html = function(str){ //设置 if(str){ for(var i = 0; i < this.elements.length; i++){ this.elements[i].innerHTML = str; } //获取 }else{ return this.elements[0].innerHTML; } return this; }
【CSS】
对于CSS来说,有两种参数格式:一种是json格式,一种是字符串格式
当第一个参数为对象时,则判断为json格式,否则为字符串格式
对于字符串格式来说,只有一个参数时,为获取样式,两个参数时,为设置样式
获取样式时,仅获取当前集合中第0个元素的样式;设置样式时,则设置当前集合中所有元素的样式
//获取计算样式兼容写法 function _getCSS(obj,style){ if(window.getComputedStyle){ return getComputedStyle(obj)[style]; } return obj.currentStyle[style]; }
//CSS获取与设置 Mquery.prototype.css = function(attr,value){ //如果是对象的形式,以对象的形式设置 if(typeof attr == 'object'){ for(var att in attr){ for(var j = 0; j < this.elements.length; j++){ this.elements[j].style[att] = attr[att]; } } //如果不是对象的形式 }else{ //设置 if(arguments.length == 2){ for(var i = 0; i < this.elements.length; i++){ this.elements[i].style[attr] = value; } //获取 }else if(arguments.length == 1){ return _getCSS(this.elements[0],attr) } } return this; }
【attr】
特性设置与获取的思路与CSS类似,只是方法变成了setAttribute()和getAttribute()
//attr获取与设置 Mquery.prototype.attr = function(attr,value){ //如果是对象的形式 if(typeof attr == 'object'){ for(var att in attr){ for(var j = 0; j < this.elements.length; j++){ this.elements[j].setAttribute(att,attr[att]); } } //如果不是对象的形式 }else{ //设置 if(arguments.length == 2){ for(var i = 0; i < this.elements.length; i++){ this.elements[i].setAttribute(attr,value); } //获取 }else if(arguments.length == 1){ return this.elements[0].getAttribute(attr); } } return this; }
事件绑定
【on】
在jQuery中,最常用的事件绑定方法就是on方法。在on方法中要特别注意的是this的绑定,由于函数fn中的this实际上是window,所以应该将fn的this绑定到当前元素
//事件绑定 Mquery.prototype.on = function(eventType,fn){ for(var i = 0; i < this.elements.length; i++){ _addEvent(this.elements[i],eventType,fn.bind(this.elements[i)); } return this; }
【click和hover】
click方法是一个简写方法
Mquery.prototype.click = function(fn){ this.on('click',fn); return this; }
hover方法是mouseover和mouseout的合成方法
Mquery.prototype.hover = function(fnOver,fnOut){ this.on('mouseover',fnOver); this.on('mouseout',fnOut); return this; }
【return false】
在jQuery中,使用return false可以同时阻止默认行为和阻止冒泡
//事件绑定兼容写法 function _addEvent(target,type,handler){ if(target.addEventListener){ target.addEventListener(type,function(e){ //如果事件函数中出现 return false;则阻止默认事件和阻止冒泡 if(typeof handler == 'function' && handler() === false){ e.preventDefault(); e.cancelBubble = true; } },false); }else{ target.attachEvent('on'+type,function(event){ if(typeof handler == 'function' && handler() === false){ event.cancelBubble = true; event.returnValue = false; } return handler.call(target,event); }); } }
其他设置
jQuery的功能非常强大。下面选择一些常用功能进行实现
【显示隐藏】
//隐藏 Mquery.prototype.hide = function(){ for(var i = 0; i < this.elements.length; i++){ //保存当前元素的display值 this.elements[i].displayValue = this.elements[i].style.display; this.elements[i].style.display = 'none'; } return this; } //显示 Mquery.prototype.show = function(){ for(var i = 0; i < this.elements.length; i++){ this.elements[i].style.display = this.elements[i].displayValue; //删除保存的元素的display值 delete this.elements[i].displayValue; } return this; }
【插件设置】
$.extend = function(json){ for(var attr in json){ $[attr] = json[attr]; } }; $.fn = {}; $.fn.extend = function(json){ for(var attr in json){ Mquery.prototype[attr] = json[attr]; } };
【索引设置】
//根据索引选择元素 Mquery.prototype.eq = function(number){ return $(this.elements[number]); } //根据元素获取索引 Mquery.prototype.index = function(){ var elements = this.elements[0].parentNode.children; for(var i = 0; i < elements.length; i++){ if(elements[i] === this.elements[0]){ return i; } } }
【子级筛选】
//筛选出当前匹配的元素集合中每个元素的后代 Mquery.prototype.find = function(str){ var arr = []; for(var i = 0; i < this.elements.length; i++){ Array.prototype.push.apply(arr,this.elements[i].querySelectorAll(str)); } return $(arr); }
完整源码
下面是mQuery的完整源码
//事件绑定兼容写法 function _addEvent(target,type,handler){ if(target.addEventListener){ target.addEventListener(type,function(e){ //如果事件函数中出现 return false;则阻止默认事件和阻止冒泡 if(typeof handler == 'function' && handler() === false){ e.preventDefault(); e.cancelBubble = true; } },false); }else{ target.attachEvent('on'+type,function(event){ if(typeof handler == 'function' && handler() === false){ event.cancelBubble = true; event.returnValue = false; } return handler.call(target,event); }); } } //获取计算样式兼容写法 function _getCSS(obj,style){ if(window.getComputedStyle){ return getComputedStyle(obj)[style]; } return obj.currentStyle[style]; } //将类数组转换成数组 function _toArray(arrayLike){ return Array.prototype.slice.call(arrayLike); } //构造函数 function Mquery(arg){ //保存所选择的元素 this.elements = []; switch(typeof arg){ //当参数是函数时,如$(function(){})(),直接运行里面的代码 case 'function': _addEvent(window,'load',arg); break; //当参数是字符串时,选择元素 case 'string': this.elements = _toArray(document.querySelectorAll(arg)); break; //当参数是DOM对象时,将DOM对象转换为$对象 case 'object': if(arg.constructor == Array){ this.elements = arg; }else{ this.elements.push(arg); } break; } } //根据索引选择元素 Mquery.prototype.eq = function(number){ return $(this.elements[number]); } //根据元素获取索引 Mquery.prototype.index = function(){ var elements = this.elements[0].parentNode.children; for(var i = 0; i < elements.length; i++){ if(elements[i] === this.elements[0]){ return i; } } } //筛选出当前匹配的元素集合中每个元素的后代 Mquery.prototype.find = function(str){ var arr = []; for(var i = 0; i < this.elements.length; i++){ Array.prototype.push.apply(arr,this.elements[i].querySelectorAll(str)); } return $(arr); } //CSS获取与设置 Mquery.prototype.css = function(attr,value){ //如果是对象的形式,以对象的形式设置 if(typeof attr == 'object'){ for(var att in attr){ for(var j = 0; j < this.elements.length; j++){ this.elements[j].style[att] = attr[att]; } } //如果不是对象的形式 }else{ //设置 if(arguments.length == 2){ for(var i = 0; i < this.elements.length; i++){ this.elements[i].style[attr] = value; } //获取 }else if(arguments.length == 1){ return _getCSS(this.elements[0],attr) } } return this; } //attr获取与设置 Mquery.prototype.attr = function(attr,value){ //如果是对象的形式 if(typeof attr == 'object'){ for(var att in attr){ for(var j = 0; j < this.elements.length; j++){ this.elements[j].setAttribute(att,attr[att]); } } //如果不是对象的形式 }else{ //设置 if(arguments.length == 2){ for(var i = 0; i < this.elements.length; i++){ this.elements[i].setAttribute(attr,value); } //获取 }else if(arguments.length == 1){ return this.elements[0].getAttribute(attr); } } return this; } //HTML获取与设置 Mquery.prototype.html = function(str){ //设置 if(str){ for(var i = 0; i < this.elements.length; i++){ this.elements[i].innerHTML = str; } //获取 }else{ return this.elements[0].innerHTML; } return this; } //隐藏 Mquery.prototype.hide = function(){ for(var i = 0; i < this.elements.length; i++){ //保存当前元素的display值 this.elements[i].displayValue = this.elements[i].style.display; this.elements[i].style.display = 'none'; } return this; } //显示 Mquery.prototype.show = function(){ for(var i = 0; i < this.elements.length; i++){ this.elements[i].style.display = this.elements[i].displayValue; //删除保存的元素的display值 delete this.elements[i].displayValue; } return this; } //事件绑定 Mquery.prototype.on = function(eventType,fn){ for(var i = 0; i < this.elements.length; i++){ _addEvent(this.elements[i],eventType,fn.bind(this.elements[i])); } return this; } //click简写 Mquery.prototype.click = function(fn){ this.on('click',fn); return this; } //鼠标移入移出 Mquery.prototype.hover = function(fnOver,fnOut){ this.on('mouseover',fnOver); this.on('mouseout',fnOut); return this; } $.extend = function(json){ for(var attr in json){ $[attr] = json[attr]; } }; $.fn = {}; $.fn.extend = function(json){ for(var attr in json){ Mquery.prototype[attr] = json[attr]; } }; function $(arg){ return new Mquery(arg); }
实际应用
下面使用mQuery来实现一个简单的效果
<style> div { width:60px; height:60px; margin:5px; float:left; } </style> <span id="result"></span> <div style="background-color:blue;"></div> <div style="background-color:rgb(15,99,30);"></div> <div style="background-color:#123456;"></div> <div style="background-color:#f11;"></div> <script src="mQuery.js"></script> <script> $("div").click(function(){ $("#result").html("背景颜色是 " + $(this).css("background-color")); }) </script>
点击不同颜色的元素块,将在右侧显示具体的颜色值
关注我】。(●'◡'●)
如果,您希望更容易地发现我的新博客,不妨点击一下绿色通道的【因为,我的写作热情也离不开您的肯定与支持,感谢您的阅读,我是【Jack_孟】!
本文来自博客园,作者:jack_Meng,转载请注明原文链接:https://www.cnblogs.com/mq0036/p/11565263.html
【免责声明】本文来自源于网络,如涉及版权或侵权问题,请及时联系我们,我们将第一时间删除或更改!