天天看點

jquery源碼系列:append方法實作過程

no1:

// define a local copy of jquery

var jquery = function( selector, context ) {

// the jquery object is actually just the init constructor 'enhanced'

return new jquery.fn.init( selector, context, rootjquery );  //調用第二步init方法

},

no2:

jquery.fn = jquery.prototype = {

constructor: jquery,

init: function( selector, context, rootjquery ) {

var match, elem, ret, doc;

// handle $(""), $(null), or $(undefined)

if ( !selector ) {

return this;

}

// handle $(domelement)

if ( selector.nodetype ) {

this.context = this[0] = selector;

this.length = 1;

// the body element only exists once, optimize finding it

if ( selector === "body" && !context && document.body ) {

this.context = document;

this[0] = document.body;

this.selector = selector;

// handle html strings

if ( typeof selector === "string" ) {

// are we dealing with html string or an id?

if ( selector.charat(0) === "<" && selector.charat( selector.length - 1 ) === ">" && selector.length >= 3 ) {

// assume that strings that start and end with <> are html and skip the regex check

match = [ null, selector, null ];

} else {

match = quickexpr.exec( selector );

// verify a match, and that no context was specified for #id

if ( match && (match[1] || !context) ) {

// handle: $(html) -> $(array)

if ( match[1] ) {

context = context instanceof jquery ? context[0] : context;

doc = ( context ? context.ownerdocument || context : document );

// if a single string is passed in and it's a single tag

// just do a createelement and skip the rest

ret = rsingletag.exec( selector );

if ( ret ) {

if ( jquery.isplainobject( context ) ) {

selector = [ document.createelement( ret[1] ) ];

jquery.fn.attr.call( selector, context, true );

selector = [ doc.createelement( ret[1] ) ];

ret = jquery.buildfragment( [ match[1] ], [ doc ] );

selector = ( ret.cacheable ? jquery.clone(ret.fragment) : ret.fragment ).childnodes;

return jquery.merge( this, selector );

// handle: $("#id")

elem = document.getelementbyid( match[2] );

// check parentnode to catch when blackberry 4.6 returns

// nodes that are no longer in the document #6963

if ( elem && elem.parentnode ) {

// handle the case where ie and opera return items

// by name instead of id

if ( elem.id !== match[2] ) {

return rootjquery.find( selector );

// otherwise, we inject the element directly into the jquery object

this[0] = elem;

// handle: $(expr, $(...))

} else if ( !context || context.jquery ) {

return ( context || rootjquery ).find( selector );

// handle: $(expr, context)

// (which is just equivalent to: $(context).find(expr)

return this.constructor( context ).find( selector );

// handle: $(function)

// shortcut for document ready

} else if ( jquery.isfunction( selector ) ) {

return rootjquery.ready( selector );

if ( selector.selector !== undefined ) {

this.selector = selector.selector;

this.context = selector.context;

return jquery.makearray( selector, this );

// start with an empty selector

selector: "",

// the current version of jquery being used

jquery: "1.7.1",

// the default length of a jquery object is 0

length: 0,

// the number of elements contained in the matched element set

size: function() {

return this.length;

toarray: function() {

return slice.call( this, 0 );

// get the nth element in the matched element set or

// get the whole matched element set as a clean array

get: function( num ) {

return num == null ?

// return a 'clean' array

this.toarray() :

// return just the object

( num < 0 ? this[ this.length + num ] : this[ num ] );

// take an array of elements and push it onto the stack

// (returning the new matched element set)

pushstack: function( elems, name, selector ) {

// build a new jquery matched element set

var ret = this.constructor();

if ( jquery.isarray( elems ) ) {

push.apply( ret, elems );

jquery.merge( ret, elems );

// add the old object onto the stack (as a reference)

ret.prevobject = this;

ret.context = this.context;

if ( name === "find" ) {

ret.selector = this.selector + ( this.selector ? " " : "" ) + selector;

} else if ( name ) {

ret.selector = this.selector + "." + name + "(" + selector + ")";

// return the newly-formed element set

return ret;

// execute a callback for every element in the matched set.

// (you can seed the arguments with an array of args, but this is

// only used internally.)

each: function( callback, args ) {

return jquery.each( this, callback, args );

ready: function( fn ) {

// attach the listeners

jquery.bindready();

// add the callback

readylist.add( fn );

eq: function( i ) {

i = +i;

return i === -1 ?

this.slice( i ) :

this.slice( i, i + 1 );

first: function() {

return this.eq( 0 );

last: function() {

return this.eq( -1 );

slice: function() {

return this.pushstack( slice.apply( this, arguments ),

"slice", slice.call(arguments).join(",") );

map: function( callback ) {

return this.pushstack( jquery.map(this, function( elem, i ) {

return callback.call( elem, i, elem );

}));

end: function() {

return this.prevobject || this.constructor(null);

// for internal use only.

// behaves like an array's method, not like a jquery method.

push: push,

sort: [].sort,

splice: [].splice

};

no3: 進入append入口

jquery.fn.extend({

text: function( text ) {

if ( jquery.isfunction(text) ) {

return this.each(function(i) {

var self = jquery( this );

self.text( text.call(this, i, self.text()) );

});

if ( typeof text !== "object" && text !== undefined ) {

return this.empty().append( (this[0] && this[0].ownerdocument || document).createtextnode( text ) );

return jquery.text( this );

wrapall: function( html ) {

if ( jquery.isfunction( html ) ) {

jquery(this).wrapall( html.call(this, i) );

if ( this[0] ) {

// the elements to wrap the target around

var wrap = jquery( html, this[0].ownerdocument ).eq(0).clone(true);

if ( this[0].parentnode ) {

wrap.insertbefore( this[0] );

wrap.map(function() {

var elem = this;

while ( elem.firstchild && elem.firstchild.nodetype === 1 ) {

elem = elem.firstchild;

return elem;

}).append( this );

wrapinner: function( html ) {

jquery(this).wrapinner( html.call(this, i) );

return this.each(function() {

var self = jquery( this ),

contents = self.contents();

if ( contents.length ) {

contents.wrapall( html );

self.append( html );

wrap: function( html ) {

var isfunction = jquery.isfunction( html );

jquery( this ).wrapall( isfunction ? html.call(this, i) : html );

unwrap: function() {

return this.parent().each(function() {

if ( !jquery.nodename( this, "body" ) ) {

jquery( this ).replacewith( this.childnodes );

}).end();

append: function() {

if ( this.nodetype === 1 ) {

this.appendchild( elem );

prepend: function() {

return this.dommanip(arguments, true, function( elem ) {

this.insertbefore( elem, this.firstchild );

before: function() {

if ( this[0] && this[0].parentnode ) {

return this.dommanip(arguments, false, function( elem ) {

this.parentnode.insertbefore( elem, this );

} else if ( arguments.length ) {

var set = jquery.clean( arguments );

set.push.apply( set, this.toarray() );

return this.pushstack( set, "before", arguments );

after: function() {

this.parentnode.insertbefore( elem, this.nextsibling );

var set = this.pushstack( this, "after", arguments );

set.push.apply( set, jquery.clean(arguments) );

return set;

// keepdata is for internal use only--do not document

remove: function( selector, keepdata ) {

for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {

if ( !selector || jquery.filter( selector, [ elem ] ).length ) {

if ( !keepdata && elem.nodetype === 1 ) {

jquery.cleandata( elem.getelementsbytagname("*") );

jquery.cleandata( [ elem ] );

if ( elem.parentnode ) {

elem.parentnode.removechild( elem );

empty: function() {

// remove element nodes and prevent memory leaks

if ( elem.nodetype === 1 ) {

// remove any remaining nodes

while ( elem.firstchild ) {

elem.removechild( elem.firstchild );

clone: function( dataandevents, deepdataandevents ) {

dataandevents = dataandevents == null ? false : dataandevents;

deepdataandevents = deepdataandevents == null ? dataandevents : deepdataandevents;

return this.map( function () {

return jquery.clone( this, dataandevents, deepdataandevents );

html: function( value ) {

if ( value === undefined ) {

return this[0] && this[0].nodetype === 1 ?

this[0].innerhtml.replace(rinlinejquery, "") :

null;

// see if we can take a shortcut and just use innerhtml

} else if ( typeof value === "string" && !rnoinnerhtml.test( value ) &&

(jquery.support.leadingwhitespace || !rleadingwhitespace.test( value )) &&

!wrapmap[ (rtagname.exec( value ) || ["", ""])[1].tolowercase() ] ) {

value = value.replace(rxhtmltag, "<$1></$2>");

try {

for ( var i = 0, l = this.length; i < l; i++ ) {

if ( this[i].nodetype === 1 ) {

jquery.cleandata( this[i].getelementsbytagname("*") );

this[i].innerhtml = value;

// if using innerhtml throws an exception, use the fallback method

} catch(e) {

this.empty().append( value );

} else if ( jquery.isfunction( value ) ) {

this.each(function(i){

self.html( value.call(this, i, self.html()) );

replacewith: function( value ) {

// make sure that the elements are removed from the dom before they are inserted

// this can help fix replacing a parent with child elements

if ( jquery.isfunction( value ) ) {

var self = jquery(this), old = self.html();

self.replacewith( value.call( this, i, old ) );

if ( typeof value !== "string" ) {

value = jquery( value ).detach();

var next = this.nextsibling,

parent = this.parentnode;

jquery( this ).remove();

if ( next ) {

jquery(next).before( value );

jquery(parent).append( value );

return this.length ?

this.pushstack( jquery(jquery.isfunction(value) ? value() : value), "replacewith", value ) :

this;

detach: function( selector ) {

return this.remove( selector, true );

dommanip: function( args, table, callback ) {

var results, first, fragment, parent,

value = args[0],

scripts = [];

// we can't clonenode fragments that contain checked, in webkit

if ( !jquery.support.checkclone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) {

jquery(this).dommanip( args, table, callback, true );

if ( jquery.isfunction(value) ) {

var self = jquery(this);

args[0] = value.call(this, i, table ? self.html() : undefined);

self.dommanip( args, table, callback );

parent = value && value.parentnode;

// if we're in a fragment, just use that instead of building a new one

if ( jquery.support.parentnode && parent && parent.nodetype === 11 && parent.childnodes.length === this.length ) {

results = { fragment: parent };

results = jquery.buildfragment( args, this, scripts );  //構造一個新執行個體

fragment = results.fragment;

if ( fragment.childnodes.length === 1 ) {

first = fragment = fragment.firstchild;

first = fragment.firstchild;

if ( first ) {

table = table && jquery.nodename( first, "tr" );

for ( var i = 0, l = this.length, lastindex = l - 1; i < l; i++ ) {

callback.call(

table ?

root(this[i], first) :

this[i],

// make sure that we do not leak memory by inadvertently discarding

// the original fragment (which might have attached data) instead of

// using it; in addition, use the original fragment object for the last

// item instead of first because it can end up being emptied incorrectly

// in certain situations (bug #8070).

// fragments from the fragment cache must always be cloned and never used

// in place.

results.cacheable || ( l > 1 && i < lastindex ) ?

jquery.clone( fragment, true, true ) :

fragment

);

if ( scripts.length ) {

jquery.each( scripts, evalscript );

//buildfragment 方法

jquery.buildfragment = function( args, nodes, scripts ) {

var fragment, cacheable, cacheresults, doc,

first = args[ 0 ];

// nodes may contain either an explicit document object,

// a jquery collection or context object.

// if nodes[0] contains a valid object to assign to doc

if ( nodes && nodes[0] ) {

doc = nodes[0].ownerdocument || nodes[0];

// ensure that an attr object doesn't incorrectly stand in as a document object

// chrome and firefox seem to allow this to occur and will throw exception

// fixes #8950

if ( !doc.createdocumentfragment ) {

doc = document;

// only cache "small" (1/2 kb) html strings that are associated with the main document

// cloning options loses the selected state, so don't cache them

// ie 6 doesn't like it when you put <object> or <embed> elements in a fragment

// also, webkit does not clone 'checked' attributes on clonenode, so don't cache

// lastly, ie6,7,8 will not correctly reuse cached fragments that were created from unknown elems #10501

if ( args.length === 1 && typeof first === "string" && first.length < 512 && doc === document &&

first.charat(0) === "<" && !rnocache.test( first ) &&

(jquery.support.checkclone || !rchecked.test( first )) &&

(jquery.support.html5clone || !rnoshimcache.test( first )) ) {

cacheable = true;

cacheresults = jquery.fragments[ first ];

if ( cacheresults && cacheresults !== 1 ) {

fragment = cacheresults;

if ( !fragment ) {

fragment = doc.createdocumentfragment();

jquery.clean( args, doc, fragment, scripts );

if ( cacheable ) {

jquery.fragments[ first ] = cacheresults ? fragment : 1;

return { fragment: fragment, cacheable: cacheable };

繼續閱讀