利用Pjax下载动态加载插件方案分享

 更新时间:2022年09月29日 08:41:43   作者:游侠好梦  
在纯静态网站里,有时候会动态更新某个区域往会选择 Pjax(swup、barba.js)去处理。本文主要和大家分享下Pjax下载动态加载插件方案,感兴趣的可以了解一下

在纯静态网站里,有时候会动态更新某个区域往会选择 Pjax(swup、barba.js)去处理,他们都是使用 ajax 和 pushState 通过真正的永久链接,页面标题和后退按钮提供快速浏览体验。

但是实际使用中可能会遇到不同页面可能会需要加载不同插件处理,有些人可能会全量选择加载,这样会导致加载很多无用的脚本,有可能在用户关闭页面时都不一定会访问到,会很浪费资源。

解决思路

首先想到的肯定是在请求到新的页面后,我们手动去比较当前 DOM 和 新 DOM 之间 script 标签的差异,手动给他插入到 body 里。

处理 Script

一般来说 JavaScript 脚本都是放在 body 后,避免阻塞页面渲染,假设我们页面脚本也都是在 body 后,并在 script 添加 [data-reload-script] 表明哪些是需要动态加载的。

首先我们直接获取到带有 [data-reload-script] 属性的 script 标签:

// NewHTML 为 新页面 HTML
const pageContent = NewHTML.replace('<body', '<div id="DynamicPluginBody"').replace('</body>', '</div>');
let element = document.createElement('div');
element.innerHTML = pageContent;
const children = element.querySelector('#DynamicPluginBody').querySelectorAll('script[data-reload-script]');

然后通过创建 script 标签插入到 body

children.forEach(item => {
    const element = document.createElement('script');
    for (const { name, value } of arrayify(item.attributes)) {
        element.setAttribute(name, value);
    }
    element.textContent = item.textContent;
    element.setAttribute('async', 'false');
    document.body.insertBefore(element)
})

如果你的插件都是通过 script 引入,且不需要执行额外的 JavaScript 代码,只需要在 Pjax 钩子函数这样处理就可以了。

执行代码块

实际很多插件不仅仅需要你引入,还需要你手动去初始化做一些操作的。我们可以通过 src 去判断是引入的脚本,还是代码块。

let scripts = Array.from(document.scripts)
let scriptCDN = []
let scriptBlock = []

children.forEach(item => {
    if (item.src)
        scripts.findIndex(s => s.src === item.src) < 0 && scriptCDN.push(item);
    else
        scriptBlock.push(item.innerText)
})

scriptCDN 继续通过上面方式插入到 body 里,然后通过 eval 或者 new Function 去执行 scriptBlock 。因为 scriptBlock 里的代码可能是会依赖 scriptCDN 里的插件的,所以需要在 scriptCDN 加载完成后在执行 scriptBlock 。

const loadScript = (item) => {
    return new Promise((resolve, reject) => {
        const element = document.createElement('script');
        for (const { name, value } of arrayify(item.attributes)) {
            element.setAttribute(name, value);
        }
        element.textContent = item.textContent;
        element.setAttribute('async', 'false');
        element.onload = resolve
        element.onerror = reject
        document.body.insertBefore(element)
    })
}

const runScriptBlock = (code) => {
    try {
        const func = new Function(code);
        func()
    } catch (error) {
        try {
            window.eval(code)
        } catch (error) {
        }
    }
}

Promise.all(scriptCDN.map(item => loadScript(item))).then(_ => {
    scriptBlock.forEach(code => {
        runScriptBlock(code)
    })
})

卸载插件

按照上面思去处理之后,会存在一个问题。 比如:我们添加了一个 全局的 'resize' 事件的监听,在跳转其他页面时候我们需要移除这个监听事件。

这个时候我们需要对代码块的格式进行一个约束,比如像下面这样,在初次加载时执行 mount 里代码,页面卸载时执行 unmount 里代码。

<script data-reload-script>
    DynamicPlugin.add({
        // 页面加载时执行
        mount() {
            this.timer = setInterval(() => {
                document.getElementById('time').innerText = new Date().toString()
            }, 1000)
        },
        // 页面卸载时执行
        unmount() {
            window.clearInterval(this.timer)
            this.timer = null
        }
    })
</script>

DynamicPlugin 大致结构:

let cacheMount = []
let cacheUnMount = []
let context = {}

class DynamicPlugin {
    add(options) {
        if (isFunction(options))
            cacheMount.push(options)

        if (isPlainObject(options)) {
            let { mount, unmount } = options
            if (isFunction(mount))
                cacheMount.push(mount)
            if (isFunction(unmount))
                cacheUnMount.push(unmount)
        }

        // 执行当前页面加载钩子
        this.runMount()
    }

    runMount() {
        while (cacheMount.length) {
            let item = cacheMount.shift();
            item.call(context);
        }
    }

    runUnMount() {
        while (cacheUnMount.length) {
            let item = cacheUnMount.shift();
            item.call(context);
        }
    }
}

页面卸载时调用 DynamicPlugin.runUnMount()。

处理 Head

Head 部分处理来说相对比较简单,可以通过拿到新旧两个 Head,然后循环对比每个标签的 outerHTML,用来判断哪些比是需要新增的哪些是需要删除的。

结尾

本文示例代码完整版本可以 参考这里

到此这篇关于利用Pjax下载动态加载插件方案分享的文章就介绍到这了,更多相关Pjax下载动态加载插件内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 判断用户的在线状态 onbeforeunload事件

    判断用户的在线状态 onbeforeunload事件

    window.event.clientX和window.event.clientY 将捕捉当前事件发生时鼠标相对与窗口的桌面坐标,通常情况下IE的关闭按钮都会在页面的右上部分,所以点关闭的时候鼠标的坐标的Y坐标一定是小于0的
    2011-03-03
  • es6函数中的作用域实例分析

    es6函数中的作用域实例分析

    这篇文章主要介绍了es6函数中的作用域,结合实例形式分析了es6函数作用域相关原理、用法与操作注意事项,需要的朋友可以参考下
    2020-04-04
  • JS模拟实现Select效果代码

    JS模拟实现Select效果代码

    这篇文章主要介绍了JS模拟实现Select效果代码,涉及JavaScript基于鼠标点击事件动态操作页面元素实现Select效果的技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-09-09
  • 收集json解析的四种方法分享

    收集json解析的四种方法分享

    这篇文章主要介绍了json解析的四种方法,有需要的朋友可以参考一下
    2014-01-01
  • html中table数据排序的js代码

    html中table数据排序的js代码

    最近应项目要求,需要对table中的数据进行升序或降序排列,于是研究了一下js,希望对大家有帮助。
    2011-08-08
  • 深入理解JavaScript系列(31):设计模式之代理模式详解

    深入理解JavaScript系列(31):设计模式之代理模式详解

    这篇文章主要介绍了深入理解JavaScript系列(31):设计模式之代理模式详解,代理模式使得代理对象控制具体对象的引用,代理几乎可以是任何对象:文件,资源,内存中的对象,或者是一些难以复制的东西,需要的朋友可以参考下
    2015-03-03
  • Javascript ES6中对象类型Sets的介绍与使用详解

    Javascript ES6中对象类型Sets的介绍与使用详解

    这篇文章主要给大家介绍了关于Javascript ES6中Sets的介绍与使用的相关资料,文中通过示例代码介绍的非常详细,对大家具有一定的参考学习价值,需要的朋友们下面跟着小编来一起学习学习吧。
    2017-07-07
  • 浅谈es6语法 (Proxy和Reflect的对比)

    浅谈es6语法 (Proxy和Reflect的对比)

    下面小编就为大家带来一篇浅谈es6语法 (Proxy和Reflect的对比)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-10-10
  • uni-app 微信小程序授权登录的实现步骤

    uni-app 微信小程序授权登录的实现步骤

    本文主要介绍了uni-app 微信小程序授权登录的实现步骤,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-01-01
  • JavaScript面向对象精要(下部)

    JavaScript面向对象精要(下部)

    这篇文章主要介绍了JavaScript面向对象精要(下部),需要的朋友可以参考下
    2017-09-09

最新评论