溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Javascript PJAX 原理是什么及如何使用

發(fā)布時間:2020-07-31 10:00:29 來源:億速云 閱讀:616 作者:Leah 欄目:web開發(fā)

本篇文章給大家分享的是有關(guān)Javascript PJAX 原理是什么及如何使用,小編覺得挺實用的,因此分享給大家學習,希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。

pjax 即 pushState + ajax,它被封裝成了一個 jQuery 擴展以方便使用。pjax 主要用來解決 HTML 頁面局部刷新 url 不更新和不支持后退和前進的問題,提升用戶體驗。

pjax原理

pjax 的實現(xiàn)是利用 HTML5 的 pushState() 和 replaceState() 新特性和 ajax 結(jié)合實現(xiàn)。pushState() 和 replaceState() 用來操作 State(狀態(tài))對象,即可添加和修改歷史記錄,進而更新 url 和提供前進、后退操作 ajax 實現(xiàn)數(shù)據(jù)的異步加載進而局部刷新。

工作流程圖

Javascript PJAX 原理是什么及如何使用

源碼分析

  • pjax支持判斷
(function($){
    $.support.pjax =
       window.history && window.history.pushState && window.history.replaceState &&
       // pushState isn't reliable on iOS until 5.
       !navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]\D|WebApps\/.+CFNetwork)/)
    if ($.support.pjax){
       enable()   //啟用
    } else {
       disable()  //禁用
    }
})(jQuery)
  • enable()
function enable() {
    $.fn.pjax = fnPjax             //注冊jQuery的pjax方法
    $.pjax = pjax                  //注冊pjax對象
    $.pjax.enable = $.noop
    $.pjax.disable = disable
    $.pjax.click = handleClick     //注冊click回調(diào)
    $.pjax.submit = handleSubmit   //注冊submit回調(diào)
    $.pjax.reload = pjaxReload     //注冊reload回調(diào)
    $.pjax.defaults = {}           //設置默認值
    $(window).on('popstate.pjax', onPjaxPopstate)  //綁定popstate事件回調(diào)
}

$.noop是一個空方法,不做任何事,即function(){}popstate.pjax是 JS 事件的命名空間寫法,popstate是事件類型,每當激活的歷史記錄發(fā)生變化時(瀏覽器操作前進、后退按鈕、調(diào)用 back() 或者 go() 方法),都會觸發(fā) popstate 事件,但調(diào)用 pushState()、replaceState() 不會觸發(fā) popstate 事件。.pjax是該事件的命名空間,這樣方便解綁指定命名空間的事件響應,在綁定匿名函數(shù)時常使用,例如:this.on('click.pjax', selector, function(event){})

  • fnPjax()

該方法返回一個 jQuery 對象,等同于 $.fn.pjax。

return this.on('click.pjax', selector, function(event) {
    //獲取pjax配置信息
    options = optionsFor(container, options)
    //自動綁定click事件響應
    return this.on('click.pjax', selector, function(event) {
       var opts = options
       if (!opts.container) {
           opts = $.extend({}, options)
           //如果不配置container,則默認獲取data-pjax屬性值對應的
           opts.container = $(this).attr('data-pjax')
       }
       handleClick(event, opts)     //調(diào)用click回調(diào)
    })
}
  • pjax()
// Use it just like $.ajax:
//
//   var xhr = $.pjax({ url: this.href, container: '#main' })
//   console.log( xhr.readyState )
//
// Returns whatever $.ajax returns.
function pjax(options) {
    //獲取設置
    options = $.extend(true, {}, $.ajaxSettings, pjax.defaults, options)
    //判斷檢測
    if (containerType !== 'string')
  
    /**
     * ajax響應回調(diào)注冊
     */
    //beforeSend
    options.beforeSend = function(xhr, settings) {
        //設置pjax頭信息,供后端做兼容處理
        xhr.setRequestHeader('X-PJAX', 'true')
        xhr.setRequestHeader('X-PJAX-Container', options.container)
        //設置超時
    }
    //complete
    options.complete = function(xhr, textStatus) {
       //綁定pjax:complete事件
       fire('pjax:complete', [xhr, textStatus, options])
       //綁定pjax:end事件
       fire('pjax:end', [xhr, options])
    }
    //error
    options.error = function(xhr, textStatus, errorThrown) {
       //綁定pjax:error事件
       fire('pjax:error', [xhr, textStatus, errorThrown, options])
    }
    //success,重點
    options.success = function(data, status, xhr) {
       //判斷檢測
       if (currentVersion && latestVersion && currentVersion !== latestVersion)
       ... ...
       window.history.replaceState(pjax.state, container.title, container.url)
       //綁定pjax:beforeReplace事件
       fire('pjax:beforeReplace', [container.contents, options], {
           state: pjax.state,
           previousState: previousState
       })
       //渲染頁面
       context.html(container.contents)
       //綁定pjax:success事件
       fire('pjax:success', [data, status, xhr, options])
    }
    //初始化ajax
    var xhr = pjax.xhr = $.ajax(options)
    if (xhr.readyState > 0) {
       //緩存頁面cache
       cachePush(pjax.state.id, [options.container, cloneContents(context)])
       //pushState
       window.history.pushState(null, "", options.requestUrl)
       //綁定pjax:start事件
       fire('pjax:start', [xhr, options])
       //綁定pjax:send事件
       fire('pjax:send', [xhr, options])
    }
    //返回jQuery對象
    return pjax.xhr
}
  • 回調(diào)函數(shù)

1) handleClick()

