local_cache 本地缓存 资源版本管理 js资源缓存

OTT端性能优化建设之本地缓存设计 | 《优酷OTT互联网大屏前端技术实践》第七章-阿里云开发者社区 - Google Chrome

 

local_cache.js
复制代码
// local_cache.js
// 原文地址:https://developer.aliyun.com/article/771774
// 源码地址:https://g.alicdn.com/de/local_cache/0.0.1/page/localStorage/index.js
// 实现原理图: https://ucc.alicdn.com/pic/developer-ecology/236bb9fc0b2c4a28bc1710d093117666.png
var local_cache = (function () {
    const oHead = document.getElementsByTagName('head')[0]
    const _localStorage = window.localStorage || {}
    const nowDateNum = +formatDateTime(false, 'YYYYMMDD')

    // 创建ajax函数
    let ajax = function (_options) {
        const options = _options || {}
        options.type = (options.type || 'GET').toUpperCase()
        options.dataType = options.dataType || 'javascript'
        const params = options.data ? formatParams(options.data) : ''
        

        // 创建-第一步
        let xhr
        if (window.XMLHttpRequest) {
            xhr = new XMLHttpRequest()
        } else {
            xhr = ActiveXObject('Microsoft.XMLHTTP')
        }
        

        // 在响应成功前设置一个定时器(响应超时提示)
        const timer = setTimeout(function () {
            // 让后续的函数停止执行
            xhr.onreadystatechange = null
            console.log('timeout:' + options.url)
            options.error && options.error(status)
        }, options.timeout || 8000)
        

        // 接收-第三步
        xhr.onreadystatechange = function () {
            if (xhr.readyState == 4) {
                clearTimeout(timer)
                const status = xhr.status
                if (status >= 200 && status < 300) {
                    options.success && options.success(xhr.responseText, xhr.responseXML)
                } else {
                    options.error && options.error(status)
                }
            }
        }
        

        // 连接和发送-第二步
        if (options.type == 'GET') {
            xhr.open('GET', options.url + '?' + params, true)
            xhr.send(null)
        }

        if (options.type == 'POST') {
            xhr.open('POST', options.url, true)
            // 设置表单提交时的内容类型
            xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
            xhr.send(params)
        }
    }
    

    // 格式化参数
    function formatParams(data) {
        let arr = []
        for (let name in data) {
            arr.push(encodeURIComponent(name) + '=' + encodeURIComponent(data[name]))
        }
        arr.push(('v=' + Math.random()).replace('.', ''))
        return arr.join('&')
    }
    

    // 时间戳转日期格式
    function formatDateTime(time, format) {
        const t = (time && new Date(time)) || new Date()
        let tf = function (i) {
            return (i < 10 ? '0' : '') + i
        }

        return format.replace(/YYYY|MM|DD|hh|mm|ss/g, function (a) {
            switch (a) {
                case 'YYYY':
                    return tf(t.getFullYear())
                    break
                case 'MM':
                    return tf(t.getMonth() + 1)
                    break
                case 'DD':
                    return tf(t.getDate())
                    break
                case 'hh':
                    return tf(t.getHours())
                    break
                case 'mm':
                    return tf(t.getMinutes())
                    break
                case 'ss':
                    return tf(t.getSeconds())
                    break
            }
        })
    }
    

    function handleError(url, callback, _send) {
        let script = document.createElement('script')
        script.type = 'text/javascript'
        script.onload = script.onreadystatechange = function () {
            if (!this.readyState || this.readyState === 'loaded' || this.readyState === 'complete') {
                console.log('create script loaded : ' + url)
                callback && callback()
                _send && _send()
                script.onload = script.onreadystatechange = null
            }
        }

        script.onerror = function () {
            console.log('create script error : ' + url)
            _send && _send()
            script.onload = null
        }

        script.src = url
        oHead.appendChild(script)
    }
    

    // eval js字符串代码
    function _eval(fnString) {
        window['eval'].call(window, fnString)
    }
    

    function autoDel() {
        if (!_localStorage.setItem) {
            return
        }

        for (let key in _localStorage) {
            const value = key.indexOf('##') > -1 ? _localStorage.getItem(key) : ''
            const isSetExpire = value.split('##').length > 1
            const date = isSetExpire && value.split('##')[1]
            const isExpire = date && nowDateNum > +date.replace(/-/gi, '')
            if (isExpire) {
                _localStorage.removeItem(key)
                _localStorage.removeItem(key + '_data')
                console.log('DEL:' + key + '|' + key + '_data')
            }
        }
    }
     

    function del(lists = []) {
        if (!Array.isArray(lists)) return

        for (let key in lists) {
            _localStorage.removeItem(key)
            _localStorage.removeItem(key + '_data')
            console.log('DEL:' + key + '|' + key + '_data')
        }
    }
    

    let onIndex = -1
    let send = function (list = [], index = 0) {
        if (!Array.isArray(list)) return

        const num = list.length
        if (!num || num < index + 1) {
            autoDel()
            return
        }
        if (index <= onIndex) {
            return
        }

        onIndex = index
        const item_url = list[index].url
        const item_aliases = list[index].aliases
        const callback = list[index].callback
        const isStorage = list[index].storage !== false
        if (!_localStorage) {
            handleError(item_url, callback)
            send(list, index + 1)
            return
        }

        const isDone = item_url === (_localStorage.getItem && _localStorage.getItem(item_aliases))

        if (isDone) {
            const fnString = _localStorage.getItem(item_aliases + '_data')
            try {
                _eval(fnString)
            } catch (e) {
                console.log('eval error')
            }
            callback && callback()
            console.log('local:' + item_aliases)
            send(list, index + 1)
            return
        }

        ajax({
            url: item_url,
            success: function (response, xml) {
                // 请求成功后执行
                try {
                    _eval(response)
                } catch (e) {
                    console.log('eval error')
                }

                callback && callback()

                console.log('ajax:' + item_aliases)
                const isSetExpire = item_url.split('##').length > 1
                const date = isSetExpire && item_url.split('##')[1]
                const isExpire = date && nowDateNum > +date.replace(/-/gi, '')
                if (isStorage && _localStorage.setItem && !isExpire) {
                    try {
                        _localStorage.setItem(item_aliases, item_url)
                        _localStorage.setItem(item_aliases + '_data', response)
                    } catch (oException) {
                        if (oException.name == 'QuotaExceededError') {
                            console.log('超出本地存储限额!')
                            _localStorage.clear()
                            _localStorage.setItem(item_aliases, item_url)
                            _localStorage.setItem(item_aliases + '_data', response)
                        }
                    }
                }
                send(list, index + 1)
            },
            error: function (status) {
                // 失败后执行
                console.log('ajax ' + item_aliases + ' error')
                handleError(item_url.replace('2580', ''), callback, function () {
                    send(list, index + 1)
                })
                setTimeout(function () {
                    send(list, index + 1)
                }, 300)
            },
        })
    }

    return { send, del }
})()
复制代码

 

