Javascript PJAX 原理和使用

Javascript PJAX 原理和使用

pjax 即 pushState + ajax,它被封装成了一个 jQuery 扩展以方便使用。pjax 主要用来解决 HTML 页面局部刷新 url 不更新和不支持后退和前进的问题,提升用户体验。

pjax原理

pjax 的实现是利用 HTML5 的 pushState() 和 replaceState() 新特性和 ajax 结合实现。pushState() 和 replaceState() 用来操作 State(状态)对象,即可添加和修改历史记录,进而更新 url 和提供前进、后退操作 ajax 实现数据的异步加载进而局部刷新。

工作流程图

Snipaste_2020-07-30_17-45-04.png

源码分析

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).+OSs+[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回调    $.pjax.submit = handleSubmit   //注册submit回调    $.pjax.reload = pjaxReload     //注册reload回调    $.pjax.defaults = {}           //设置默认值    $(window).on('popstate.pjax', onPjaxPopstate)  //绑定popstate事件回调}

登录后复制

$.noop是一个空方法,不做任何事,即function(){}。popstate.pjax是 JS 事件的命名空间写法,popstate是事件类型,每当激活的历史记录发生变化时(浏览器操作前进、后退按钮、调用 back() 或者 go() 方法),都会触发 popstate 事件,但调用 pushState()、replaceState() 不会触发 popstate 事件。.pjax是该事件的命名空间,这样方便解绑指定命名空间的事件响应,在绑定匿名函数时常使用,例如:this.on(‘click.pjax’, selector, function(event){})。

立即学习“Java免费学习笔记(深入)”;

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)     //调用click回调    })}

登录后复制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响应回调注册     */    //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}

登录后复制回调函数

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)    //环境检测    if (link.tagName.toUpperCase() !== 'A')    ... ...    //绑定pjax:click事件    var clickEvent = $.Event('pjax:click')    $link.trigger(clickEvent, [opts])    //执行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)    //环境检测    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)    }    //执行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    }    //执行pjax    return pjax($.extend(defaults, optionsFor(container, options)))}

登录后复制

4)onPjaxPopstate()

// popstate handler takes care of the back and forward buttonsfunction onPjaxPopstate(event) {     //环境监测     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,执行pjax         pjax(options)     }}

登录后复制

pjax使用

经过上述分析,就可以很容易使用 pjax 了。

客户端

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

options配置

参数名 默认值 说明

timeout650ajax 超时时间(单位 ms),超时后会执行默认的页面跳转,所以超时时间不应过短,不过一般不需要设置pushtrue使用 window.history.pushState 改变地址栏 url(会添加新的历史记录)replacefalse使用 window.history.replaceState 改变地址栏 url(不会添加历史记录)maxCacheLength20缓存的历史页面个数(pjax 加载新页面前会把原页面的内容缓存起来,缓存加载后其中的脚本会再次执行)version
是一个函数,返回当前页面的 pjax-version,即页面中 标签内容。使用 response.setHeader(“X-PJAX-Version”, “”) 设置与当前页面不同的版本号,可强制页面跳转而不是局部刷新scrollTo0页面加载后垂直滚动距离(与原页面保持一致可使过度效果更平滑)type“GET”ajax 的参数,http 请求方式dataType“html”ajax 的参数,响应内容的 Content-Typecontainer
用于查找容器的 CSS 选择器,[container] 参数没有指定时使用urllink.href要跳转的连接,默认 a 标签的 href 属性fragment
使用响应内容的指定部分(css 选择器)填充页面,服务端不进行处理导致全页面请求的时候需要使用该参数,简单的说就是对请求到的页面做截取pjax事件

为了方便扩展,pjax 支持一些预定义的事件。

事件名 支持取消 参数 说明

pjax:click✔options点击按钮时触发。可调用 e.preventDefault() 取消 pjaxapjax:beforeSend✔xhr, optionsajax 执行 beforeSend 函数时触发,可在回调函数中设置额外的请求头参数。可调用 e.preventDefault() 取消 pjaxpjax:start
xhr, optionspjax 开始(与服务器连接建立后触发)pjax:send
xhr, optionspjax:start之后触发pjax:clicked
optionsajax 请求开始后触发pjax:beforeReplace
contents, optionsajax请求成功,内容替换渲染前触发pjax:success
data, status, xhr, options内容替换成功后触发pjax:timeout✔xhr, optionsajax 请求超时后触发。可调用 e.preventDefault() 继续等待 ajax 请求结束pjax:error✔xhr, textStatus, error, optionsajax 请求失败后触发。默认失败后会跳转 url,如要阻止跳转可调用 e.preventDefault()pjax:complete
xhr, textStatus, optionsajax请求结束后触发,不管成功还是失败pjax:end
xhr, optionspjax所有事件结束后触发pjax:popstate

forward / back(前进/后退)pjax:start
null, optionspjax开始pjax:beforeReplace
contents, options内容替换渲染前触发,如果缓存了要导航页面的内容则使用缓存,否则使用pjax加载pjax:end
null, optionspjax结束

客户端通过以下 2 个步骤就可以使用 pjax :

引入jquery 和 jquery.pjax.js注册事件

JS

/** * 方式1 监听按钮父节点事件 */$(document).pjax(selector, [container], options);/** * 方式2 直接监听按钮,可以不用指定容器,默认使用按钮的data-pjax属性值查找容器 */$("a[data-pjax]").pjax();/** * 方式3 主动绑定点击事件监听 */$(document).on('click', 'a', $.pjax.click);$(document).on('click', 'a', function(event) {    //获取container    var container = $(this).closest('[data-pjax-container]');    //click回调    $.pjax.click(event, container);});/** * 方式4 主动绑定表单提交事件监听 */$(document).on('submit', 'form', function(event) {    //获取container    var container = $(this).closest('[data-pjax-container]');    //submit回调    $.pjax.submit(event, container);});/** * 方式5 加载内容到指定容器 */$.pjax({url: this.href, container: '#main'});/** * 方式6 重新加载当前页面容器的内容 */$.pjax.reload('#container');

登录后复制

YII

在 Yii 中,已经将 pjax 封装成了 widgets,故在渲染时如下使用即可:

//view... ...

登录后复制

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 .= "jQuery(document).on($submitEvent, $formSelector, function (event) {jQuery.pjax.submit(event, '#$id', $options);});";    }    $view->registerJs($js);}

登录后复制

服务端

由于只是 HTML5 支持 pjax,所以后端需要做兼容处理。通过 X-PJAX 头信息可得知客户端是否支持 pjax,如果支持,则只返回局部页面,否则 a 链接默认跳转,返回整个页面。

/** * 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回调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. 已经阻止元素发生默认的行为    if (event.isDefaultPrevented())        return    ...    var clickEvent = $.Event('pjax:click')    $(link).trigger(clickEvent, [opts])    // 6. pjax:click事件回调中已经阻止元素发生默认的行为    if (!clickEvent.isDefaultPrevented()) {        pjax(opts)    }}//pjaxfunction 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 方案来实现

推荐教程:《PHP》《JS教程》

以上就是Javascript PJAX 原理和使用的详细内容,更多请关注【创想鸟】其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至253000106@qq.com举报,一经查实,本站将立刻删除。

发布者:PHP中文网,转转请注明出处:https://www.chuangxiangniao.com/p/2726513.html

(0)
上一篇 2025年3月7日 23:32:44
下一篇 2025年3月5日 02:04:27

AD推荐 黄金广告位招租... 更多推荐

相关推荐

发表回复

登录后才能评论