// Examples
//
//   $(document).on('click', 'a', $.pjax.click)
//   // is the same as
//   $(document).pjax('a')
//
// Returns nothing.
function handleClick(event, container, options) {
    options = optionsFor(container, options)
    //環(huán)境檢測
    if (link.tagName.toUpperCase() !== 'A')
    ... ...
    //綁定pjax:click事件
    var clickEvent = $.Event('pjax:click')
    $link.trigger(clickEvent, [opts])
    //執(zhí)行pjax
    pjax(opts)
    //成功則阻止默認行為
    event.preventDefault()
    //綁定pjax:clicked事件
    $link.trigger('pjax:clicked', [opts])
}

2)handleSubmit()

// Examples
//
//  $(document).on('submit', 'form', function(event) {
//    $.pjax.submit(event, '[data-pjax-container]')
//  })
//
// Returns nothing.
function handleSubmit(event, container, options) {
    options = optionsFor(container, options)
    //環(huán)境檢測
    if (form.tagName.toUpperCase() !== 'FORM')
    ... ...
    //默認配置
    var defaults = {
        type: ($form.attr('method') || 'GET').toUpperCase(),
        url: $form.attr('action'),
        container: $form.attr('data-pjax'),
        target: form
    }
    if (defaults.type !== 'GET' && window.FormData !== undefined) {
        //POST時data域
        defaults.data = new FormData(form)
    }
    //執(zhí)行pjax
    pjax($.extend({}, defaults, options))
    //成功則阻止默認行為
    event.preventDefault()
}

3)pjaxReload()

// Reload current page with pjax.
function pjaxReload(container, options) {
    var defaults = {
        //當前url
        url: window.location.href,
        push: false,
        replace: true,
        scrollTo: false
    }
    //執(zhí)行pjax
    return pjax($.extend(defaults, optionsFor(container, options)))
}

4)onPjaxPopstate()

// popstate handler takes care of the back and forward buttons
function onPjaxPopstate(event) {
     //環(huán)境監(jiān)測
     if (state && state.container)
     ... ...
     //獲取頁面cache
     var cache = cacheMapping[state.id] || []
     //綁定pjax:popstate事件
     var popstateEvent = $.Event('pjax:popstate', {
         state: state,
         direction: direction
     })
     container.trigger(popstateEvent)
     if (contents) {
         //有頁面cache,直接渲染頁面
         //綁定pjax:start事件
     container.trigger('pjax:start', [null, options])
     //綁定pjax:beforeReplace事件
     var beforeReplaceEvent = $.Event('pjax:beforeReplace', {
              state: state,
              previousState: previousState
         })
         container.trigger(beforeReplaceEvent, [contents, options])
         //渲染頁面
         container.html(contents)
         //綁定pjax:end事件
         container.trigger('pjax:end', [null, options])
     } else {
         //無頁面cache,執(zhí)行pjax
         pjax(options)
     }
}

pjax使用

經(jīng)過上述分析,就可以很容易使用 pjax 了。

客戶端

pjax 支持 options 配置和事件機制。

  • options配置
