jQuery ajax - 주 함수 분석

58831 단어 jqueryAjax
jQuery ajax 가 Callbacks, Deferred, serialize, event 등 모듈 에 대한 의존 으로 인해 이 모듈 들 에 대해 알 지 못 하 는 친구 들 을 한 번 보 는 것 을 권장 합 니 다 jQuery Callbacks, jQuery Deferred, jQuery serialize, jQuery 이벤트 (상).
이 글 은 380 + 줄 을 가 진 jQuery. ajax 함 수 를 분석 하고 있 으 며, 이 함 수 는 jQuery ajax 의 핵심 함수 이 며, jQuery 의 다른 ajax 방법 은 거의 이 방법 에 기초 하고 있다.
지난 글 에서 우 리 는 Baidu ajax (당연히 구 판 입 니까? 아니면 간략화 되 었 습 니까?) 를 알 게 되 었 습 니 다. 그러면 우 리 는 이 간단 한 ajax 방법 에 어떤 기능 을 추가 하고 싶 습 니까?
 
체인 조작
jQuery 니까 체인 조작 문제 부터 해결 해 야 지.
jQuery 의 Deferred 는 비동기 체인 모델 을 실현 할 수 있 습 니 다. Promise 대상 은 쉽게 연결 성공, 실패, 진행 중 세 가지 상태의 리 셋 함 수 를 실현 한 다음 에 상태 코드 를 통 해 서로 다른 함 수 를 리 셋 하면 됩 니 다.
 
하지만 하나의 promise 로 돌아 가 는 것 은 소 용이 없다.
promise 는 비동기 만 처리 할 수 있 습 니 다. 우 리 는 더 유용 한 것 을 되 돌려 야 합 니 다.
jqXHR 는 바로 이런 것 이다. 실제로 그 는 짝 퉁 XHR 의 대상 이다.
이렇게 하면 우 리 는 XHR 대상 의 기능 을 확장 하고 일정한 잘못 사용 처 리 를 제공 하여 외부 에 노출 시 키 는 방법 을 제공 할 수 있다.
//   xhr,     xhr……╮(╯▽╰)╭
//           
//       xhr    
//            
jqXHR = {
    //     
    readyState: 0,

    //
    getResponseHeader: function( key ) {
        var match;
        //      2,  2  ajax  
        if ( state === 2 ) {
            //        
            if ( !responseHeaders ) {
                //      
                responseHeaders = {};
                while ( (match = rheaders.exec( responseHeadersString )) ) {
                    //      
                    responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
                }
            }
            //       key  
            match = responseHeaders[ key.toLowerCase() ];
        }
        //   
        return match == null ? null : match;
    },

    //         
    getAllResponseHeaders: function() {
        //         ,       ,   null
        return state === 2 ? responseHeadersString : null;
    },

    //      
    setRequestHeader: function( name, value ) {
        var lname = name.toLowerCase();
        //   state  0
        if ( !state ) {
            //   requestHeadersNames[ lname ]   ,
            //  requestHeadersNames[ lname ]  ,name     
            //   ,requestHeadersNames[ lname ]  ,
            //  requestHeadersNames[ lname ]   name,
            //
            name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
            //    name   ,          name,     
            //         
            requestHeaders[ name ] = value;
        }
        return this;
    },

    //      content-type
    overrideMimeType: function( type ) {
        if ( !state ) {
            s.mimeType = type;
        }
        return this;
    },

    //           
    statusCode: function( map ) {
        var code;
        //   map  ,    
        if ( map ) {
            //       2,             
            if ( state < 2 ) {
                //   map     code
                for ( code in map ) {
                    //
                    statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
                }
            //     2,       
            } else {
                //   Deferred               
                jqXHR.always( map[ jqXHR.status ] );
            }
        }
        return this;
    },

    //     
    abort: function( statusText ) {
        var finalText = statusText || strAbort;
        //       XHR  ,         XHR  
        if ( transport ) {
            transport.abort( finalText );
        }
        //   done,     
        done( 0, finalText );
        return this;
    }
};

 
근 데 이 건 아직 체인 이 없 잖 아!
어떻게 jqXHR 를 Promise 로 만 듭 니까?
한 상대 에 게 다른 상 대 를 갖 게 하 는 방법 은 무엇 일 까?
상속
아니, 그냥 꽂 으 면 돼.
동태, 약 류 의 특징 을 말 해 야 한다.
무슨 소리 야?Promise 에 있 는 방법 을 jqXHR 대상 에 꽂 으 면 됩 니 다.
    //  jqXHR  promise     ,  jqXHR     promise 
    //       jQuery.extend(jqXHR, promise)  
    // jqXHR    xhr  ,     promise   
    //    jqXHR complete  completeDeferred.add
    //    jqXHR       completeDeferred  Callbacks  
    //   promise     complete    
    //         ,       jqXHR  complete、success、error  
    // Javascript      ,    …… rz
    deferred.promise( jqXHR ).complete = completeDeferred.add;
    //   jqXHR.success promise   done  
    jqXHR.success = jqXHR.done;
    //   jqXHR.error promise   fail  
    jqXHR.error = jqXHR.fail;

