2017-04-17 jQuery 原理
2017-04-17 本文已影响0人
GodlinE
jQuery 的内部实现
-
课程目标
-
需要掌握
了解常用方法的使用
掌握常用方法的底层实现逻辑
学会根据一个目标需求,来根据自己的思想编写出大致实现
了解一些编写框架的技巧,比如为什么用闭包,为什么传递 window 参数
了解一些机制,比如插件机制,如何扩展一些方法或者属性
要求掌握面向对象的思想去解决问题,熟练了解原型, this 指针等概念 -
整体是一个执行的闭包
这样写的主要目的是为了防止内部一些功能被污染 -
传递 window 参数
是为了方便亚索,以及提高检索效率 -
内部接收 undefined
是为了放置,外界修改 undefined 所代表的含义 -
内部的参数,如何让外界可以访问
可以通过给 window 对象,动态的增加属性,暴露给外界调用
(function(){
//1.定义构造函数
var szjQuery = function(selector){
//内部 new 了一个对象返回给外界,让外界使用方便,没必要再创建
return new szjQuery.prototype.init(selector);
}
//2.修改函数的原型
//以后,可以在这个位置扩展一些属性,或者方法
//注意:这里有个比较重要的方法 init 方法,基本上所有的创建 jQuery 对象初始化的逻辑,都是在这个函数里面进行处理的
szjQuery.prototype = {
szjQuery:'1.1.0',
selector:'',
length:0,
constructor:szjQuery,
init:function(){
//this.length = 0;
//在这里处理 selector 传进来的参数
//1.特殊字符 ‘’ null undefined NaN 0 false
if(!selector){
return this;
}
//2.判断是否是字符串
if($.isString(selector)){
//2.1要不就是代码片段,去空格处理
var result = $.trimString(selector);
if($.isHTML(result)){
var tag = document.createElement('div');
tag.innerHTML = selector;
var firstChildren = tag.children;
//call apply 作用重新控制 this 指向,此处可以使用遍历然后控制 this[i] = firstChildren[i] 来实现
[].push.apply(this,firstChildren);
return this;
}
//2.2要不就是选择器
var tags = document.querySelectorAll(selector);
[].push.apply(this,tags);
return this;
}
//3.判定是否是伪数组/真数组
if($.isWindow(selector) && $.isLikeArray(selector)){
[].push.apply(this,selector);
return this;
}
//4.判断是否是一个函数
if($.isFunction(selector)){
$.ready(selector);
}
//其他
//DOM 基本对象 基本数据类型 1234
this[0] = selector;
this.length = 1;
return this;
}
};
//3.创建一个快速批量创建静态方法/实例方法的方法
//因为内部的 this ,是动态指向调用者的
//所以,可以借助这个特性,来动态的给对象或者方法添加方法
szjQuery.extend = szjQuery.prototype.extend = function(funDic){
for(var key in funDic){
this[key] = funDic[key];
}
}
//4.重新修改 init 函数的原型指向,避免方法调用不到的问题
//此处必须了解以下几点
//4.1每个函数都有对应的原型对象
//4.2通过什么构造函数,创建出来的实例对象,这个对象的 __proto__ 就指向该构造函数的原型对象
//4.3当调用一个对象方法的时候,是根据 __proto__ 来进行查找方法的
//4.4所以,当一个对象找不到方法,应该注意查看该对象的构造函数的
//4.5注意,任何一个函数的原型,都可以修改
szjQuery.prototype.init.prototype = szjQuery.prototype;
//通过 window 对象,将内部对象暴露给外界
//因为,基本上,所有的功能都是依附于 jQuery 对象存在的
//所以,可以jQuery 对象给外界,然后其他的信息,会被间接的暴露出去
window.szjQuery = window.$ = szjQuery;
//6.扩展静态方法
//工具类,判断类的是否是字符串,是否是对象等等
szjQuery.extend({
//是否是字符串方法
'isString':function(){
return typeof str === 'string';
},
//可以压缩字符串的首尾空格
//此处注意兼容,以及正则表达式的使用
'trimString':function(str){
if(str.trim){
return str.trim();
}
return str.replace(/^\s+|\s+$/g, '');
},
//判断是否是窗口过对象
'isWindow': function(w){
return window.window !== w;
},
//是否是伪数组
'isLikeArray':function(){
return (this.isObject(arr) && 'length' in arr && (arr.length - 1) in arr);
},
//是否是对象
'isObject':function(obj){
return typeof obj === 'object';
},
//是否是代码片段
//注意空格的容错处理
'isHTML':function(html){
return html.charAt(0) === '<' && html.charAt(html.length - 1) === '>' &&html.length > 3
},
//是否是函数
'isFunction':function(fun){
return typeof fun === 'function';
},
//是否是 DOM 对象
'isDOM':function(dom){
if('nodeType' in dom){
return true;
}
return false;
},
//入口函数的处理
//需要注意执行时机,以及执行次数
'ready':function(func){
if(!$.isFunction(func)){
return;
}
//1.判断当前文档是否已经加载完毕
//直接执行
//document.readyState 可以兼容当前所有主流浏览器
var state = document.readyState;
if(state === 'complete'){
func();
return;
}
//2.如果没有加载完毕
//监听 DOM 树建立完成
// IE9+
if(document.addEventListener){
document.addEventListener('DOMContentloaded',func);
return;
}
// IE8
document.attachEvent('onreadystatechange',function(){
var state = document.readyState;
if(state === 'complete'){
func();
}
});
}
});
//扩展实例方法
//数组相关的一些方法
//比如转换称为数组,遍历,切割数组等等
//此处需要掌握,数组操作的常用方法的内部实现
szjQuery.prototype.extend({
//将伪数组,转换成为真数组
'toArray':function(){
var result = [].slice.call(this);
return result;
//
// begin = begin === undefined ? 0 : begin;
// end = (end === undefined || end > this.length) ? this.length: end;
//
// var tmpArr = [];
// for (var i = begin; i < end; i++) {
// tmpArr.push(this[i]);
// }
//
// return tmpArr;
},
//7.2获取某个一个 DOM 元素
//注意正负索引值的处理
'get':function(index){
if(index === undefined){
return this.toArray();
}
index = index >=0?index:index + this.length;
return this[index];
// if (index >= 0) {
// return this[index];
// }
// // -1 +
// index = index + this.length;
// return this[index];
},
//7.3获取指定的 DOM 元素,包装的 jQuery 对象
//可以考虑,借助其他已经实现过的方法,进行处理
//因为,内部的逻辑大致和 get 方法类似
'eq':function(index){
if(index === undefined){
return $('');
}
return $(this.get(index));
},
//7.4获取第一个的 DOM 元素,包装的 jQuery 对象
//可以考虑,借助其他已经实现国的方法,进行处理
//因为,内部的逻辑,大致和 get 方法类似
'first':function(){
return this.eq(0);
},
//7.5 获取最后一个的 DOM 元素,包装 jQuery 对象
//可以考虑,借助其他已经实现的方法,进行处理
//因为,内部的逻辑,大致和 get 方法类似
'last':function(){
return this.eq(-1);
},
//[].splice 获取的是方法实现
//7.6一定要注意,jQuery 对象里面的,这些方法的实现,其实和数组的这些方法实现一模一样
//所以,此处,可以直接把数组对应方法的实现放在这里
//这样,到时候,外界调用的时候,就会按照相同的操作数组的逻辑,来操作 jQuery 对象
'splice':[].solice,
'push':[].push,
'sort':[].sort,
'each':function(func){
$.each(this,func);
},
'reverse':function(){
var result = [];
for(var i = this.length - 1 ; i >= 0;i--){
result.push(this[i]);
}
return $(result);
}
});
//8.静态方法
//数组相关
szjQuery.extend({
//8.1 遍历数组,或者伪数组,或者对象
//内部注意不同类型的处理
//还有就是,此方法,支持对象方法,和静态方法
//一般碰到这种情况,我们优先开发静态方法,然后再在实例方法中,调用静态方法实现即可
'each':function(obj,func){
if($.isLikeArray(obj)){
for(var i = 0;i < obj.length;i++){
//func(i,obj[i]);
var isContinue = func.call(obj[i],i,obj[i]);
//isContinue = isContinue !== undefined ? isContinue : true;
if(isContinue === undefined){
isContinue = true;
}
if(!isContinue){
return;
}
}
return;
}
//不是伪数组
if($.isObject(obj)){
for(var key in obj){
//func(key,obj[key]);
var isContinue = func.call(obj[key],key,obj[key]);
isContinue = isContinue !== undefined ? isContinue:true;
if(!isContinue){
return;
}
}
}
},
//8.2可以将一个字符串的空格,都给亚索,然后把里面的有效元素,存储到一个数组中
//可以用来做一些格式化的操作
'trimSpaceArrayWithStr':function(str){
var resultArray = [];
var temp = str.split(' ');
for(var i = 0 ; i < temp.length;i++){
var value = temp[i];
if(value.length > 0){
resultArray.push(value);
}
}
return resultArray;
}
});
//DOM 操作的相关方法
//注意:如果想要仿照一个功能
//先对这个功能进行充分的测试
//报错参数 参数个数 参数类型 返回值等信息
szjQuery.prototype.extend({
//9.1 清空 DOM 对象里面的内容
'empty':function(){
this.each(function(){
//index value value
//this 就是每一个 DOM 对象
this.innerHTML = '';
});
return this;
},
//9.2 删除指定的 DOM 对象
'remove':function(){
this.each(function(){
//this 是每一个 DOM 对象
this.parentNode.removeChild(this);
});
return this;
},
//9.3获取指定元素的 html 内容
'html':function(content){
if(content === undefined){
return this.get(0).innerHTML;
}
this.each(function(){
this.innerHTML = content;
});
return this;
},
//9.3 获取指定元素的 text 内容
'text':function(content){
if(content === undefined){
var resultStr = '';
this.each(function(){
resultStr += this.innerText;
});
return resultStr;
}
if($.isFuntion(content)){
this.each(function(index,value){
this.innerText = content(index,this.innerText)
});
return this;
}
this.each(function(){
this.innerText = content;
});
return this;
},
//9.4追加元素到另外一个元素中
//下面几个方法类似
//注意,测试先后顺序,分清插入的位置即可
//注意核心代码的实现,然后再扩展业务逻辑
'appendTo':function(content){
content = $(content);
var source = this;
var target = content;
target.each(function(t_index,t_value){
source.each(function(s_index,s_value){
if(t_index == 0){
t_value.appendChild(s_value);
}else{
var clone = s_value.cloneNode(true);
t_value.appendChild(clone);
}
})
});
return this;
},
‘prependTo’:function(content){
content = $(content);
var source = this;
var target = content;
target.each(function(t_index,t_value){
source.reverse().each(function(s_index,s_value){
if(t_index == 0){
//t_value.appendChild(s_value);
t_value.inertBefore(s_value,t_value.firstChild);
}else{
var clone = s_value.cloneNode(true);
//t_value.appendChild(clone);
t_value.insetBefore(clone,t_value.firstChild);
}
})
});
return this;
},
'prepend':function(content){
if($.isObject(content)){
var source = content;
var target = this;
source.prependTo(target);
return this;
}
this.each(function(){
// this == DOM 对象
this.innerHTML = content + this.innerHTML;
});
return this;
},
'append':function(content){
if($.isObject(content)){
var source = content;
var target = this;
source.appendTo(target);
return this;
}
this.each(function(){
//this == DOM 对象
this.innerHTML = this.innerHTML + content;
});
return this;
}
});
//10.DOM 样式的获取
//注意浏览器兼容
//此处需要掌握:了解样式的分类,以及基本的获取方式
szjQuery.extend({
'getStyle':function(dom){
if($.isDOM(dom)){
var finalStyle = dom.currentStyle ? dom.currentStyle : window.getComputedStyle(dom,null);
return finalStyle;
}
}
});
/*
*11.DOM 节点属性的操作
*包括,操作属性,操作节点属性,修改/获取 CSS 样式
*获取/设置 value 值
*判断类,新增、删除、切换类
*事件添加和移除
*注意:
*此段代码的开发,思路如下
*1.需要考虑核心的代码是什么?
*2.大部分都是批量操作,所以,从小工能开始实现,然后慢慢按照需求进行扩展
*3.千万不要一次性的从最大的功能开始左
*4.不要求实现的一模一样,只需要直到一个大概逻辑,有个简单基本实现就好
*5.容错性逻辑,最后统一处理,放置我们观察主要逻辑
*6.关于一些类名的判断,全部都是靠字符串之间的关系进行处理
*注意容错
*/
szjQuery.prototype.extend({
//操作节点属性
'attr':function(){
if(arguments.length === 1){
var value = arguments[0];
//1.如果是字符串
if($.isString(value)){
//获取第一个 DOM 对象的对应的节点属性值
return this[0].getAttribute(value);
}
//2.如果是对象
if($.isObject(value)){
//批量的设置 DOM 对象里面的节点属性值
this.each(function(index,dom){
$.each(value,function(key,value){
dom.setAttribute(key,value);
})
});
return this;
}
}
//arguments,this,return
if(arguments.length === 2 && $.isString(arguments[0])){
var key = arguments[0];
var value = arguments[1];
//遍历所有的 DOM 对象
this.each(function(index,dom){
dom.setAttribute(key,value);
});
return this;
}
throw '请输入正确的参数';
},
'removeAttr':function(){
var firstParam = arguments[0];
if($.isString(firstParam)){
this.each(function(index,dom){
dom.removeAttribute(firstParam);
})
}
return this;
},
'prop':function(){
if(arguments.length === 1){
var value = arguments[0];
//1.如果是字符串
if($.isString(value)){
//获取第一个 DOM 对象的对应的属性值
return this[0][value];
//this['name'];
//this.name
}
//2.如果是对象
if($.isObject(value)){
//批量的设置 DOM 对象里面的属性值
this.each(function(index,dom){
$.each(value,function(key,value){
dom[key] = value;
})
})
return this;
}
}
if(arguments.length === 2 && $.isString(arguments[0])){
var key = arguments[0];
var value = arguments[1];
//遍历所有的 DOM 对象
this.each(function(index,dom){
dom[key] = value;
});
return this;
}
throw '请输入正确的参数';
},
'removeProp':function(){
var firstParam = arguments[0];
if($.isString(firstParam)){
this.each(function(index,dom){
delete dom[firstParam];
})
}
return this;
},
'CSS':function(){
if(arguments.length === 1){
var value = arguments[0];
//1.如果是字符串
if($.isString(value)){
//value == background width
//获取第一个 DOM 对象里面的样式属性
var dom = this[0];
return $.getStyle(dom)[value];
}
//2.如果是对象
if($is.Object(value)){
//批量的设置 DOM 对象里面的批量样式属性值
this.each(function(){
$.each(value,function(index,dom){
//dom.setAttribute(key,value);
dom.style[key] = value;
})
});
return this;
}
}
if(arguments.length === 2 && $.isString(arguments[0])){
var key = arguments[0];
var value = arguments[1];
//遍历所有的 DOM 对象
this.each(function(index,dom){
dom.style[key] = value;
});
return this;
}
throw '请输入正确的参数';
},
'val':funciton(firstParam){
if(firstParam === undefined){
var dom = this[0];
//return dom.getAttribute('value'); 这种写法是错误的无法实时获取更新了的数值
return dom['value'];
}else{
//批量的给所有的 DOM 对象,赋值 value
this.each(function(index,dom){
dom['value'] = firstParam;
});
return this;
}
},
‘hasClass’:function(firstParam){
if(firstParam === undefined){
return false;
}
// 'box111' 'box1' ' box1'
// ' box1 '
if($.isString(firstParam)){
var trimClassName = ' ' + $.trimString(firstParam) + ' ';
var hasClass = false;
this.each(function(index,dom){
var parentClassName = ' ' + $.trimString(dom.className) + ' ';
var searchIndex = parentClassName.indexOf(trimClassName);
if(searchIndex >= 0){
hasClass = true;
//结束遍历
return false'
}
});
return hasClass;
}
},
'addClass':function(firstParam){
if(firstParam === undefined){
return this;
}
if($.isString(firstParam)){
//1.遍历所有的 dom 对象
this.each(function(index,dom){
//先获取给定的类名数组
var classNameArr = $.trimSpaceArrayWithStr(firstParam);
$.each(classNameArr,function(index,className){
if(!$(dom).hasClass(className)){
dom.className = dom.className + ' ' + className;
}
});
//格式化, dom 的类名
var resultClassNames = $.trimSpaceArrayWithStr(dom.className);
//按照指定的字符,链接所有的元素,-》字符串,就是 join 的作用
dom.className = resultClassNames.join(' ');
});
}
return this;
},
'removeClass':function(firstParam){
if(firstParam === undefined ){
//清空所有 DOM 对象里面的类名
this.each(function(index,dom){
dom.className = '';
});
return this;
}
//'box1 box2'
//'box1'
//'box1 box2'
if($.isString(firstParam)){
//'box1'
//var searchClassName = ' ' + $.trimString(firstParam) + ' ';
//' box1 box2 '
//['box1','box2']
var searchClassNames = $.trimSpaceArrayWithStr(firstParam);
//遍历所有的 DOM 对象
this.each(function(index,dom){
//判断 DOM 对象的 className,是否包含指定的类名
var parentClassName = ' ' + dom.className + ' ';
//针对于每一个 DOM 对象
//删除,一个类名 集合
$.each(searchClassNames,function(){
//如果包含,则删除
if($(dom).hasClass(searchClassName)){
parentClassName = parentClassName.replace(searchClassName,'
');
}
});
//代表该删除的,已经删除完毕
//dom.className = parentClassName;
//['box1','box2','box3']
//'box1 box2 box3'
var classNameArr = #.trimSpaceArrayWithStr(parentClassName);
dom.className = classNameArr.join(' ');
})
}
return this;
},
'toggleClass':function(firstParam){
if(firstParam === undefined){
this.each(function(index,dom){
if(dom.className.length > 0){
//永远有值
dom['orginClassName'] = dom.className;
}
//格式化 className
var resultClassName = dom.className === ' ' ? dom['orginClassName'] : ' ';
dom.className = $trimSpaceArrayWithStr(resultClassName).join(' ');
});
return this;
}
if($.isString(firstParam)){
//firstParam == ' box1 box2 '
var searchClassNames = $.trimSpaceArrayWithStr(firstParam);
//firstParam == 'box1'
//让每一个 DOM 对象,都切换给定的类名
this.each(function(index,dom){
$.each(searchClassName,function(index,searchClassName){
//判断是否有这个类名
//有就删除没有就添加
if($(dom).hasClass(searchClassName)){
$(dom).removeClass(searchClassName);
}else{
$(dom).addClass(searchClassName);
}
})
});
return this;
}
}
});
/*
*12 添加基本的事件监听和移除方法
*注意浏览器的兼容
*以及以后开发工具类的时候,工具类方法的类型
*是选择对象方法,还是使用静态方法
*主要区分依据,就是看下在方法内部是否可以借助 this 对象中的属性获取其他信息
*如果不需要,则统一使用静态方法进行实现
*/
$.extend({
'addEvent':function(ele,type,func){
//0.容错处理
if(!$.isDOM(ele) || !$.isString(type) || isFunction(func)){
return;
}
//1.根据传递过来的参数,进行事件的监听
if(ele.addEventListener){
ele.addEventListner(type,func)
}else{
ele.attachEvent('on' + type ,func)
}
return;
},
'removeEvent':function(ele,type,func){
if(!$.isDOM(ele) || $.isString(type) || $.isFunction(func)){
return;
}
//1.根据传递过来的参数,进行事件的监听
if(ele.removeEventListener){
ele.removeEventListener(type,func);
}else{
ele.detachEvent('on' + type,func);
}
return;
}
});
/*
*13.批量操作事件绑定,和事件解绑的方法
*此处注意开发步骤
*先从最基本,最小的功能开始左
*比如可以先看下一个 dom 对象,添加一个事件类型,绑定一个回调函数的时候是如何实现的
*然后慢慢进行扩展多个类型的
*多个回调函数的
*多个 DOM 对象的
*以此类推
*/
$.prototype.extend({
//一个类型,一个回调函数,多个DOM 对象的情况
'_on':function(type,func){
//批量给 DOM 对象,添加事件
this.each(function(index,dom){
$.addEvent(dom,type,func)
})
},
//多个类型,一个回调函数,多个 DOM 对象的情况
'_on2':function(types,func){
//批量给 DOM 对象,添加事件
var typeArray = $.trimSpaceArrayWithStr(types);
this.each(function(index,type){
$.each(typeArray,function(index,type){
$.addEvent(dom,type,func)
})
})
},
//多个类型,多个回调函数,多个 DOM 对象的情况
'_on3':function(firstParam){
//当事件绑定的时候
if(arguments.length === 1){
if(!$.isObject(firstParam)){
return this;
}
this.each(function(index,dom){
$.each(firstParam,function(type,func){
$.addEvent(dom,type,func);
})
});
return this;
}
if(arguments.length === 2){
//'click dbclick mouseover',func
//批量给 DOM 对象,添加事件
var typeArray = $.trimSpaceArrayWithStr(arguments[0]);
var func = arguments[1];
this.each(function(index.dom){
$.each(typeArray,function(index,type){
$.addEvent(dom,type,func);
})
})
}
},
//为了存储,监听的事件信息,方便解绑
'on':function(firstParam){
//当事件绑定的时候,把这个 DOM 对象身上的事件类型,和事件回调都给记录下来
if(arguments.length === 1){
if(!$.isObject(firstParam)){
return this;
}
this.each(function(index,dom){
//1.给 dom, 新增一个属性
dom['eventCache'] = dom['eventCache'] || [];
$.each(firstParam,function(type,func){
$.addEvent(dom,type,func);
dom['eventCache'][type] = dom['eventCache'][type] || [];
dom['eventCache'][type].push(func);
})
});
return this;
}
if(arguments.length ===2){
var typeArray = $.trimSpaceArrayWithStr(argument[0]);
var func = arguments[1];
this.each(function(index,dom){
dom['eventCache'] = dom['eventCache'] || [];
$.each(typeArray,function(index,type){
$.addEvent(dom,type,func);
dom['eventCache'][type] = dom['eventCache'][type] || [];
dom['eventCache'][type].push(func);
})
})
}
},
//解绑多个 DOM 对象,一个事件类型的情况
'_off':function(type){
//type === 'click'
this.each(funcntion(index,dom){
var funcs = dom['eventCache'][type];
$each(funcs,function(index,func){
$.removeEvent(dom,type,func);
})
});
return this;
},
//解绑多个 DOM 对象,多个事件类型的情况
'_off2':function(types){
var typeArr = $.trimSpaceArrayWithStr(types);
this.each(function(index,dom){
$.each(typeArr,function(index,type){
//找到类型对应的回调函数组
var funcs = dom['eventCache'][type];
//解绑函数
$.each(funcs,function(index,func){
$.removeEvent(dom,type,func)
})
})
});
return this;
},
/*
*解绑多个DOM 对象,一个事件类型 指定回调函数的情况
*注意:吃出的开发,需要结合 on 方法内部,先记录下,当前对象绑定的事件信息
*/
'off': function (types, paramFunc) {
// type === 'click'
var typeArr = $.trimSpaceArrayWithStr(types);
// 遍历所有的DOM对象
this.each(function (index, dom) {
console.log(dom['eventCache']);
// 遍历所有的事件类型
$.each(typeArr, function (index, type) {
// 找到类型对应的回调函数数组
var funcs = dom['eventCache'][type];
// 解绑函数
// 遍历所有的函数
$.each(funcs, function (index, func) {
// 处理指定函数这个参数没有传递的情况
if (paramFunc === undefined) {
$.removeEvent(dom, type, func);
}
// 处理指定函数 这个参数传递的情况
if (func === paramFunc) {
// 解绑
$.removeEvent(dom, type, func);
}
})
})
});
return this;
}
})
})(window)