參數(shù)名默認值說明
timeout650ajax 超時時間(單位 ms),超時后會執(zhí)行默認的頁面跳轉(zhuǎn),所以超時時間不應過短,不過一般不需要設置
pushtrue使用 window.history.pushState 改變地址欄 url(會添加新的歷史記錄)
replacefalse使用 window.history.replaceState 改變地址欄 url(不會添加歷史記錄)
maxCacheLength20緩存的歷史頁面?zhèn)€數(shù)(pjax 加載新頁面前會把原頁面的內(nèi)容緩存起來,緩存加載后其中的腳本會再次執(zhí)行)
version
是一個函數(shù),返回當前頁面的 pjax-version,即頁面中 標簽內(nèi)容。使用 response.setHeader(“X-PJAX-Version”, “”) 設置與當前頁面不同的版本號,可強制頁面跳轉(zhuǎn)而不是局部刷新
scrollTo0頁面加載后垂直滾動距離(與原頁面保持一致可使過度效果更平滑)
type“GET”ajax 的參數(shù),http 請求方式
dataType“html”ajax 的參數(shù),響應內(nèi)容的 Content-Type
container
用于查找容器的 CSS 選擇器,[container] 參數(shù)沒有指定時使用
urllink.href要跳轉(zhuǎn)的連接,默認 a 標簽的 href 屬性
fragment
使用響應內(nèi)容的指定部分(css 選擇器)填充頁面,服務端不進行處理導致全頁面請求的時候需要使用該參數(shù),簡單的說就是對請求到的頁面做截取
  • pjax事件

為了方便擴展,pjax 支持一些預定義的事件。