이렇게 직접 끼 워 넣 으 면 강 한 언어 에 서 는 할 수 없다. 물론 조합 모드 로 한 대상 이 여러 대상 을 가 진 방법 을 실현 할 수도 있 지만 대상 에 게 여러 가지 방법 을 동적 으로 추가 할 수 는 없다.
강 한 언어 는 대상 이 평생 '할 수 있다' 는 몇 가지 '방법' 만 제한 할 수 있 고 자바 script 의 대상 은 생명 주기 내 에 각종 '방법' 을 마음대로 배 울 수 있다.
그래서 강 한 언어 와 자바 script 의 대상 모델 은 차이 가 있 고 디자인 모델 을 자바 script 에 억지로 끼 워 넣 는 것 은 잘못된 것 일 수 있 습 니 다.
예 를 들 어 자바 script 조합 모드 로 위의 코드 를 다시 쓰 면 다음 과 같 을 수 있 습 니 다.
//       deferred completeDeferred
function JQXHR(){
    //   jqXHR      
    for(i in deferred.promise){
        this[i] = this.promise[i];
    }
    this.complete = completeDeferred.add;
    this.success = this.done;
    this.error = this.done
}

var jqXHR = new JQXHR();

어떤 게 좋 을까요?
위의 모습 으로 변 한 것 은 주로 다음 두 가지 문 제 를 해결 해 야 하기 때문이다.
  • jqXHR 는 외부 에 노출 되 어 있 기 때문에 deferred 와 complete Deferred 를 포함 할 수 없고 사용자 가 외부 에서 이 두 대상 을 조작 하 는 상 태 를 허용 하지 않 습 니 다.
  • deferred 와 complete Deferred 도 jqXHR 의 개인 변수 가 될 수 없습니다. ajax 이벤트 가 촉발 되 어야 하기 때 문 입 니 다.

  •  
    UI 가 ajax 상태 에 따라 변경 할 수 있 도록 전역 이 벤트 를 제공 합 니 다.
    여 기 는 주로 jQuery. event. trigger 와 jQuery. fn. trigger 를 이용 하여 사건 을 모 의 했 습 니 다.
        //
        if ( fireGlobals && jQuery.active++ === 0 ) {
            //    jQuery.event.trigger    
            jQuery.event.trigger("ajaxStart");
        }

    ajax 가 시 작 될 때 전역 이 벤트 를 모 의 합 니 다. ajax Start.
            //     ,           ajaxSend
            if ( fireGlobals ) {
                globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
            }

    ajax 에서 메 시 지 를 보 내 면 ajax Send 를 촉발 합 니 다.
            //           
            if ( fireGlobals ) {
                //          ajaxSuccess  ajaxError
                globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
                    [ jqXHR, s, isSuccess ? success : error ] );
            }
    
            //       Callbacks  
            completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
    
            //           
            if ( fireGlobals ) {
                //          ajaxComplete
                globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
                //  ajax    ,  active 1,   0,    ajax  
                if ( !( --jQuery.active ) ) {
                    //   ajaxStop  
                    jQuery.event.trigger("ajaxStop");
                }
            }    

    끝 날 때 ajax Success 나 ajax Error 를 촉발 하고 ajax Complete 를 출발 합 니 다. 모든 ajax 가 끝나 면 ajax Stop 을 촉발 합 니 다.
     
     
    캐 시 데 이 터 를 사용 할 수 있 는 지 여부
        //      content
        //            
        if ( !s.hasContent ) {
    
            //   data  ,       
            if ( s.data ) {
                //    cacheURL 
                cacheURL = ( s.url += ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + s.data );
                //                
                delete s.data;
            }
    
            //                 
            if ( s.cache === false ) {
            
                s.url = rts.test( cacheURL ) ?
    
                    //      _  ,       
                    cacheURL.replace( rts, "$1_=" + ajax_nonce++ ) :
    
                    //       _ = xxx URL  
                    cacheURL + ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ajax_nonce++;
            }
        }

    주소 에 인자 추가 하기 =캐 시 를 피하 기 위해 xxx.
        //         If-Modified-Since If-None-Match   
        if ( s.ifModified ) {
            if ( jQuery.lastModified[ cacheURL ] ) {
                jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] );
            }
            if ( jQuery.etag[ cacheURL ] ) {
                jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] );
            }
        }

    그리고 If - Modified - Since 와 If - None - Match 헤드 정 보 를 설정 합 니 다.
        //          If-Modified-Since If-None-Match 
        if ( s.ifModified ) {
            //   Last-Modified
            modified = jqXHR.getResponseHeader("Last-Modified");
            //   Last-Modified  
            if ( modified ) {
                //  jQuery.lastModified[cacheURL]  Last-Modified
                jQuery.lastModified[ cacheURL ] = modified;
            }
            //   etag
            modified = jqXHR.getResponseHeader("etag");
            //   etag  
            if ( modified ) {
                //  jQuery.etag[cacheURL]  etag
                jQuery.etag[ cacheURL ] = modified;
            }
        }

    캐 시 If - Modified - Since 와 If - None - Match 헤드 정보.
     
    설정 시간 초과
            //
            if ( s.async && s.timeout > 0 ) {
                //     
                timeoutTimer = setTimeout(function() {
                    jqXHR.abort("timeout");
                }, s.timeout );
            }

    주요 기능 분석 이 끝 났 습 니 다. 전체 비고 코드 는 다음 과 같 습 니 다. 
     
    전체 메모
    jQuery.ajax = function( url, options ) {
    
        //   url   obj,  1.5       
        if ( typeof url === "object" ) {
            options = url;
            url = undefined;
        }
    
        //   options
        options = options || {};
    
        var transport,
            //   cacheURL
            cacheURL,
            //    
            responseHeadersString,
            responseHeaders,
            //      
            timeoutTimer,
            //       
            parts,
            //           
            fireGlobals,
            //     
            i,
            //   jQuery.ajaxSetup      
            s = jQuery.ajaxSetup( {}, options ),
            //        ,     this
            callbackContext = s.context || s,
            //                 
            //  s.context,  DOM  ,  jQuery   
            globalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ?
                //   jQuery  
                jQuery( callbackContext ) :
                //    jQuery.event
                jQuery.event,
            //     deferred
            deferred = jQuery.Deferred(),
            // deferred    Callbacks  
            completeDeferred = jQuery.Callbacks("once memory"),
            //           
            statusCode = s.statusCode || {},
            //    
            requestHeaders = {},
            requestHeadersNames = {},
            //    jqXHR   
            state = 0,
            //       
            strAbort = "canceled",
            //   xhr,     xhr……╮(╯▽╰)╭
            //           
            //       xhr    
            //            
            jqXHR = {
                //     
                readyState: 0,
    
                //
                getResponseHeader: function( key ) {
                    var match;
                    //      2,  2  ajax  
                    if ( state === 2 ) {
                        //        
                        if ( !responseHeaders ) {
                            //      
                            responseHeaders = {};
                            while ( (match = rheaders.exec( responseHeadersString )) ) {
                                //      
                                responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
                            }
                        }
                        //       key  
                        match = responseHeaders[ key.toLowerCase() ];
                    }
                    //   
                    return match == null ? null : match;
                },
    
                //         
                getAllResponseHeaders: function() {
                    //         ,       ,   null
                    return state === 2 ? responseHeadersString : null;
                },
    
                //      
                setRequestHeader: function( name, value ) {
                    var lname = name.toLowerCase();
                    //   state  0
                    if ( !state ) {
                        //   requestHeadersNames[ lname ]   ,
                        //  requestHeadersNames[ lname ]  ,name     
                        //   ,requestHeadersNames[ lname ]  ,
                        //  requestHeadersNames[ lname ]   name,
                        //
                        name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
                        //    name   ,          name,     
                        //         
                        requestHeaders[ name ] = value;
                    }
                    return this;
                },
    
                //      content-type
                overrideMimeType: function( type ) {
                    if ( !state ) {
                        s.mimeType = type;
                    }
                    return this;
                },
    
                //           
                statusCode: function( map ) {
                    var code;
                    //   map  ,    
                    if ( map ) {
                        //       2,             
                        if ( state < 2 ) {
                            //   map     code
                            for ( code in map ) {
                                //
                                statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
                            }
                        //     2,       
                        } else {
                            //   Deferred               
                            jqXHR.always( map[ jqXHR.status ] );
                        }
                    }
                    return this;
                },
    
                //     
                abort: function( statusText ) {
                    var finalText = statusText || strAbort;
                    //       XHR  ,         XHR  
                    if ( transport ) {
                        transport.abort( finalText );
                    }
                    //   done,     
                    done( 0, finalText );
                    return this;
                }
            };
    
        //  jqXHR  promise     ,  jqXHR     promise 
        //       jQuery.extend(jqXHR, promise)  
        // jqXHR    xhr  ,     promise   
        //    jqXHR complete  completeDeferred.add
        //    jqXHR       completeDeferred  Callbacks  
        //   promise     complete    
        //         ,       jqXHR  complete、success、error  
        // Javascript      ,    …… rz
        deferred.promise( jqXHR ).complete = completeDeferred.add;
        //   jqXHR.success promise   done  
        jqXHR.success = jqXHR.done;
        //   jqXHR.error promise   fail  
        jqXHR.error = jqXHR.fail;
    
        //   url  ,       。    #          
        //   http://127.0.0.1#main,    #main
        //      //,         ,                
        //   //127.0.0.1,  http://127.0.0.1
        s.url = ( ( url || s.url || ajaxLocation ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" );
    
        //   type,    
        s.type = options.method || options.type || s.method || s.type;
    
        //         
        //     "*",
        //
        s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().match( core_rnotwhite ) || [""];
    
        //    、  、         ,          
        if ( s.crossDomain == null ) {
            //     url
            parts = rurl.exec( s.url.toLowerCase() );
            //        
            s.crossDomain = !!( parts &&
                ( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] ||
                    ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) !=
                        ( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) )
            );
        }
    
        //   data         ,        
        if ( s.data && s.processData && typeof s.data !== "string" ) {
            //    
            s.data = jQuery.param( s.data, s.traditional );
        }
    
        //    
        inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
    
        //      prefilter  ,   
        if ( state === 2 ) {
            return jqXHR;
        }
    
        //             
        fireGlobals = s.global;
    
        //
        if ( fireGlobals && jQuery.active++ === 0 ) {
            //    jQuery.event.trigger    
            jQuery.event.trigger("ajaxStart");
        }
    
        //      
        s.type = s.type.toUpperCase();
    
        //         content
        s.hasContent = !rnoContent.test( s.type );
    
        //   URL,       If-Modified-Since If-None-Match
        cacheURL = s.url;
    
        //      content
        //            
        if ( !s.hasContent ) {
    
            //   data  ,       
            if ( s.data ) {
                //    cacheURL 
                cacheURL = ( s.url += ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + s.data );
                //                
                delete s.data;
            }
    
            //                 
            if ( s.cache === false ) {
            
                s.url = rts.test( cacheURL ) ?
    
                    //      _  ,       
                    cacheURL.replace( rts, "$1_=" + ajax_nonce++ ) :
    
                    //       _ = xxx URL  
                    cacheURL + ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ajax_nonce++;
            }
        }
    
        //         If-Modified-Since If-None-Match   
        if ( s.ifModified ) {
            if ( jQuery.lastModified[ cacheURL ] ) {
                jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] );
            }
            if ( jQuery.etag[ cacheURL ] ) {
                jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] );
            }
        }
    
        //
        if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
            jqXHR.setRequestHeader( "Content-Type", s.contentType );
        }
    
        //   dataType,    Accept 
        jqXHR.setRequestHeader(
            "Accept",
            s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
                s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
                s.accepts[ "*" ]
        };
    
        //   s.headers,           
        for ( i in s.headers ) {
            jqXHR.setRequestHeader( i, s.headers[ i ] );
        }
    
        //   beforeSend        
        if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
            //   
            return jqXHR.abort();
        }
    
        //   abort      ajax,     ajax
        strAbort = "abort";
    
        //  jqXHR     、  、      
        for ( i in { success: 1, error: 1, complete: 1 } ) {
            jqXHR[ i ]( s[ i ] );
        }
    
        //   transport
        transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
    
        //
        if ( !transport ) {
            done( -1, "No Transport" );
        } else {
            //   ,  reayState 1
            jqXHR.readyState = 1;
            
            //     ,           ajaxSend
            if ( fireGlobals ) {
                globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
            }
            //
            if ( s.async && s.timeout > 0 ) {
                //     
                timeoutTimer = setTimeout(function() {
                    jqXHR.abort("timeout");
                }, s.timeout );
            }
    
            try {
                //   state 1
                state = 1;
                //     
                transport.send( requestHeaders, done );
            } catch ( e ) {
                //     ,  ajax   
                if ( state < 2 ) {
                    done( -1, e );
                //           
                } else {
                    throw e;
                }
            }
        }
    
        //         
        function done( status, nativeStatusText, responses, headers ) {
            var isSuccess, success, error, response, modified,
                statusText = nativeStatusText;
    
            //
            if ( state === 2 ) {
                return;
            }
    
            //          
            state = 2;
    
            //       
            if ( timeoutTimer ) {
                clearTimeout( timeoutTimer );
            }
            //   jqXHR        ,
            //   transport              
            transport = undefined;
    
            //      
            responseHeadersString = headers || "";
    
            //   readyState
            jqXHR.readyState = status > 0 ? 4 : 0;
    
            //       
            if ( responses ) {
                //   ajaxHandleResponses    
                response = ajaxHandleResponses( s, jqXHR, responses );
            }
    
            // If successful, handle type chaining
            //     
            if ( status >= 200 && status < 300 || status === 304 ) {
    
                //         If-Modified-Since If-None-Match 
                if ( s.ifModified ) {
                    //   Last-Modified
                    modified = jqXHR.getResponseHeader("Last-Modified");
                    //   Last-Modified  
                    if ( modified ) {
                        //  jQuery.lastModified[cacheURL]  Last-Modified
                        jQuery.lastModified[ cacheURL ] = modified;
                    }
                    //   etag
                    modified = jqXHR.getResponseHeader("etag");
                    //   etag  
                    if ( modified ) {
                        //  jQuery.etag[cacheURL]  etag
                        jQuery.etag[ cacheURL ] = modified;
                    }
                }
    
                //       
                if ( status === 304 ) {
                    //     
                    isSuccess = true;
                    //      notmodified
                    statusText = "notmodified";
    
                //          
                } else {
                    isSuccess = ajaxConvert( s, response );
                    statusText = isSuccess.state;
                    success = isSuccess.data;
                    error = isSuccess.error;
                    isSuccess = !error;
                }
            //     
            } else {
                //  statusText  error  
                //    statusText "error"
                //    status 0
                error = statusText;
                if ( status || !statusText ) {
                    statusText = "error";
                    if ( status < 0 ) {
                        status = 0;
                    }
                }
            }
    
            //       xhr  jqXHR status statusText
            jqXHR.status = status;
            jqXHR.statusText = ( nativeStatusText || statusText ) + "";
    
            //         , deferred    
            if ( isSuccess ) {
                deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );    
            } else {
                deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
            }
    
            //     statusCode  
            jqXHR.statusCode( statusCode );
            statusCode = undefined;
    
            //           
            if ( fireGlobals ) {
                //          ajaxSuccess  ajaxError
                globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
                    [ jqXHR, s, isSuccess ? success : error ] );
            }
    
            //       Callbacks  
            completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
    
            //           
            if ( fireGlobals ) {
                //          ajaxComplete
                globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
                //  ajax    ,  active 1,   0,    ajax  
                if ( !( --jQuery.active ) ) {
                    //   ajaxStop  
                    jQuery.event.trigger("ajaxStop");
                }
            }
        }
    
        //     xhr
        return jqXHR;
    };

     

    좋은 웹페이지 즐겨찾기