如果说php是世界上最好的语言,那么javascript无疑可以称为世界上最飘逸的语言,最近看了下jQuery的源码,实现了一个简陋的jQuery。我觉得要看懂jQuery整体结构,需要搞懂js作用域链,闭包,js prototype继承,关于闭包网络上的定义实在太多了,这里参照了js权威指南里的定义,感觉从js的角度好理解一点。
闭包:js函数对象不仅包含函数的代码逻辑,还引用了当前的作用域链,
函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内
这种特性在计算机科学文献中称为闭包,所有的js函数都是闭包。
javascript运行在它被定义的作用域里,而不是执行的作用域里
关于js作用域参见 : http://www.laruence.com/2009/05/28/863.html
废话少说,上滥代码
<!DOCTYPE html>
<html>
<head>
<title>jQuery源码浅析</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<script>
/**
*
* 参照jQuery 3.2.1源码
* 省略了一些规范,如AMD, Commonjs
* 整个jQuery包含在匿名函数中,函数就是闭包
*/
(function(window, factory){
factory(window);
})(window, function(window){
/**
* 为什么要传window对象?
* 1.在jQuery里可以把window对象当局部变量使用
* 2.压缩的时候window变量名可以压缩至1个字节
*/
var getProto = Object.getPrototypeOf;
var class2type = {};
var toString = class2type.toString;
var hasOwn = class2type.hasOwnProperty;
var fnToString = hasOwn.toString;
var ObjectFunctionString = fnToString.call( Object );
/**
* 工厂方法,返回jQuery.fn.init的实例,即jQuery对象
* selector待查找的字符串,源码里还有context参数,此处省略
* 我们所说的jQuery其实准确的说是jQuery工厂方法,调用jQuery工厂方法返回的才是真正的jQuery对象
*/
var jQuery = function(selector){
return new jQuery.fn.init(selector);
},
version = "3.2.1";
//jQuery原型对象赋值给jQuery.fn,定义在jQuery.prototype里的方法可以被所有的jQuery对象使用
jQuery.fn = jQuery.prototype = {
jquery: version,
constructor: jQuery,
//...省掉了一些方法
};
//jQuery.fn的属性init函数,jQuery的选择器使用了Ssize,这里简单的使用一个id选择器
jQuery.fn.init = function(selector){
/* (在Javascript中,This关键字永远都指向函数(方法)的所有者) this指向的是jQuery.fn
* 这里简单的将DOM对象赋值给this[0],其他属性省略, 我们使用jQuery的时候使用下标0即可将jQuery对象转化为普通的DOM对象
*/
this[0] = window.document.getElementById(selector);
return this;
};
/**
* 这一句很关键
* 将jQuery.fn赋值给jQuery.fn.init的原型,这样jQuery.fn.init的实例(通常我们所说的jQuery对象就是它)可以使用jQuery.fn的方法
* 结合之前可以得出 jQuery.fn.init.prototype = jQuery.fn = jQuery.prototype
* jQuery.fn,jQuery.prototype扩展的方法属性 jQuery对象可以使用
*/
jQuery.fn.init.prototype = jQuery.fn;
//实现了jQuery的html方法
jQuery.fn.html = function(value){
if(typeof value === "string"){
this[0].innerHTML = value;
return this;
}else{
return this[0].innerHTML;
}
};
/**
*
* jQuery扩展方法,除了可以扩展jQuery外,还可以扩展你指定的对象
* jQuery.extend 扩展jQuery,可以理解为扩展类方法
* jQuery.fn.extend 扩展jQuery.fn,即jQuery实例可以使用,可以理解为扩展实例方法
*
* 具体用法
* 1.jQuery.extend(obj) 扩展jQeury
* 2.jQuery.extend(true, obj) 深度扩展jQuery
* 3.jQuery.extend(obj1, obj2) 扩展obj1
* 4.jQuery.extend(true obj1, obj2) 深度扩展obj1
*/
jQuery.extend = jQuery.fn.extend = function() {
var options, name, src, copy, copyIsArray, clone,
target = arguments[ 0 ] || {},
i = 1,
length = arguments.length,
deep = false;
//第一个参数为boolean且为true的时候为深度扩展
if ( typeof target === "boolean" ) {
deep = target;
// Skip the boolean and the target
target = arguments[ i ] || {};
i++;
}
//被扩展的对象不是对象或者函数
if ( typeof target !== "object" && !jQuery.isFunction( target ) ) {
target = {};
}
//参数只有1个对象的情况下,扩展jQuery,多个对象则扩展第一个对象
if ( i === length ) {
target = this;
i--;
}
for ( ; i < length; i++ ) {
//只处理非空值
if ( ( options = arguments[ i ] ) != null ) {
// Extend the base object
for ( name in options ) {
src = target[ name ];
copy = options[ name ];
// Prevent never-ending loop
if ( target === copy ) {
continue;
}
//仅在属性为纯粹对象或者 数组的时候深度拷贝才有效
if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
( copyIsArray = Array.isArray( copy ) ) ) ) {
if ( copyIsArray ) {
copyIsArray = false;
clone = src && Array.isArray( src ) ? src : [];
} else {
clone = src && jQuery.isPlainObject( src ) ? src : {};
}
//递归扩展
target[ name ] = jQuery.extend( deep, clone, copy );
// Don't bring in undefined values
} else if ( copy !== undefined ) {
target[ name ] = copy;
}
}
}
}
// Return the modified object
return target;
};
jQuery.extend( {
isFunction: function( obj ) {
return jQuery.type( obj ) === "function";
},
isPlainObject: function( obj ) {
var proto, Ctor;
// Detect obvious negatives
// Use toString instead of jQuery.type to catch host objects
if ( !obj || toString.call( obj ) !== "[object Object]" ) {
return false;
}
proto = getProto( obj );
// Objects with no prototype (e.g., `Object.create( null )`) are plain
if ( !proto ) {
return true;
}
// Objects with prototype are plain iff they were constructed by a global Object function
Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor;
return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString;
},
type: function( obj ) {
if ( obj == null ) {
return obj + "";
}
// Support: Android <=2.3 only (functionish RegExp)
return typeof obj === "object" || typeof obj === "function" ?
class2type[ toString.call( obj ) ] || "object" :
typeof obj;
},
});
//简易ready方法实现, 只支持DOMContentLoaded
jQuery.extend( {
readyList : [],
ready: function(fn){
if(fn && typeof fn === 'function'){
//向数组末尾添加函数
jQuery.readyList.push(fn);
}else{
//顺序执行ready绑定的方法
for(var i in jQuery.readyList){
fn = jQuery.readyList[i];
//fn来自于全局作用域,属于window对象
fn.call(window, jQuery);
}
}
}
});
//只支持DOMContentLoaded
document.addEventListener( "DOMContentLoaded", completed );
window.addEventListener( "load", completed );
function completed() {
document.removeEventListener( "DOMContentLoaded", completed );
window.removeEventListener( "load", completed );
jQuery.ready();
}
//只暴露了两个变量到全局作用域
window.$ = window.jQuery = jQuery;
});
$.ready(function(){
console.log('----设置id为test的元素文档内容,并获取文档内容----' + $('test').html('jQuery').html());
});
$.ready(function(){
console.log(1);
});
$.ready(function(){
console.log(2);
});
$.ready(function(){
console.log(3);
});
var obj1 = {
ball : {
nba : 'nba'
},
};
var obj2 = {
ball : {
cba : 'cba'
}
};
var obj3 = {
ball : {
nba : 'nba'
}
};
//扩展obj1
$.extend(obj1, obj2);
/**
* {ball : {'cba' : 'cba'}}
*/
//深度扩展obj3
$.extend(true, obj3, obj2);
/**
* {ball : {'nba' : 'nba'}, {'cba' : 'cba'}}
*/
</script>
<body>
<div id="test">my jQuery</div>
</body>
</html>