事件名支持取消參數(shù)說明
pjax:click?options點擊按鈕時觸發(fā)。可調(diào)用 e.preventDefault() 取消 pjaxa
pjax:beforeSend?xhr, optionsajax 執(zhí)行 beforeSend 函數(shù)時觸發(fā),可在回調(diào)函數(shù)中設置額外的請求頭參數(shù)??烧{(diào)用 e.preventDefault() 取消 pjax
pjax:start
xhr, optionspjax 開始(與服務器連接建立后觸發(fā))
pjax:send
xhr, optionspjax:start之后觸發(fā)
pjax:clicked
optionsajax 請求開始后觸發(fā)
pjax:beforeReplace
contents, optionsajax請求成功,內(nèi)容替換渲染前觸發(fā)
pjax:success
data, status, xhr, options內(nèi)容替換成功后觸發(fā)
pjax:timeout?xhr, optionsajax 請求超時后觸發(fā)??烧{(diào)用 e.preventDefault() 繼續(xù)等待 ajax 請求結(jié)束
pjax:error?xhr, textStatus, error, optionsajax 請求失敗后觸發(fā)。默認失敗后會跳轉(zhuǎn) url,如要阻止跳轉(zhuǎn)可調(diào)用 e.preventDefault()
pjax:complete
xhr, textStatus, optionsajax請求結(jié)束后觸發(fā),不管成功還是失敗
pjax:end
xhr, optionspjax所有事件結(jié)束后觸發(fā)
pjax:popstate

forward / back(前進/后退)
pjax:start
null, optionspjax開始
pjax:beforeReplace
contents, options內(nèi)容替換渲染前觸發(fā),如果緩存了要導航頁面的內(nèi)容則使用緩存,否則使用pjax加載
pjax:end
null, optionspjax結(jié)束

客戶端通過以下 2 個步驟就可以使用 pjax :

  1. 引入jquery 和 jquery.pjax.js
  2. 注冊事件
JS
<script src="jquery.pjax.js"></script>

/**
 * 方式1 監(jiān)聽按鈕父節(jié)點事件
 */
$(document).pjax(selector, [container], options);
/**
 * 方式2 直接監(jiān)聽按鈕,可以不用指定容器,默認使用按鈕的data-pjax屬性值查找容器
 */
$("a[data-pjax]").pjax();
/**
 * 方式3 主動綁定點擊事件監(jiān)聽
 */
$(document).on('click', 'a', $.pjax.click);
$(document).on('click', 'a', function(event) {
    //獲取container
    var container = $(this).closest('[data-pjax-container]');
    //click回調(diào)
    $.pjax.click(event, container);
});
/**
 * 方式4 主動綁定表單提交事件監(jiān)聽
 */
$(document).on('submit', 'form', function(event) {
    //獲取container
    var container = $(this).closest('[data-pjax-container]');
    //submit回調(diào)
    $.pjax.submit(event, container);
});
/**
 * 方式5 加載內(nèi)容到指定容器
 */
$.pjax({url: this.href, container: '#main'});
/**
 * 方式6 重新加載當前頁面容器的內(nèi)容
 */
$.pjax.reload('#container');
YII

在 Yii 中,已經(jīng)將 pjax 封裝成了 widgets,故在渲染時如下使用即可:

//view
<?php Pjax::begin(); ?>
... ...
<?php Pjax::end(); ?>

pjax 封裝成的 widgets 源碼文件widgets/Pjax.php ,事件注冊部分如下:

public function registerClientScript()
{
    //a標簽的click
    if ($this->linkSelector !== false) {
        $linkSelector = Json::htmlEncode($this->linkSelector !== null ? $this->linkSelector : '#' . $id . ' a');
        $js .= "jQuery(document).pjax($linkSelector, \"#$id\", $options);";
    }
    //form表單的submit
    if ($this->formSelector !== false) {
        $formSelector = Json::htmlEncode($this->formSelector !== null ? $this->formSelector : '#' . $id . ' form[data-pjax]');
        $submitEvent = Json::htmlEncode($this->submitEvent);
        $js .= "\njQuery(document).on($submitEvent, $formSelector, function (event) {jQuery.pjax.submit(event, '#$id', $options);});";
    }
    $view->registerJs($js);
}

服務端

由于只是 HTML5 支持 pjax,所以后端需要做兼容處理。通過 X-PJAX 頭信息可得知客戶端是否支持 pjax,如果支持,則只返回局部頁面,否則 a 鏈接默認跳轉(zhuǎn),返回整個頁面。

/**
 * IndexController示例
 */
public function actionIndex() {
    $dataProvider = new CActiveDataProvider('Article', array(
        'criteria' => array('order' => 'create_time DESC')
    ));
    //存在X-Pjax頭,支持pjax
    if (Yii::$app->getRequest()->getHeaders()->get('X-Pjax')) {
        //返回局部頁面
        $this->renderPartial('index', array(
            'dataProvider' => $dataProvider,
        ));
    } else {
        //返回整個頁面
        $this->render('index', array(
            'dataProvider' => $dataProvider,
        ));
    }
}

pjax失效情況

在以下 9 種情況時候 pjax 會失效,源碼部分如下:

//click回調(diào)
function handleClick(event, container, options) {
    ...
    // 1. 點擊的事件源不是a標簽。a標簽可以對舊版本瀏覽器的兼容,因此不建議使用其他標簽注冊事件
    if (link.tagName.toUpperCase() !== 'A')
        throw "$.fn.pjax or $.pjax.click requires an anchor element"
    // 2. 使用鼠標滾輪點擊、點擊超鏈接的同時按下Shift、Ctrl、Alt和Meta
    if (event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)
        return
    // 3. 跨域
    if (location.protocol !== link.protocol || location.hostname !== link.hostname)
        return
    // 4. 當前頁面的錨點定位
    if (link.href.indexOf('#') > -1 && stripHash(link) == stripHash(location))
        return
    // 5. 已經(jīng)阻止元素發(fā)生默認的行為
    if (event.isDefaultPrevented())
        return
    ...
    var clickEvent = $.Event('pjax:click')
    $(link).trigger(clickEvent, [opts])
    // 6. pjax:click事件回調(diào)中已經(jīng)阻止元素發(fā)生默認的行為
    if (!clickEvent.isDefaultPrevented()) {
        pjax(opts)
    }
}
//pjax
function pjax(options) {
    options.beforeSend = function(xhr, settings) {
        //7. ajx超時
       timeoutTimer = setTimeout(function() {
       if (fire('pjax:timeout', [xhr, options]))
           xhr.abort('timeout')
       }, settings.timeout)
    }
    options.success = function(data, status, xhr) {
    //8. 當前頁面和請求的新頁面版本不一致
    if (currentVersion && latestVersion && currentVersion !== latestVersion) {
       return
    }
    //9. ajax失敗
    context.html(container.contents)
}

其他方案

除了使用 pjax 解決局部刷新并支持前進和后退問題外,也可以使用 browserstate/history.js + ajax 方案來實現(xiàn)

以上就是Javascript PJAX 原理是什么及如何使用,小編相信有部分知識點可能是我們?nèi)粘9ぷ鲿姷交蛴玫降?。希望你能通過這篇文章學到更多知識。更多詳情敬請關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI