前端老铁别懵圈:shim 和 polyfill 到底有啥不一样?(附避坑指南)
- 前端老铁别懵圈:shim 和 polyfill 到底有啥不一样?(附避坑指南)
- 先整一句人话:这俩都是“背锅侠”,但背法不一样
- 从“想喝可乐”到“自己造汽水”——概念先掰扯清楚
- 1. shim:接口翻译官,老鸨子接新客
- 2. polyfill:功能克隆兵,缺啥造啥
- 实战场景:到底什么时候上哪道菜?
- 场景 1:产品经理非要你在 H5 里裁剪图片,而且——“兼容到安卓 4.4”
- 场景 2:后台小哥扔给你一段 swagger 生成的 TS 代码,满屏 `async/await`,IE11 当场去世
- 场景 3:Safari 报“can't find variable: IntersectionObserver”,懒加载模块全崩
- 打包体积爆炸?教你三招“瘦身术”
- 1. 别一上来就 `import 'core-js';` 全量!
- 2. 打包两层策略:现代浏览器走“干净 bundle”,老旧走“带 polyfill”
- 3. 动态 polyfill 服务:polyfill.io 真香
- 踩坑合集:那些年一起熬过的夜
- 坑 1:重复引用 Promise polyfill,导致 `instanceof Promise === false`
- 坑 2:shim 把原生方法覆盖了,性能雪崩
- 坑 3:WeakMap polyfill 内存泄漏
- 坑 4:babel-runtime 和 @babel/plugin-transform-runtime 混用
- 手写一个“最小但能用”的 fetch shim,拿去忽悠面试官
- 一句话总结:别再把它们当“同义词”
前端老铁别懵圈:shim 和 polyfill 到底有啥不一样?(附避坑指南)
先整一句人话:这俩都是“背锅侠”,但背法不一样
早几年我在一家做政务系统的公司混,甲方爸爸一句“必须兼容 IE9”,我当场原地裂开。那时候天天跟这俩哥们打交道,咖啡当水喝,console 当聊天框。shim 和 polyfill 就像夜班同事:一个帮你“翻译”甲方需求,一个直接“造假”给你把功能硬怼出来。今天咱就把当年踩过的坑、熬过的夜、骂过的娘,统统打包成一篇口水文,能救一个是一个。
从“想喝可乐”到“自己造汽水”——概念先掰扯清楚
1. shim:接口翻译官,老鸨子接新客
shim 的核心思想是“接口不变,底层偷换”。浏览器不认新 API?行,我给它整一层“塑料普通话”,让它听懂。
举个最小白的例子:IE 没有Array.prototype.forEach,我们给它糊一层:
// 这就是纯血 shim:代码里继续 forEach,老浏览器也能跑if(!Array.prototype.forEach){Array.prototype.forEach=function(callback,thisArg){// 老 IE 不认箭头函数,咱就写 ES3 语法for(vari=0;i<this.length;i++){// 经典防坑:跳过稀疏数组的 undefinedif(iinthis){callback.call(thisArg,this[i],i,this);}}};}看见没?语法糖照常用,背后全是“老鸨子”在接客,客户(新 API)爽了,浏览器(旧环境)也以为自己在玩老招式。
2. polyfill:功能克隆兵,缺啥造啥
polyfill 的哲学简单粗暴——“你没有,我给你 3D 打印一个”。
继续拿 IE 开涮:IE 全家没有Promise,那就从零搓一个:
// 超缩略版 Promise polyfill,能跑但不严谨,仅演示思路(function(global){functionMyPromise(executor){varself=this;self.state='pending';self.value=undefined;self.callbacks=[];functionresolve(result){if(self.state!=='pending')return;self.state='fulfilled';self.value=result;// 异步模拟微任务,真项目别这么写!setTimeout(function(){self.callbacks.forEach(function(cb){cb.onFulfilled(result);});},0);}functionreject(reason){if(self.state!=='pending')return;self.state='rejected';self.value=reason;setTimeout(function(){self.callbacks.forEach(function(cb){cb.onRejected(reason);});},0);}executor(resolve,reject);}MyPromise.prototype.then=function(onFulfilled,onRejected){varself=this;returnnewMyPromise(function(resolve,reject){self.callbacks.push({onFulfilled:function(val){try{varx=onFulfilled?onFulfilled(val):val;resolve(x);}catch(e){reject(e);}},onRejected:function(reason){try{varx=onRejected?onRejected(reason):reason;reject(x);}catch(e){reject(e);}}});});};// 最后一步:挂到全局,假装自己是原生global.Promise=MyPromise;})(window);看到区别没?polyfill 直接把缺失的“功能器官”移植进去, shim 只是“语言翻译”。一个动刀子,一个动嘴皮子。
实战场景:到底什么时候上哪道菜?
场景 1:产品经理非要你在 H5 里裁剪图片,而且——“兼容到安卓 4.4”
安卓 4.4 的 WebView 连canvas.toBlob都不给,咋整?
思路:先找 shim,不行再上 polyfill。
// 1. 先 shim:把 toBlob 转成 toDataURL 再转 Blobif(!HTMLCanvasElement.prototype.toBlob){Object.defineProperty(HTMLCanvasElement.prototype,'toBlob',{value:function(callback,type,quality){vardataURL=this.toDataURL(type,quality);varbin=atob(dataURL.split(',')[1]);vararr=[];for(vari=0;i<bin.length;i++){arr.push(bin.charCodeAt(i));}varblob=newBlob([newUint8Array(arr)],{type:type||'image/png'});callback(blob);}});}一行业务代码不用改,直接canvas.toBlob(blob => {...}),老 WebView 也能跑。
这就是 shim 的爽点:不动业务,只动底层。
场景 2:后台小哥扔给你一段 swagger 生成的 TS 代码,满屏async/await,IE11 当场去世
async/await语法糖背后是 generator + Promise。IE11 连 Promise 都没有,generator 更别聊。
这种时候就别想 shim 了,必须整 polyfill 全家桶:
- 先装依赖
npmi core-js regenerator-runtime --save- 在入口顶部一次性喂饱:
// 把 core-js 的 stable 特性全打包进来,体积爆炸但省心import'core-js/stable';// regenerator-runtime 负责 generator / asyncimport'regenerator-runtime/runtime';- 如果只想按需(强烈建议),改写成:
import'core-js/es/promise';import'core-js/es/object/assign';import'regenerator-runtime/runtime';- 然后 webpack 配置
babel-loader加@babel/preset-env,useBuiltIns: 'usage',让 Babel 自动嗅探你代码里用到的 API,帮你import对应的 polyfill。
注意:core-js 3 和 2 的目录结构不一样,别整混了,不然 IE 直接给你白屏,现场尬舞。
场景 3:Safari 报“can’t find variable: IntersectionObserver”,懒加载模块全崩
IntersectionObserver 这玩意好用得飞起,但 Safari 14 之前部分版本缺席。
社区有现成的 polyfill,体积 6KB(gzip),比自己手搓省头发:
npmi intersection-observer --save入口判断一下,按需加载,避免无谓流量:
// 动态加载 polyfill,只有不支持才请求,节省 6KBif(!('IntersectionObserver'inwindow)){import('intersection-observer').then(()=>{// 真正初始化懒加载initLazyLoad();});}else{initLazyLoad();}上面这段“懒加载 polyfill”的写法,同样适用于ResizeObserver、fetch、URL等现代 API,套路一模一样, CV 工程师狂喜。
打包体积爆炸?教你三招“瘦身术”
1. 别一上来就import 'core-js';全量!
core-js 全量 90KB+(gzip),IE 都退休了你还给全球用户塞红包?
用@babel/preset-env的useBuiltIns: 'usage'+corejs: 3,Babel 会自动帮你按需引入,立减 70KB 不是梦。
2. 打包两层策略:现代浏览器走“干净 bundle”,老旧走“带 polyfill”
webpack 多入口 +HtmlWebpackPlugin的excludeChunks:
// webpack.config.jsmodule.exports={entry:{modern:'./src/modern.js',legacy:'./src/polyfilled.js'},plugins:[newHtmlWebpackPlugin({filename:'index.html',excludeChunks:['legacy']}),newHtmlWebpackPlugin({filename:'legacy.html',excludeChunks:['modern']})]};服务器根据User-Agent把 IE 重定向到legacy.html,其他现代浏览器走index.html。
这一招对政务、银行项目尤其好用,领导看你首屏 100 分,绩效直接 +20%。
3. 动态 polyfill 服务:polyfill.io 真香
polyfill.io通过浏览器 UA 动态返回所需 polyfill,链接丢一行即可:
<!-- 只给缺失的浏览器返回代码,体积≈0 --><scriptcrossorigin="anonymous"src="https://polyfill.io/v3/polyfill.min.js?features=default,es2015,IntersectionObserver"></script>自建也行,源码在 GitHub,内网部署不依赖外网,妈妈再也不用担心我白屏。
踩坑合集:那些年一起熬过的夜
坑 1:重复引用 Promise polyfill,导致instanceof Promise === false
有些库(某聊天 SDK 不点名)自己偷偷塞了个 Promise,结果你业务代码里new Promise走的是另一份,两边原型链不一致,instanceof直接翻车。
解决:统一版本,把core-js的 Promise 锁死,或者在构建里alias强制指向同一文件。
坑 2:shim 把原生方法覆盖了,性能雪崩
某日期库为了兼容,把Date.now重写成new Date().getTime(),还全局覆盖。结果你代码里高频Date.now()做节流,瞬间从 O(1) 变 O(N)。
教训:写 shim 一定先判断原生存不存在,不存在再补,千万别“强行覆盖”。
坑 3:WeakMap polyfill 内存泄漏
IE 没有原生 WeakMap,社区 polyfill 用数组 + 随机 key 模拟,删不掉引用,单页应用跑一晚上,内存飙到 2G,领导手机直接烫成暖手宝。
解决:IE 时代该退就退,真要强上,记得 Destroy 阶段手动清空,或者限制缓存数量,别再让 polyfill 背锅。
坑 4:babel-runtime 和 @babel/plugin-transform-runtime 混用
transform-runtime会把全局污染转成局部引用,可你同时import 'core-js/stable'又把 polyfill 挂到全局,左右互搏,包体积 double。
正确姿势:选边站。
- 想全局污染 + 简单:用
preset-env+useBuiltIns - 想零污染 + 造库:用
transform-runtime+core-js-pure
手写一个“最小但能用”的 fetch shim,拿去忽悠面试官
fetch 本质是 Promise + XMLHttpRequest,IE 没有 Promise,我们假装 Promise 已存在(前面已 polyfill),下面只包 shim:
(function(global){// 参数序列化辅助,超简化版functionobj2query(obj){varstr='';for(varkinobj){if(obj.hasOwnProperty(k)){str+=(str?'&':'')+k+'='+encodeURIComponent(obj[k]);}}returnstr;}functionmyFetch(url,opts){opts=opts||{};returnnewPromise(function(resolve,reject){varxhr=newXMLHttpRequest();varmethod=(opts.method||'GET').toUpperCase();vardata=opts.body||null;// 处理 query 拼接if(method==='GET'&&opts.params){url+=(url.indexOf('?')>-1?'&':'?')+obj2query(opts.params);}xhr.open(method,url,true);// 简单设置请求头if(opts.headers){for(varhinopts.headers){if(opts.headers.hasOwnProperty(h)){xhr.setRequestHeader(h,opts.headers[h]);}}}// 超时vartimeout=opts.timeout||0;if(timeout){xhr.timeout=timeout;xhr.ontimeout=function(){reject(newError('Request timeout'));};}xhr.onload=function(){// 构造一个看起来像 Response 的对象varresponse={ok:xhr.status>=200&&xhr.status<300,status:xhr.status,statusText:xhr.statusText,url:xhr.responseURL,text:function(){returnPromise.resolve(xhr.responseText);},json:function(){try{returnPromise.resolve(JSON.parse(xhr.responseText));}catch(e){returnPromise.reject(e);}}};resolve(response);};xhr.onerror=function(){reject(newError('Network error'));};xhr.send(data);});}// 挂全局,假装自己是原生 fetchglobal.fetch=myFetch;})(window);interviewer:“小伙纸,说说 fetch 原理?”
你:“我自己写过,xhr + Promise 套壳,IE 也能跑,github 链接在这里……”
(注意:生产别用,缺太多细节,仅忽悠用)
一句话总结:别再把它们当“同义词”
- shim = 接口翻译,不造功能,只转发。
- polyfill = 功能克隆,缺啥造啥,自带器官。
记住这句骚话:
“shim 是口红,polyfill 是整容。”
口红卸了还能看,整容失败……那就只能回炉重造了。
下次再有同事把俩词混着叫,你就把这篇文章甩他脸上,让他好好背三遍,背不会?
webpack alias 配一夜,包教包会,保熟。
欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。
推荐:DTcode7的博客首页。
一个做过前端开发的产品经理,经历过睿智产品的折磨导致脱发之后,励志要翻身农奴把歌唱,一边打入敌人内部一边持续提升自己,为我们广大开发同胞谋福祉,坚决抵制睿智产品折磨我们码农兄弟!
| 专栏系列(点击解锁) | 学习路线(点击解锁) | 知识定位 |
|---|---|---|
| 《微信小程序相关博客》 | 持续更新中~ | 结合微信官方原生框架、uniapp等小程序框架,记录请求、封装、tabbar、UI组件的学习记录和使用技巧等 |
| 《AIGC相关博客》 | 持续更新中~ | AIGC、AI生产力工具的介绍,例如stable diffusion这种的AI绘画工具安装、使用、技巧等总结 |
| 《HTML网站开发相关》 | 《前端基础入门三大核心之html相关博客》 | 前端基础入门三大核心之html板块的内容,入坑前端或者辅助学习的必看知识 |
| 《前端基础入门三大核心之JS相关博客》 | 前端JS是JavaScript语言在网页开发中的应用,负责实现交互效果和动态内容。它与HTML和CSS并称前端三剑客,共同构建用户界面。 通过操作DOM元素、响应事件、发起网络请求等,JS使页面能够响应用户行为,实现数据动态展示和页面流畅跳转,是现代Web开发的核心 | |
| 《前端基础入门三大核心之CSS相关博客》 | 介绍前端开发中遇到的CSS疑问和各种奇妙的CSS语法,同时收集精美的CSS效果代码,用来丰富你的web网页 | |
| 《canvas绘图相关博客》 | Canvas是HTML5中用于绘制图形的元素,通过JavaScript及其提供的绘图API,开发者可以在网页上绘制出各种复杂的图形、动画和图像效果。Canvas提供了高度的灵活性和控制力,使得前端绘图技术更加丰富和多样化 | |
| 《Vue实战相关博客》 | 持续更新中~ | 详细总结了常用UI库elementUI的使用技巧以及Vue的学习之旅 |
| 《python相关博客》 | 持续更新中~ | Python,简洁易学的编程语言,强大到足以应对各种应用场景,是编程新手的理想选择,也是专业人士的得力工具 |
| 《sql数据库相关博客》 | 持续更新中~ | SQL数据库:高效管理数据的利器,学会SQL,轻松驾驭结构化数据,解锁数据分析与挖掘的无限可能 |
| 《算法系列相关博客》 | 持续更新中~ | 算法与数据结构学习总结,通过JS来编写处理复杂有趣的算法问题,提升你的技术思维 |
| 《IT信息技术相关博客》 | 持续更新中~ | 作为信息化人员所需要掌握的底层技术,涉及软件开发、网络建设、系统维护等领域的知识 |
| 《信息化人员基础技能知识相关博客》 | 无论你是开发、产品、实施、经理,只要是从事信息化相关行业的人员,都应该掌握这些信息化的基础知识,可以不精通但是一定要了解,避免日常工作中贻笑大方 | |
| 《信息化技能面试宝典相关博客》 | 涉及信息化相关工作基础知识和面试技巧,提升自我能力与面试通过率,扩展知识面 | |
| 《前端开发习惯与小技巧相关博客》 | 持续更新中~ | 罗列常用的开发工具使用技巧,如 Vscode快捷键操作、Git、CMD、游览器控制台等 |
| 《photoshop相关博客》 | 持续更新中~ | 基础的PS学习记录,含括PPI与DPI、物理像素dp、逻辑像素dip、矢量图和位图以及帧动画等的学习总结 |
| 日常开发&办公&生产【实用工具】分享相关博客》 | 持续更新中~ | 分享介绍各种开发中、工作中、个人生产以及学习上的工具,丰富阅历,给大家提供处理事情的更多角度,学习了解更多的便利工具,如Fiddler抓包、办公快捷键、虚拟机VMware等工具 |
吾辈才疏学浅,摹写之作,恐有瑕疵。望诸君海涵赐教。望轻喷,嘤嘤嘤
非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。愿斯文对汝有所裨益,纵其简陋未及渊博,亦足以略尽绵薄之力。倘若尚存阙漏,敬请不吝斧正,俾便精进!