使用方法
复制代码
local_cache.send([
    {
        aliases: 'jquery',
        url: 'https://cdn.bootcss.com/jquery/1.9.1/jquery.min.js',
    },
    {
        aliases: 'vue',
        url: 'https://cdn.staticfile.org/vue/2.6.9/vue.js',
        callback: () => {
            console.log('vue loaded')
        }
    }
])
复制代码
 
更新版本
复制代码
local_cache.send([
    {
        aliases: 'jquery',
        url: 'https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js',
    },
    {
        aliases: 'vue',
        url: 'https://cdn.staticfile.org/vue/2.6.9/vue.js',
        callback: () => {
            console.log('vue loaded')
            // 启动项目
            init()
        }
    }
])
复制代码
 

更新版本2

其实也可以通过强行注入 ?v=1.7 这种方式来更新。
复制代码
local_cache.send([
    {
        aliases: 'jquery',
        url: 'https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js?v=1.1',
    },
])
复制代码

 

 
删除依赖
local_cache.del(['jquery-1.9.1'])

 

设置过期时间
如果超出过期时间,缓存将被自动删除,并且总是拉取最新的资源。但不再进行缓存了。(暂时不知道使用场景)
复制代码
local_cache.send([
    {
        aliases: 'jquery',
        url: 'https://cdn.bootcss.com/jquery/1.9.1/jquery.min.js##2018-08-10',
    },
])
复制代码

 

禁止缓存
只拉取资源,但不进行缓存。(暂时不知道使用场景,为啥不使用 script:src 直接引入呢?)
复制代码
local_cache.send([
    {
        aliases: 'jquery',
        url: 'https://cdn.bootcss.com/jquery/1.9.1/jquery.min.js##2018-08-10',
        storage: false
    },
])
复制代码

 

 
posted @   贝尔塔猫  阅读(167)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
历史上的今天:
2018-03-04 sass 的学习
点击右上角即可分享
微信分享提示