本节内容
1.jQuery的无new构建
2.插件接口
3.默认回调对象设计
4.ownerDocument 和 documentElement 的区别
5.jQuery.parseHTML()
6.buildFragment()
7.jQuery.fn.init(selector, context, root)
jQuery的无new构建
jQuery 通过 $()
返回实例
var jQuery = function(selector, context) {
return new jQuery();
}
jQuery.prototype = {
name: function() {},
age: function() {}
}
但是这样有个问题,代码陷入死循环了!所以改改
var jQuery = function(selector, context) {
return jQuery.prototype.init();
}
jQuery.prototype = {
init: function() {
return this; // 这里的 this 指向原型对象
},
name: function() {},
age: function() {}
}
js 中的 this
指向问题:
1.在一般函数方法中使用 this
指代全局对象
function test() {
this.x = 1; // 这里 this 指向 window
}
2.作为对象方法调用,this
指代上级对象
var obj = {
x: 1,
m: function test() {
alert(this.x); // 这里 this 指向 obj
}
};
obj.m(); // 1
3.作为构造函数调用,this
指代 new
出的对象
function test() {
this.x = 1;
console.log(this.x);
}
var obj = new test();
还有问题没解决,就是不论 $()
调用多少次,构建的都是同一个实例,这样是不行的,所以将代码改成这样
var jQuery = function(selector, context) {
return new jQuery.prototype.init();
}
jQuery.prototype = {
init: function() {
return this; // 这里的 this 指向 init 对象
},
name: function() {},
age: function() {}
}
构建不同的实例这个问题解决了,然而新的问题又来了,创建出来的实例对象中没有 name()
和 age()
,那只能再改改代码,添加上一行代码
jQuery.fn.init.prototype = jQuery.fn;
插件接口
jQuery.extend = jQuery.fn.extend = function() {
通过这个接口能够
1.扩展某个对象的属性或方法
2.还能扩展 jQuery 自身的属性方法
3.还支持深度拷贝
由于 javascript 不支持方法的重载,所以只能在一个方法里面实现重载,就是通过在判断参数列表的不同来执行不同的逻辑
jQuery.extend = jQuery.fn.extend = function() {
var src, copyIsArray, copy, name, options, clone,
target = arguments[0] || {}, // 被扩展的对象
i = 1, // 第一个扩展对象的下标
length = arguments.length, // 参数列表的长度
deep = false; // 深拷贝
// 第一种重载
// 如果第一个参数类型为 boolean,那就表示要设置是否深拷贝
if ( typeof target === "boolean" ) {
deep = target;
target = arguments[1] || {};
i = 2;
}
// 处理奇怪的情况
if ( typeof target !== "object" ) {
target = {};
}
// 第二种重载
// 当传入参数只有一个的时候,就扩展 jQuery 自身
if ( length === i ) {
target = this; // jQuery.extend时,this指的是jQuery;jQuery.fn.extend时,this指的是jQuery.fn
--i;
}
for ( ; i < length; i++ ) {
if ( (options = arguments[ i ]) != null ) {
for ( name in options ) {
copy = options[ name ];
// 防止自引用
if ( target === copy ) {
continue;
}
// 如果是深拷贝,且被拷贝的属性值本身是个对象或数组
if ( deep && copy && ( Object.prototype.toString.call(copy) === '[Object Object]' ||
( copyIsArray = Array.isArray(copy) ) ) ) {
src = target[ name ];
if ( copyIsArray && !Array.isArray( src ) ) {
clone = [];
} else if ( !copyIsArray && ( Object.prototype.toString.call(copy) !== '[Object Object]' ) ) {
clone = {};
} else {
clone = src;
}
copyIsArray = false;
target[ name ] = jQuery.extend( deep, clone, copy ); // 递归~
} else if ( copy !== undefined ) { // 浅拷贝,且属性值不为undefined
target[ name ] = copy;
}
}
}
}
// 返回扩展对象
return target;
}
默认回调对象设计
function Callbacks() {
var list = [];
var self;
self = {
add: function(fn) {
list.push(fn)
},
fire: function(args) {
list.forEach(function(fn) {
fn(args);
})
}
}
return self;
}
function fn1(val) {
console.info('fn1 says:' + val);
}
function fn2(val) {
console.info('fn2 says:' + val);
}
var cbs = Callbacks();
cbs.add(fn1);
cbs.fire('foo');
cbs.add(fn2);
cbs.fire('bar');
这里我有个迷惑的点就是 Callbacks()
函数中的 list
去哪了,打开控制台可以看到 cbs
内部并没 list
。
用排除法分析:
1.list
是 Callbacks()
函数的局部变量,如果不用 var 声明就是全局变量,也就是说 list
只能存在于 Callbacks()
的内部。
2.创造对象的两种方法,一种是使用 new
关键字创建,另一种是调用函数然后函数返回一个对象。
3.很显然 Callbacks()
采用的是第二种方法创建对象,返回的是 self
这个对象,self
对象相当于一个闭包,在这个闭包内能够访问到 list
对象。
4.这相当于将变量私有化的一种手段,无论在哪里都没法访问到 list
,只能通过 self
内部的 add()
和 fire()
两个方法才能访问到 list
。
总结
list
变量到底在哪里我还是不得而知,唯一肯定的是它是 Callbacks()
函数的局部变量。
ownerDocument 和 documentElement 的区别
通过google浏览器可以看到任何DOM对象都有 ownerDocument 属性,而 documentElement 是 document 对象独有的属性。
documentElement
返回的是 html
对象
ownerDocument
返回的是文档根节点也就是 document
对象
jQuery.parseHTML()
作用:将一串 html 字符转换为 DOM 对象
// @data html字符串
// @context HTML文档
// @keepScripts 不知道
jQuery.parseHTML = function( data, context, keepScripts ) {
if ( typeof data !== "string" ) {
return [];
}
if ( typeof context === "boolean" ) {
keepScripts = context;
context = false;
}
var base, parsed, scripts;
// 前面的代码直接忽略
// 如果没有 HTML 文档就创建一个
if ( !context ) {
if ( support.createHTMLDocument ) {
// 创建 HTML 文档的核心代码
// 对这个方法有什么疑问参见 https://developer.mozilla.org/zh-CN/docs/Web/API/DOMImplementation/createHTMLDocumen
context = document.implementation.createHTMLDocument( "" );
base = context.createElement( "base" );
base.href = document.location.href;
context.head.appendChild( base );
} else {
context = document;
}
}
// 这个正则表达式放着儿不知道什么意思,应该是处理某种匪夷所思的 bug 吧
// rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i );
// 这个正则表达式是用来匹配像 <span></span>、<img /> 这种单个标签的
parsed = rsingleTag.exec( data );
scripts = !keepScripts && [];
if ( parsed ) {
return [ context.createElement( parsed[ 1 ] ) ];
}
// 将字符转换为 DOM 的操作在这儿
// 这里返回的是 DocumentFragment 节点,它的 childNodes 就是转换好的 html 字符
parsed = buildFragment( [ data ], context, scripts );
if ( scripts && scripts.length ) {
jQuery( scripts ).remove();
}
return jQuery.merge( [], parsed.childNodes );
};
buildFragment()
// @elems [ html 字符串, html 字符串, html 字符串, ... ]
// @context HTML文档
// 剩余的参数就不知道什么意思了
function buildFragment( elems, context, scripts, selection, ignored ) {
var elem, tmp, tag, wrap, attached, j,
// 这里用到了 DocumentFragment 的特性
// DocumentFragment 节点不属于文档树
// DocumentFragment 节点插入文档树时,插入的不是 DocumentFragment 自身,而是它的所有子孙节点
fragment = context.createDocumentFragment(),
nodes = [],
i = 0,
l = elems.length;
for ( ; i < l; i++ ) {
elem = elems[ i ];
if ( elem || elem === 0 ) {
if ( toType( elem ) === "object" ) {
jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );
} else if ( !rhtml.test( elem ) ) {
nodes.push( context.createTextNode( elem ) );
} else { // 上面的两个 if 分支都不用看了,因为程序压根儿都没走过那儿
// 待会儿所有由字符转换的 DOM 对象会挂在 tmp 下作为它的 childNodes
tmp = tmp || fragment.appendChild( context.createElement( "div" ) );
// 下面3行代码不知道什么意思,应该是处理某种匪夷所思的 bug 吧
// rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i );
// 这个正则表达式其实就是用来匹配 TagName 的
// 其实我觉得 tmp.innerHTML = elem 就足够了
// 写那么复杂可能是处理 elems 中有多个 html 字符串的情况吧
tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase();
wrap = wrapMap[ tag ] || wrapMap._default;
tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ];
j = wrap[ 0 ];
while ( j-- ) {
tmp = tmp.lastChild;
}
// 此时 html 字符串已经转换为 tmp 的子节点
// 这一步是将 tmp 的子节点全部放到 nodes 数组中
jQuery.merge( nodes, tmp.childNodes );
tmp = fragment.firstChild;
tmp.textContent = "";
}
}
}
fragment.textContent = "";
i = 0;
while ( ( elem = nodes[ i++ ] ) ) {
// 直接忽略
if ( selection && jQuery.inArray( elem, selection ) > -1 ) {
if ( ignored ) {
ignored.push( elem );
}
continue;
}
attached = isAttached( elem );
// 这一步又将所有转换的 DOM 对象挂在 DocumentFragment 片段下作为子节点
tmp = getAll( fragment.appendChild( elem ), "script" );
// 忽略
if ( attached ) {
setGlobalEval( tmp );
}
// 忽略
if ( scripts ) {
j = 0;
while ( ( elem = tmp[ j++ ] ) ) {
if ( rscriptType.test( elem.type || "" ) ) {
scripts.push( elem );
}
}
}
}
return fragment;
}
jQuery.fn.init(selector, context, root)
这个方法处理的东西比较多,有些代码看不懂,不过影响不大,只要能理解整体思路就行了
var rootjQuery,
rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,
init = jQuery.fn.init = function( selector, context, root ) {
var match, elem;
// 处理非法参数
if ( !selector ) {
return this;
}
root = root || rootjQuery;
// 传入的是字符串
if ( typeof selector === "string" ) {
if ( selector[ 0 ] === "<" &&
selector[ selector.length - 1 ] === ">" &&
selector.length >= 3 ) { // 匹配 '<' 开始 '>' 结尾的字符,如:<span></span>
match = [ null, selector, null ];
} else {
match = rquickExpr.exec( selector );
}
if ( match && ( match[ 1 ] || !context ) ) {
// 传入的是html字符串
if ( match[ 1 ] ) {
context = context instanceof jQuery ? context[ 0 ] : context;
// 将html字符转换成DOM对象,并把它挂在jQuery对象上
jQuery.merge( this, jQuery.parseHTML(
match[ 1 ],
context && context.nodeType ? context.ownerDocument || context : document,
true
) );
// HANDLE: $(html, props)
if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) {
for ( match in context ) {
// Properties of context are called as methods if possible
if ( isFunction( this[ match ] ) ) {
this[ match ]( context[ match ] );
// ...and otherwise set as attributes
} else {
this.attr( match, context[ match ] );
}
}
}
return this;
// 传入的是#id
} else {
// 直接用document.getElementById拿到DOM对象
elem = document.getElementById( match[ 2 ] );
if ( elem ) {
this[ 0 ] = elem;
this.length = 1;
}
return this;
}
// HANDLE: $(expr, $(...))
} else if ( !context || context.jquery ) {
return ( context || root ).find( selector );
// HANDLE: $(expr, context)
// (which is just equivalent to: $(context).find(expr)
} else {
return this.constructor( context ).find( selector );
}
// 传入的是DOM对象
} else if ( selector.nodeType ) {
this[ 0 ] = selector;
this.length = 1;
return this;
// HANDLE: $(function)
// Shortcut for document ready
} else if ( isFunction( selector ) ) {
return root.ready !== undefined ?
root.ready( selector ) :
selector( jQuery );
}
return jQuery.makeArray( selector, this );
};
其他的else if不知道是处理哪种情况的,不过可以看出init这个接口可以处理不同种类的参数,可以向其中传入 html字符、id、className、DOM对象、jQuery对象等等。
网友评论