Service Worker 是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。使用 Service Worker 的话,传输协议必须为 HTTPS。因为 Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。
Service Worker 实现缓存功能一般分为三个步骤:首先需要先注册 Service Worker,然后监听到 install 事件以后就可以缓存需要的文件,那么在下次用户访问的时候就可以通过拦截请求的方式查询是否存在缓存,存在缓存的话就可以直接读取缓存文件,否则就去请求数据。以下是这个步骤的实现:
// index.js if (navigator.serviceWorker) { navigator.serviceWorker .register("sw.js") .then(function (registration) { console.log("service worker 注册成功"); }) .catch(function (err) { console.log("servcie worker 注册失败"); }); } // sw.js // 监听 `install` 事件,回调中缓存所需文件 self.addEventListener("install", (e) => { e.waitUntil( caches.open("my-cache").then(function (cache) { return cache.addAll(["./index.html", "./index.js"]); }) ); }); // 拦截所有请求事件 // 如果缓存中已经有请求的数据就直接用缓存,否则去请求数据 self.addEventListener("fetch", (e) => { e.respondWith( caches.match(e.request).then(function (response) { if (response) { return response; } console.log("fetch source"); }) ); });
Proxy 是 ES6 中新增的功能,它可以用来自定义对象中的操作。 Vue3.0 中将会通过 Proxy 来替换原本的 Object.defineProperty 来实现数据响应式。
let p = new Proxy(target, handler);
target
代表需要添加代理的对象,handler
用来自定义对象中的操作,比如可以用来自定义 set 或者 get 函数。
let onWatch = (obj, setBind, getLogger) => { let handler = { set(target, property, value, receiver) { setBind(value, property); return Reflect.set(target, property, value); }, get(target, property, receiver) { getLogger(target, property); return Reflect.get(target, property, receiver); }, }; return new Proxy(obj, handler); }; let obj = { a: 1 }; let p = onWatch( obj, (v, property) => { console.log(`监听到属性${property}改变为${v}`); }, (target, property) => { console.log(`'${property}' = ${target[property]}`); } ); p.a = 2; // 控制台输出:监听到属性a改变 p.a; // 'a' = 2
因为可以自定义 Hooks, 我们可以非常方便的复用状态逻辑。
// 定时器DEMO function useCount(defaultCount) { const [count, setCount] = useState(defaultCount); const timer = useRef(); useEffect(() => { timer.current = setInterval(() => { setCount((count) => count + 1); }, 1000); }, []); useEffect(() => { if (count >= 10) { clearInterval(timer.current); } }); return [count]; } function App() { const [count] = useCount(0); return ( <> <h1>count: {count}</h1> </> ); }
webpack.config.js 是 webpack 的默认打包配置文件。也可以npx webpack --config 配置文件名
手动设置
/** * Wepack配置接口 */ const path = require("path"); module.exports = { // 打包模式 mode: "production", // 入口 entry: "./index.js", // 出口 output: { filename: "bundle.js", // path 后必须是一个绝对位置 path: path.resolve(__dirname, "bundle"), }, };
其中entry: "./index.js"
是一个简写,
entry: { main: "./index.js" }
hash 一般是结合 CDN 缓存来使用,通过 webpack 构建之后,生成对应文件名自动带上对应的 MD5 值。如果文件内容改变的话,那么对应文件哈希值也会改变,对应的 HTML 引用的 URL 地址也会改变,触发 CDN 服务器从源服务器上拉取对应数据,进而更新本地缓存。但是在实际使用的时候,这几种 hash 计算还是有一定区别。
hash
hash 是跟整个项目的构建相关,只要项目里有文件更改,整个项目构建的 hash 值都会更改,并且全部文件都共用相同的 hash 值
chunkhash
采用 hash 计算的话,每一次构建后生成的哈希值都不一样,即使文件内容压根没有改变。这样子是没办法实现缓存效果,我们需要换另一种哈希值计算方式,即 chunkhash。 chunkhash 和 hash 不一样,它根据不同的入口文件(Entry)进行依赖文件解析、构建对应的 chunk,生成对应的哈希值。我们在生产环境里把一些公共库和程序入口文件区分开,单独打包构建,接着我们采用 chunkhash 的方式生成哈希值,那么只要我们不改动公共库的代码,就可以保证其哈希值不会受影响。
contenthash
在使用 chunkhash 的例子中,如果 index.css 被 index.js 引用了,那么就会共用相同的 chunkhash 值。但是这样子有个问题,如果 index.js 更改了代码,css 文件就算内容没有任何改变,由于是该模块发生了改变,导致 css 文件会重复构建。
这个时候,我们可以使用 extra-text-webpack-plugin 里的 contenthash 值,保证即使 css 文件所处的模块里就算其他文件内容改变,只要 css 文件内容不变,那么不会重复构建。