前端老铁必看:Mock劫持Ajax翻车实录与避坑指南

前端老铁必看:Mock劫持Ajax翻车实录与避坑指南
- 前端老铁必看:Mock劫持Ajax翻车实录与避坑指南
前端老铁必看:Mock劫持Ajax翻车实录与避坑指南
说实话,我第一次用Mock.js的时候,整个人是懵的。那时候刚进公司,后端大哥说接口要"下周一定"——懂的都懂,这个"下周"可能是下辈子。产品那边又天天催页面效果,我急得像热锅上的蚂蚁,同事扔过来一个Mock.js的链接,说:“先拿这个顶着。”
我当时就纳闷了,这玩意儿咋就能让我在没有后端的情况下把页面跑起来?它凭啥能"骗"过浏览器?今天咱们就把这层窗户纸捅破,聊聊Mock是怎么在浏览器发请求的半道上截胡的,以及我这些年踩过的那些让人想砸键盘的坑。
咱就是说,这玩意儿到底咋忽悠浏览器的
先别急着抄代码,咱得唠唠心里话。你以为Mock.mock是啥神仙魔法?其实它就是个大骗子,专门在浏览器发请求的半道上截胡。咱们得把url、rtype还有template这三个参数掰开了揉碎了讲,就像给死党解释为啥你昨晚没回消息一样,得通透。
别整那些虚头巴脑的定义,直接说人话:它怎么把原本要发给后端的请求,硬生生给"狸猫换太子"了。
核心原理:浏览器里的"替身演员"
咱们平时写前端,发请求不外乎就那几种方式:最老派的XMLHttpRequest、稍微现代点的fetch、或者是基于这俩封装的axios、jQuery.ajax这些。Mock.js的核心骚操作就是:在页面加载的时候,偷偷把这俩原生API给"掉包"了。
想象一下,你本来要去银行取钱(发请求),Mock.js相当于在门口雇了个群演,穿着银行保安的衣服。你走过去说"我要取500块",保安一看:"哦,是Mock名单上的人,不用进银行了,我这儿直接给你500块假钞(假数据)。"你要是去别的银行(真实接口),保安一看名单上没有,才会放你进去。
这个"掉包"的过程大概长这样:
// 这是Mock.js内部大概会干的事情,简化版示意const originalXHR = window.XMLHttpRequest;functionMockXHR(){// 先假装自己是个正常的XHRconst xhr =neworiginalXHR();// 但是open方法被我劫持了const originalOpen = xhr.open; xhr.open=function(method, url, async, user, password){// 这里开始使坏:看看这个url是不是我要拦截的if(Mock._matchUrl(url, method)){// 匹配上了!标记一下,待会儿send的时候直接返回假数据this._isMocked =true;this._mockTemplate = Mock._getTemplate(url, method);}else{this._isMocked =false;}// 不管是不是Mock,先正常调用,免得露馅returnoriginalOpen.apply(this, arguments);};// send方法也要劫持const originalSend = xhr.send; xhr.send=function(body){if(this._isMocked){// 开始表演:假装网络延迟setTimeout(()=>{// 生成假数据const mockData = Mock._generateData(this._mockTemplate);// 伪造响应 Object.defineProperty(this,'responseText',{value:JSON.stringify(mockData),writable:false}); Object.defineProperty(this,'status',{value:200,writable:false}); Object.defineProperty(this,'readyState',{value:4,writable:false});// 触发onload事件,让前端代码以为真的收到响应了if(this.onload){this.onload();}if(this.onreadystatechange){this.onreadystatechange();}}, Mock._randomLatency());// 模拟个随机延迟,更像真的return;// 真正的send?不存在的,直接return}// 不是Mock的请求,正常发出去returnoriginalSend.apply(this, arguments);};return xhr;}// 把原生的给换了 window.XMLHttpRequest = MockXHR;看到没?就是这么简单粗暴。Mock.js在页面刚加载的时候,就把window.XMLHttpRequest这个构造函数给重写了。以后你代码里不管是new XMLHttpRequest()还是axios内部创建的XHR对象,实际上都是Mock.js包装过的"替身"。
那fetch呢?它也跑不掉
现在新项目都用fetch了,Mock.js也没闲着。对fetch的劫持原理差不多,也是把window.fetch给掉包:
// Mock.js对fetch的劫持思路,伪代码示意const originalFetch = window.fetch; window.fetch=function(url, options ={}){const method =(options.method ||'GET').toUpperCase();// 检查要不要拦截if(Mock._matchUrl(url, method)){// 要拦截,返回一个假装是Promise的玩意returnnewPromise((resolve)=>{setTimeout(()=>{// 构造一个假的Response对象const mockData = Mock._generateData(Mock._getTemplate(url, method));const mockResponse =newResponse(JSON.stringify(mockData),{status:200,headers:{'Content-Type':'application/json'}});resolve(mockResponse);}, Mock._randomLatency());});}// 不拦截,走正常的fetchreturnoriginalFetch.apply(this, arguments);};所以你看,Mock.js本质上就是个"中间人攻击"的合法版本,只不过攻击的是你自己的浏览器。它坐在浏览器和真实网络之间,看着你要发请求,先查查自己的小本本(你配置的路由规则),要是匹配上了,直接给你编个假响应,连网都不用出。
url、rtype、template这三个参数到底咋配合的
很多人用Mock.js老翻车,就是没搞懂这三个参数是怎么匹配的。我当初也是,写了Mock.mock('/api/user', 'get', {...}),结果请求发了半天没反应,急得我差点给电脑磕头。
url参数:这个支持好几种写法,灵活得很,但也容易搞混。
// 1. 字符串完全匹配 - 最严格,但最不实用 Mock.mock('/api/user/list',{'data|10':[{name:'@cname'}]});// 只有请求地址完全等于/api/user/list才匹配// 2. 带正则的字符串 - 这个最常用 Mock.mock(/\/api\/user\/\d+/,{'id|+1':1,'name':'@cname'});// 匹配 /api/user/1, /api/user/999 这种带动态ID的// 3. 真正的RegExp对象 - 最灵活 Mock.mock(newRegExp('^/api/(user|order)/list$'),{'list|5-10':[]});// 匹配 /api/user/list 或 /api/order/list// 4. 通配符模式 - Mock.js自己实现的简化正则 Mock.mock('/api/*',{'msg':'通配符匹配所有/api/开头的'});// 匹配 /api/anythingrtype参数:请求类型,GET、POST、PUT、DELETE这些。注意如果不写这个参数,默认匹配所有类型,但有时候这就是坑的来源。
// 错误示范:你以为只拦截GET,其实POST也拦 Mock.mock('/api/login',{'token':'@guid'});// 结果POST /api/login的时候,也被拦截了,返回了GET该有的数据结构// 正确姿势:明确指定类型 Mock.mock('/api/login','post',{'token':'@guid','expires':7200}); Mock.mock('/api/login','get',{'msg':'这是GET请求返回的,和POST不一样'});template参数:这就是你要返回的假数据模板。Mock.js的模板语法是它的精髓,也是让人又爱又恨的地方。
// 基础模板:属性名后面跟个竖线和规则 Mock.mock('/api/goods',{// 生成10-20条数据'list|10-20':[{'id|+1':1000,// 自增ID,从1000开始'name':'@ctitle(5,10)',// 随机中文标题,5-10个字'price|100-999.2':1,// 100-999的随机数,保留2位小数'stock|0-100':1,// 0-100的整数'isNew|1-2':true,// 1/2概率是true,剩下是false'tags|1-3':['@word'],// 随机选1-3个标签'image':'@image(200x200)',// 随机图片地址'createTime':'@datetime'// 随机日期时间}],'total':function(){// 函数可以访问this,this指向当前数据对象returnthis.list.length;}});看到没?那个'list|10-20'的语法,竖线前面是属性名,后面是规则。10-20表示生成10到20条数据。+1表示自增,1-2表示布尔值概率,100-999.2表示数字范围和精度。
匹配优先级:谁说了算
这里有个大坑:Mock.js的匹配是有优先级的,后写的会覆盖先写的。而且它的匹配是从上往下找,找到第一个匹配的就停。
// 先写个通用的 Mock.mock(/\/api\/.*/,'get',{msg:'通用匹配'});// 后写个具体的 Mock.mock('/api/user','get',{msg:'用户专用'});// 当请求 GET /api/user 的时候,返回的是"用户专用"// 因为后写的把先写的给"顶"掉了,或者说匹配的时候先匹配到后面的这个设计有时候很烦人,比如你写了个*通配符拦截所有,后面再写具体接口就不生效了。所以建议把具体的规则写在前面,通用的写在后面,或者干脆别用通配符,都用正则精确匹配。
扒开源码看底裤,拦截逻辑其实是这么玩的
这部分咱不装大神,就聊聊我当初看源码时那种"就这?"的感觉。核心其实就是重写了XMLHttpRequest或者fetch,相当于在门口安了个保安。每次你要出门(发请求),保安先看看你去的地址(url)对不对,要是匹配上了,直接把你拦下来,塞给你一包假东西(template)。
源码级别的URL匹配骚操作
Mock.js的URL匹配不是简单的字符串比较,它内部实现了一个match函数,支持字符串、正则、通配符混用。咱们扒开看看:
// Mock.js内部的路由匹配逻辑,简化版 Mock._matchUrl=function(expected, actual, type){// expected是你在Mock.mock()里写的url参数// actual是真实请求的地址// 1. 如果是字符串,先转成正则if(typeof expected ==='string'){// 把通配符*转成正则的.*// 把?转成.const regStr = expected .replace(/\*/g,'.*').replace(/\?/g,'.'); expected =newRegExp('^'+ regStr +'$');}// 2. 现在expected一定是正则了,测试匹配const urlMatched = expected.test(actual);// 3. 检查请求类型if(!type){// 如果Mock.mock()没指定type,只匹配URLreturn urlMatched;}// 4. 检查rtype是否匹配const typeMatched =(Mock._rtype === type.toLowerCase());return urlMatched && typeMatched;};看到那个通配符转换了吗?Mock.mock('/api/*')实际上被转换成了/^\/api\/.*$/这个正则。所以你写的通配符其实是正则的简化写法,理解这一点对排查匹配问题很有帮助。
请求类型(rtype)的判断陷阱
Mock.js在拦截的时候,需要知道当前请求是GET还是POST。但它判断的方式有时候会让你意想不到:
// 当你在代码里调用: axios.get('/api/user');// axios内部会创建一个XHR对象,调用open('GET', '/api/user')// Mock.js劫持的open方法会收到method='GET'// 但如果你用fetch:fetch('/api/user',{method:'POST'});// Mock.js从options里读method,但注意大小写!// Mock.js内部会把method转成小写再匹配有个坑是:有些库(比如早期的jQuery)可能会在method里带多余空格,比如'get '或者' POST',Mock.js的匹配可能会失效。虽然新版本可能已经修了,但遇到匹配不上的情况,记得在控制台打印一下真实的method是什么。
数据生成引擎:template是怎么变成真实JSON的
这是Mock.js最黑魔法的地方。你写个'name|1-10': '@cname',它怎么就能生成"张三丰"或者"欧阳锋"呢?
// Mock.js的模板解析核心逻辑,极度简化版 Mock._generateData=function(template){const result ={};for(let key in template){const value = template[key];const[propName, rule]= key.split('|');// 拆分属性名和规则if(typeof value ==='string'&& value.startsWith('@')){// 处理占位符,如 @cname, @date result[propName]= Mock._handlePlaceholder(value, rule);}elseif(Array.isArray(value)){// 处理数组规则,如 'list|10': [{...}]const count = Mock._parseRule(rule);// 解析出数字10 result[propName]=[];for(let i =0; i < count; i++){// 递归生成数组元素 result[propName].push(Mock._generateData(value[0]));}}elseif(typeof value ==='function'){// 函数直接执行 result[propName]=value.call(result);}elseif(typeof value ==='object'){// 对象递归处理 result[propName]= Mock._generateData(value);}else{// 基础值,直接赋值 result[propName]= value;}}return result;};// 占位符处理 Mock._handlePlaceholder=function(placeholder, rule){// @cname -> 随机中文名// @date -> 随机日期// @image(200x200) -> 随机图片const handlers ={'@cname':()=> Mock.Random.cname(),'@date':()=> Mock.Random.date(),'@image':(size)=>`https://via.placeholder.com/${size}`,// ... 还有很多};// 解析占位符参数,如 @image(200x200)const match = placeholder.match(/^@(\w+)(?:\((.*)\))?$/);const name = match[1];const args = match[2]? match[2].split(','):[];return handlers[name](...args);};所以模板语法的本质是:属性名|规则 定义生成逻辑,@开头的字符串调用内置生成器,函数提供完全自定义逻辑,数组和对象支持嵌套和递归。
延迟模拟:为什么要假装网络很慢
Mock.js默认会给Mock的请求加个随机延迟,通常是200-600毫秒。为啥要这么干?为了让假数据看起来更像真的。
你想啊,如果Mock的请求瞬间返回(0毫秒),而你真实接口要300毫秒,那你前端代码里的loading状态可能根本没机会展示。用户会觉得"这页面怎么闪了一下就出数据了",或者更糟,你代码里的竞态条件(race condition)在Mock环境下根本测不出来,一上真环境就崩。
// Mock.js的延迟配置 Mock.setup({timeout:'200-600'// 随机200到600毫秒// 或者固定值// timeout: 400});// 甚至可以在单个接口上覆盖 Mock.mock('/api/slow-query','get',function(options){// 模拟一个3秒的慢查询this.timeout =3000;return{'data|100':[{'id|+1':1}],'msg':'这接口就是慢,后端说的'};});爽完之后的贤者时间:这招到底有啥毛病
用着是挺爽,不用等后端接口就能开发,但咱得实话实说,这玩意儿也不是万能的。比如性能问题,你搞几千条假数据试试?浏览器卡成PPT。还有类型安全,后端返回个数字,你Mock写个字符串,TS类型检查直接报错,到时候改代码改到想哭。更别提有些复杂的鉴权逻辑、文件上传,Mock根本模拟不来。咱得把这些坑都列出来,省得新手一头扎进去出不来。
性能陷阱:假数据太多,浏览器直接去世
我第一次用Mock.js生成表格数据,心想着"要测就测极限情况",写了个'list|5000': [{...}]。结果页面直接卡死,风扇转得像要起飞。为啥?
Mock.js的数据生成是同步的、阻塞的。它要一次性生成5000条数据,每条数据还有十几个字段,这个计算量在主线程里跑,UI直接冻结。
// 作死示范:一次性生成5000条复杂数据 Mock.mock('/api/big-data',{'list|5000':[{'id|+1':1,'name':'@cname','profile':'@csentence(100)',// 大段文本'tags|5':['@word'],'metadata':{'views|0-10000':1,'likes|0-1000':1,'comments|0-500':1}}]});// 前端请求的时候 axios.get('/api/big-data').then(res=>{// 用户已经等了3秒,而且这3秒页面是卡死的this.tableData = res.data.list;});解决方案:分页!Mock.js支持从URL参数里读分页信息:
// 智能Mock,支持分页 Mock.mock(/\/api\/list\?page=\d+&size=\d+/,'get',function(options){// 从url里解析参数const url =newURL(options.url, window.location.origin);const page =parseInt(url.searchParams.get('page'))||1;const size =parseInt(url.searchParams.get('size'))||10;// 只生成当前页的数据const total =1000;// 假装有1000条const start =(page -1)* size;const list =[];for(let i =0; i < size; i++){ list.push({'id': start + i +1,'name': Mock.Random.cname(),'age': Mock.Random.integer(18,60)});}return{code:200,data:{ list, total, page, size },msg:'success'};});这样每次只生成10条20条,浏览器毫无压力。
类型安全地狱:TS项目里的Mock灾难
现在前端项目都用TypeScript了,Mock.js的问题就来了:它生成的数据类型和你定义的接口可能对不上。
// 你定义的接口interfaceUser{ id:number; name:string; age:number; isVip:boolean; createTime:string;// ISO日期字符串}// 你的Mock Mock.mock('/api/user',{'id|+1':1000,// 生成的是number,对上了'name':'@cname',// string,也对上了'age|18-60':1,// number,没问题'isVip|1-2':true,// boolean,OK'createTime':'@date'// 等等,这个生成的是"2023-10-15"这样的格式// 但后端实际返回的是"2023-10-15T14:30:00.000Z"这种ISO格式!});看起来都是字符串,但格式不一样!你的代码里可能用了new Date(user.createTime),Mock数据能正常解析,真实数据也能,但有些日期处理库对格式很敏感。更惨的是数字和字符串混用:
// 后端返回的id是number类型:12345// 但你Mock写成了:'id':'@id'// Mock.Random.id()生成的是字符串:"12345678901234567890"// 或者反过来:'id|+1':1000// 生成number,但后端返回的是string:"1000"解决方案:严格对照后端接口文档写Mock,或者用TS的类型守卫做转换:
// Mock配置里就保证类型一致 Mock.mock('/api/user',{'id|+1':1000,'createTime':function(){// 强制返回ISO格式returnnewDate().toISOString();}});// 或者在前端做防御性编程functionnormalizeUser(data:any): User {return{...data, id:Number(data.id),// 强制转number createTime:newDate(data.createTime).toISOString()};}复杂业务逻辑模拟不了:鉴权、文件上传、流式数据
Mock.js能拦截XHR和fetch,但有些场景它搞不定:
文件上传进度:真实的文件上传有onprogress事件,能拿到上传百分比。Mock.js虽然能返回假数据,但模拟不了真实的上传进度事件。
// 真实的文件上传代码const xhr =newXMLHttpRequest(); xhr.upload.onprogress=(e)=>{const percent =(e.loaded / e.total)*100;this.progress = percent;// 更新进度条}; xhr.open('POST','/api/upload'); xhr.send(formData);// Mock.js能拦截这个请求并返回成功响应// 但它模拟不了progress事件的触发过程// 你的进度条要么直接100%,要么不动解决方案:自己手动触发事件,或者不要用Mock拦截,用MSW(Mock Service Worker)这种更现代的方案。
Token鉴权逻辑:真实的登录流程是:1) POST用户名密码 2) 后端返回token 3) 后续请求带token 4) token过期跳登录页。Mock.js能模拟第1、2步,但第3步(后续请求自动带token)和第4步(token过期判断)需要额外的逻辑。
// 简单的token模拟方案let mockToken =null;// 登录接口 Mock.mock('/api/login','post',(options)=>{const{ username, password }=JSON.parse(options.body);if(username ==='admin'&& password ==='123456'){ mockToken ='mock-token-'+ Date.now();return{code:200,token: mockToken,expires:3600};}return{code:401,msg:'用户名或密码错误'};});// 需要鉴权的接口 Mock.mock('/api/user/profile','get',(options)=>{// 检查header里有没有tokenconst headers = options.headers ||{};const auth = headers.Authorization || headers.authorization;if(!auth || auth !==`Bearer ${mockToken}`){return{code:401,msg:'未登录或token已过期'};}return{code:200,data:{name:'管理员',avatar:'@image'}};});流式数据(SSE/Stream):Mock.js不支持Server-Sent Events或者fetch的ReadableStream,这些得用专门的Mock工具。
跨域和CORS的坑
有时候你Mock的请求莫名其妙报CORS错误,但后端接口明明已经配置了跨域。为啥?
因为Mock.js拦截的是请求发出前的XHR对象,但如果匹配失败,请求会正常发出去。这时候如果后端没配置CORS,就会报错。更坑的是,Mock.js有时候会把请求类型判断错,导致该拦截的没拦截,不该拦截的拦截了。
// 假设你配置了Mock拦截 /api/* Mock.mock('/api/*',{msg:'mocked'});// 但你的真实请求是:fetch('https://other-domain.com/api/data',{mode:'cors'// 明确跨域});// Mock.js一看,url匹配/api/*,拦截!// 但它返回的Response对象没有CORS相关的header// 浏览器可能会报CORS错误,即使这个请求本来不该被Mock解决方案:精确匹配URL,不要用太宽泛的通配符,或者给Mock的响应加上CORS头:
Mock.mock('/api/data',{// Mock.js支持配置响应头(看版本,有些版本不支持)headers:{'Access-Control-Allow-Origin':'*'},body:{data:'mocked'}});真实搬砖场景:我在项目里是怎么靠它摸鱼的
光说不练假把式,说说实际干活怎么用。比如后端接口还没好,产品经理又催着要页面效果,这时候Mock.mock就是你的救命稻草。怎么快速生成列表页的假数据?怎么模拟接口超时或者报错(比如500错误、网络断开)来测试前端的异常处理?甚至怎么配合Vue或React的状态管理,让假数据跑得像真的一样。这里得多给点具体的思路,比如怎么组织template模板才能让数据看起来不那么假。
场景一:后端接口延迟,先画UI
这是最经典的场景。产品要一个"用户管理后台",后端说"表结构还没设计完,下周给接口"。你怎么办?
Step 1:先定义接口规范
别急着写代码,先跟后端(或者你自己)定好接口格式:
GET /api/users?page=1&size=10&keyword=xxx Response: { code: 200, data: { list: [ { id: number, name: string, email: string, status: 0|1, createTime: string } ], total: number, page: number, size: number } } Step 2:写Mock配置
// mock/user.jsimport Mock from'mockjs';// 生成一条用户数据constgenerateUser=(id)=>({'id': id,'name':'@cname','email':function(){// 根据名字生成邮箱,看起来更像真的const pinyin =this.name.charAt(0);// 简化处理,实际应该用拼音库return`${pinyin.toLowerCase()}${Mock.Random.integer(100,999)}@example.com`;},'avatar':'@image(100x100)','status|0-1':1,// 0禁用 1启用'role|1':['admin','editor','viewer'],'createTime':'@datetime("yyyy-MM-dd HH:mm:ss")','lastLogin':'@datetime("yyyy-MM-dd HH:mm:ss")'});// 列表接口 - 支持分页和搜索 Mock.mock(/\/api\/users\?.*/,'get',(options)=>{const url =newURL(options.url, window.location.origin);const page =parseInt(url.searchParams.get('page'))||1;const size =parseInt(url.searchParams.get('size'))||10;const keyword = url.searchParams.get('keyword')||'';// 生成100条假数据作为"数据库"const allUsers =[];for(let i =1; i <=100; i++){ allUsers.push(generateUser(i));}// 模拟搜索(前端过滤)let filtered = allUsers;if(keyword){ filtered = allUsers.filter(u=> u.name.includes(keyword)|| u.email.includes(keyword));}// 分页const start =(page -1)* size;const list = filtered.slice(start, start + size);return{code:200,data:{ list,total: filtered.length, page, size },msg:'success'};});// 单个用户详情 Mock.mock(/\/api\/user\/\d+/,'get',(options)=>{const id = options.url.match(/\/api\/user\/(\d+)/)[1];return{code:200,data:generateUser(parseInt(id)),msg:'success'};});// 更新用户 Mock.mock(/\/api\/user\/\d+/,'put',(options)=>{const body =JSON.parse(options.body);return{code:200,data:{...body,updateTime: Mock.Random.now()},msg:'更新成功'};});Step 3:Vue组件里直接用
<template> <div> <el-input v-model="keyword" placeholder="搜索用户" @keyup.enter="search" /> <el-table :data="userList" v-loading="loading"> <el-table-column prop="name" label="姓名"> <template #default="{row}"> <img :src="row.avatar" /> {{ row.name }} </template> </el-table-column> <el-table-column prop="email" label="邮箱" /> <el-table-column prop="status" label="状态"> <template #default="{row}"> <el-tag :type="row.status ? 'success' : 'danger'"> {{ row.status ? '启用' : '禁用' }} </el-tag> </template> </el-table-column> </el-table> <el-pagination v-model:current-page="page" v-model:page-size="size" :total="total" @change="fetchData" /> </div> </template> <script setup> import { ref, onMounted } from 'vue'; import axios from 'axios'; const userList = ref([]); const loading = ref(false); const page = ref(1); const size = ref(10); const total = ref(0); const keyword = ref(''); const fetchData = async () => { loading.value = true; try { const res = await axios.get('/api/users', { params: { page: page.value, size: size.value, keyword: keyword.value } }); userList.value = res.data.data.list; total.value = res.data.data.total; } catch (err) { console.error('获取用户列表失败:', err); } finally { loading.value = false; } }; const search = () => { page.value = 1; fetchData(); }; onMounted(fetchData); </script> 看到没?UI完全按照真实接口的逻辑写,包括loading状态、错误处理、分页逻辑。等后端接口好了,把Mock文件一删(或者配置关闭),代码一行不用改,直接连真接口。
场景二:模拟各种异常情况,测试前端鲁棒性
好的前端代码要能处理各种异常情况:网络断开、服务器500错误、超时、返回格式不对。但这些情况在开发环境很难遇到,Mock.js可以帮你"制造"这些异常。
// mock/error.jsimport Mock from'mockjs';// 模拟500服务器错误 Mock.mock('/api/error-500',{status:500,statusText:'Internal Server Error',responseText:JSON.stringify({msg:'数据库连接超时'})});// 模拟网络超时(挂起请求,永不返回) Mock.mock('/api/timeout',function(){// 返回一个pending的Promise,模拟请求卡住returnnewPromise(()=>{});});// 模拟返回格式错误(不是JSON) Mock.mock('/api/bad-format',{responseText:'这不是JSON,服务器出错了'});// 模拟随机失败(测试重试逻辑)let requestCount =0; Mock.mock('/api/unstable',(options)=>{ requestCount++;if(requestCount %3!==0){// 前两次失败return{status:503,responseText:JSON.stringify({msg:'服务暂时不可用'})};}// 第三次成功return{code:200,data:'终于成功了',msg:'success'};});// 模拟超时的另一种方式(延迟返回) Mock.mock('/api/slow-timeout',(options)=>{returnnewPromise((resolve)=>{setTimeout(()=>{resolve({code:408,msg:'请求超时'});},10000);// 10秒后才返回,但前端可能5秒就超时了});});前端代码里可以测试这些异常:
// 封装一个带超时的请求constfetchWithTimeout=async(url, options ={}, timeout =5000)=>{const controller =newAbortController();const id =setTimeout(()=> controller.abort(), timeout);try{const res =awaitfetch(url,{...options,signal: controller.signal });clearTimeout(id);return res;}catch(err){clearTimeout(id);if(err.name ==='AbortError'){thrownewError('请求超时');}throw err;}};// 测试try{const res =awaitfetchWithTimeout('/api/slow-timeout',{},3000);}catch(err){ console.log(err.message);// "请求超时"}场景三:让假数据看起来像真的
很多人Mock的数据一看就是假的:“用户1”、“用户2”、“[email protected]”。怎么让数据更真实?
技巧1:用函数动态生成关联数据
// 不好的做法:名字和邮箱没关系 Mock.mock('/api/user',{'name':'@cname','email':'@email'// 可能生成 [email protected],但名字是李四});// 好的做法:邮箱根据名字生成 Mock.mock('/api/user',{'name':'@cname','email':function(){// 根据中文名生成拼音邮箱(简化版)const nameMap ={'张':'zhang','李':'li','王':'wang','刘':'liu','陈':'chen'};const firstName =this.name.charAt(0);const pinyin = nameMap[firstName]||'user';const num = Mock.Random.integer(1,999);return`${pinyin}${num}@company.com`;}});技巧2:生成合理的业务数据
// 电商订单数据 Mock.mock('/api/orders',{'list|10':[{'orderNo':function(){// 订单号:日期+随机数+用户ID,看起来像真的const date =newDate().toISOString().slice(0,10).replace(/-/g,'');const random = Mock.Random.string('number',6);return`ORD${date}${random}`;},'amount':function(){// 金额根据商品数量计算,别瞎随机const items =this.items ||[];return items.reduce((sum, item)=> sum + item.price * item.quantity,0).toFixed(2);},'items|1-5':[{'sku':/SKU[0-9]{8}/,// SKU格式:SKU12345678'name':'@ctitle(5,15)',// 商品名5-15个字'price|10-9999.2':1,'quantity|1-10':1}],'status|1':['pending','paid','shipped','completed','cancelled'],'createTime':'@datetime("yyyy-MM-dd HH:mm:ss")','payTime':function(){// 只有paid及以后状态才有支付时间if(['paid','shipped','completed'].includes(this.status)){return Mock.Random.datetime('yyyy-MM-dd HH:mm:ss');}returnnull;}}]});技巧3:图片别用默认的,用占位图服务
// Mock.js自带的@image生成的是纯颜色图,太假 Mock.mock('/api/goods',{'image':'@image(200x200)'// 生成的是#e2e2e2这种颜色块});// 更好的做法:用占位图服务,看起来像真实商品图 Mock.mock('/api/goods',{'image':function(){const id = Mock.Random.integer(1,1000);// picsum.photos提供真实的随机图片return`https://picsum.photos/seed/${id}/300/200`;// 或者用特定主题// return `https://loremflickr.com/300/200/product?lock=${id}`;}});场景四:配合状态管理(Vuex/Pinia/Redux)
在大型项目里,Mock数据要配合状态管理使用,才能模拟完整的业务流。
// store/modules/user.js (Pinia写法)import{ defineStore }from'pinia';import axios from'axios';exportconst useUserStore =defineStore('user',{state:()=>({userInfo:null,permissions:[],loading:false,error:null}),actions:{asynclogin(credentials){this.loading =true;this.error =null;try{const res =await axios.post('/api/login', credentials);this.userInfo = res.data.data.user;this.permissions = res.data.data.permissions; localStorage.setItem('token', res.data.data.token);returntrue;}catch(err){this.error = err.response?.data?.msg ||'登录失败';returnfalse;}finally{this.loading =false;}},asyncfetchUserInfo(){this.loading =true;try{const res =await axios.get('/api/user/profile');this.userInfo = res.data.data;}catch(err){if(err.response?.status ===401){// token过期,跳转登录this.logout();}}finally{this.loading =false;}},logout(){this.userInfo =null;this.permissions =[]; localStorage.removeItem('token');}}});对应的Mock配置:
// mock/auth.jsimport Mock from'mockjs';// 模拟登录 Mock.mock('/api/login','post',(options)=>{const{ username, password }=JSON.parse(options.body);// 模拟验证延迟if(username ==='admin'&& password ==='admin123'){return{code:200,data:{token:'mock-jwt-token-'+ Date.now(),user:{id:1,username:'admin',nickname:'系统管理员',avatar:'https://picsum.photos/seed/admin/100/100',role:'super_admin'},permissions:['user:read','user:write','order:read','system:*']},msg:'登录成功'};}// 模拟密码错误return{code:401,msg:'用户名或密码错误'};});// 模拟获取用户信息(需要token) Mock.mock('/api/user/profile','get',(options)=>{const token = options.headers?.Authorization?.replace('Bearer ','');if(!token ||!token.startsWith('mock-jwt-token')){return{code:401,msg:'Token无效或已过期'};}return{code:200,data:{id:1,username:'admin',nickname:'系统管理员',avatar:'https://picsum.photos/seed/admin/100/100',department:'技术部',email:'[email protected]',phone:'13800138000',lastLoginIp: Mock.Random.ip(),lastLoginTime: Mock.Random.datetime()}};});// 模拟登出 Mock.mock('/api/logout','post',{code:200,msg:'登出成功'});这样整个登录流程都是通的,你可以测试:登录成功跳转、登录失败提示、token过期自动登出、权限控制等等。
翻车现场急救包:为什么我的请求还是发出去了
肯定有人会遇到这种情况:代码明明写了,控制台也报了Mock成功,结果Network面板里请求还是发到了后端,或者根本没反应。这时候别慌,大概率是url匹配规则写错了,或者是请求方法(GET/POST)没对上。还有一种坑是Webpack或者Vite的配置问题,把Mock文件给tree-shaking掉了。咱得把这些排查思路理一理,就像医生问诊一样,一步步教你怎么定位病灶,别只会重启大法。
症状1:Network面板里能看到真实请求
这说明Mock.js没拦截到,请求发出去了。
排查步骤1:检查URL是否匹配
// 你写的Mock Mock.mock('/api/user',{data:'mocked'});// 但真实请求可能是: axios.get('http://localhost:8080/api/user');// 带域名// 或者 axios.get('/api/user/123');// 带动态ID// 或者 axios.get('/api/user?page=1');// 带参数// 这些都不匹配 '/api/user' 这个字符串!解决方案:用正则匹配,别用字符串:
// 匹配 /api/user 以及 /api/user?page=1 Mock.mock(/\/api\/user(\?.*)?$/,{data:'mocked'});// 匹配 /api/user/123 这种RESTful风格 Mock.mock(/\/api\/user\/\d+/,{data:'mocked'});// 匹配所有/api/开头的 Mock.mock(/\/api\/.*/,{data:'mocked'});排查步骤2:检查请求方法
// 你写的Mock(没指定方法,默认匹配所有) Mock.mock('/api/user',{data:'mocked'});// 但axios可能用的是POST axios.post('/api/user',{name:'test'});// Mock.js确实拦截了,但你的template返回的是GET格式的数据// 导致前端解析报错,你以为没拦截成功解决方案:明确指定方法,或者分别配置:
Mock.mock('/api/user','get',{/* GET返回的数据 */}); Mock.mock('/api/user','post',(options)=>{// 可以读取body,返回不同的数据const body =JSON.parse(options.body);return{id: Mock.Random.id(),...body,createTime:newDate().toISOString()};});排查步骤3:检查Mock执行时机
Mock.js必须在请求发起之前完成初始化。如果你在组件里import Mock from 'mockjs'然后直接Mock.mock(),但这时候请求已经发出去了,那就晚了。
解决方案:在入口文件(main.js/app.js)最早的位置引入:
// main.jsimport{ createApp }from'vue';import App from'./App.vue';// 确保在最前面引入Mockif(process.env.NODE_ENV==='development'){require('./mock/index.js');// 或者 import './mock/index.js'}const app =createApp(App); app.mount('#app');症状2:控制台显示Mock成功,但回调没执行
这说明拦截成功了,但响应数据有问题,导致前端解析失败。
排查步骤1:检查返回格式是否是合法JSON
// 错误的Mock:返回字符串,但前端用res.json()解析 Mock.mock('/api/data','this is not json');// 正确的Mock:返回对象,Mock.js会自动转JSON Mock.mock('/api/data',{msg:'这是对象,会被自动JSON.stringify'});// 如果你手动拼JSON字符串,注意转义: Mock.mock('/api/data',{responseText:'{"key": "value"}'// Mock.js会原样返回这个字符串});排查步骤2:检查异步逻辑
// 如果你用了函数返回Promise,确保resolve的是正确格式 Mock.mock('/api/async',()=>{returnnewPromise((resolve)=>{setTimeout(()=>{// 错误:直接返回对象,但Mock.js期望包装在特定格式里resolve({data:'test'});// 正确:有些版本需要这样resolve({status:200,responseText:JSON.stringify({data:'test'})});},1000);});});症状3:Vite/Webpack项目里Mock突然失效
这大概率是tree-shaking或者热更新的问题。
Vite项目的坑:
Vite的生产构建会tree-shaking掉"没用"的代码。如果你在main.js里这样写:
if(import.meta.env.DEV){import('./mock/index.js');}Vite可能会认为这个import是动态的、可选的,在生产构建时直接删掉。或者Mock文件里的代码被认为"没有副作用",被优化掉了。
解决方案:确保Mock文件有副作用,并且引入方式让打包工具知道不能删:
// mock/index.jsimport Mock from'mockjs';// 确保这行代码被执行,而不是只定义了函数没调用 Mock.mock('/api/test',{msg:'ok'});// 导出点什么,让文件被认为是ESM模块exportdefault Mock;// main.js// 方式1:直接import,不用条件判断(推荐只在开发环境引入)import'./mock/index.js';// 方式2:如果需要条件判断,确保语法正确if(import.meta.env.DEV){// 动态import也要await,确保在后续代码前完成awaitimport('./mock/index.js');}Webpack项目的坑:
如果你用了mockjs的按需加载或者babel-plugin-import,可能会出问题。
// 错误:只引入了Mock,没引入XHR拦截逻辑import{ mock }from'mockjs';// 正确:引入完整的Mock对象import Mock from'mockjs';症状4:部分接口Mock成功,部分失败
这说明Mock的匹配规则有冲突,后写的规则覆盖了先写的。
// 先写的通用规则 Mock.mock('/api/*',{msg:'通用'});// 后写的具体规则 Mock.mock('/api/user',{msg:'用户专用'});// 结果:请求 /api/user 时,可能匹配的是第一个规则// 因为Mock.js内部存储规则的方式是对象,key是url字符串// '/api/*' 和 '/api/user' 作为对象key,匹配顺序不确定解决方案:精确匹配优先,或者用不同的匹配方式:
// 用正则的精确度来控制优先级 Mock.mock(/^\/api\/user$/,{msg:'用户专用'});// 完全匹配 Mock.mock(/^\/api\/order$/,{msg:'订单专用'}); Mock.mock(/\/api\/.*/,{msg:'通用'});// 放在最后,作为fallback万能排查法:打印日志
如果以上都查不出来,直接在Mock回调里打日志:
Mock.mock(/\/api\/.*/,(options)=>{ console.log('Mock拦截到请求:', options); console.log('URL:', options.url); console.log('Method:', options.type); console.log('Body:', options.body);// 返回明显不同的数据,确认走的是Mockreturn{isMocked:true,timestamp: Date.now(),data:'如果你看到这个,说明Mock生效了'};});打开控制台,看看请求来的时候有没有打印。如果没有打印,说明URL没匹配上;如果有打印但Network里还有请求,说明Mock.js内部出错了。
几个让同事喊你爸爸的骚操作技巧
既然要玩,就玩点花的。比如怎么用函数动态生成数据,让每次请求回来的ID都不一样?怎么利用template里的语法糖生成随机图片、随机名字,让页面看起来像个真的上线项目?还有怎么把Mock数据单独抽离成一个文件,甚至根据环境变量自动开关,上线的时候自动关闭Mock,别让假数据污染生产环境。这些技巧学会了,你在组里的地位绝对不一样。
骚操作1:函数式Mock,每次请求数据都不一样
静态Mock有个问题:第一次请求和第十次请求返回一样的数据。真实接口可不会这样(除非有缓存)。用函数可以让每次请求都生成新数据:
// 静态Mock - 每次返回一样的 Mock.mock('/api/random',{'id|+1':1,'value':'@integer(1,100)'});// 函数式Mock - 每次请求重新生成let globalId =1000; Mock.mock('/api/random',()=>{// 每次请求都会执行这个函数return{id:++globalId,// 真正的自增,不是Mock的伪自增value: Mock.Random.integer(1,100),timestamp: Date.now(),// 真实时间戳randomStr: Math.random().toString(36).substring(7)// 真随机字符串};});// 更骚的:根据请求参数返回不同数据 Mock.mock('/api/search',(options)=>{const{ keyword, page }=JSON.parse(options.body);// 根据keyword生成相关数据constgenerateItems=(key)=>{const count = Mock.Random.integer(5,20);return Array.from({length: count },(_, i)=>({id:`${page}-${i}`,title:`${key} - 搜索结果${i+1}`,desc: Mock.Random.cparagraph(2),// 随机两段中文relevance: Mock.Random.integer(60,99)// 相关度分数}));};return{code:200,data:{list:generateItems(keyword),total:999,// 假装有很多 page, keyword }};});骚操作2:Mock数据持久化,模拟CRUD
如果你在做后台管理系统,需要测试新增、编辑、删除功能,Mock的数据要能"记住"修改。
// mock/crud.jsimport Mock from'mockjs';// 内存数据库const db ={users: Array.from({length:50},(_, i)=>({id: i +1,name: Mock.Random.cname(),age: Mock.Random.integer(18,60),email: Mock.Random.email(),status: Mock.Random.pick(['active','inactive'])}))};// 查询列表 Mock.mock(/\/api\/users\?.*/,'get',(options)=>{const url =newURL(options.url, window.location.origin);const page =parseInt(url.searchParams.get('page'))||1;const size =parseInt(url.searchParams.get('size'))||10;const start =(page -1)* size;return{code:200,data:{list: db.users.slice(start, start + size),total: db.users.length }};});// 新增 Mock.mock('/api/users','post',(options)=>{const body =JSON.parse(options.body);const newUser ={id: db.users.length +1,...body,createTime:newDate().toISOString()}; db.users.push(newUser);return{code:200,data: newUser,msg:'创建成功'};});// 编辑 Mock.mock(/\/api\/users\/\d+/,'put',(options)=>{const id =parseInt(options.url.match(/\/api\/users\/(\d+)/)[1]);const body =JSON.parse(options.body);const index = db.users.findIndex(u=> u.id === id);if(index >-1){ db.users[index]={...db.users[index],...body,updateTime:newDate().toISOString()};return{code:200,data: db.users[index],msg:'更新成功'};}return{code:404,msg:'用户不存在'};});// 删除 Mock.mock(/\/api\/users\/\d+/,'delete',(options)=>{const id =parseInt(options.url.match(/\/api\/users\/(\d+)/)[1]);const index = db.users.findIndex(u=> u.id === id);if(index >-1){ db.users.splice(index,1);return{code:200,msg:'删除成功'};}return{code:404,msg:'用户不存在'};});这样你在页面上增删改查,数据会真的变化,刷新页面后数据还在(因为存在内存里),直到你F5刷新整个页面重置Mock。
骚操作3:环境变量控制Mock开关
最危险的事情就是:上线的时候Mock还开着,用户看到假数据。必须做到开发环境自动开启,生产环境自动关闭。
// .env.developmentVITE_USE_MOCK=true// .env.productionVITE_USE_MOCK=false// mock/index.jsimport Mock from'mockjs';// 根据环境变量决定是否启动const isUseMock =import.meta.env.VITE_USE_MOCK==='true';if(isUseMock){ console.log('[Mock] 开发模式:Mock服务已启动');// 批量注册Mockconst modules =import.meta.glob('./modules/*.js',{eager:true}); Object.values(modules).forEach(module=>{if(module.default){ module.default(Mock);}});// 设置全局延迟 Mock.setup({timeout:'300-800'});}else{ console.log('[Mock] 生产模式:Mock服务已禁用');}exportdefault Mock;// mock/modules/user.jsexportdefaultfunction(Mock){ Mock.mock('/api/user/info',{id:'@id',name:'@cname'}); Mock.mock('/api/user/list',{'list|10':[{'id|+1':1,'name':'@cname'}]});}// main.jsimport'./mock/index.js';// 自动根据环境变量判断骚操作4:模拟真实的数据关系
真实业务里数据是有关系的:用户有订单,订单有商品,商品有分类。Mock的时候也要体现这种关系。
// 先生成基础数据const categories = Array.from({length:5},(_, i)=>({id: i +1,name:['数码','服装','食品','家居','图书'][i]}));const goods = Array.from({length:100},(_, i)=>({id: i +1,name: Mock.Random.ctitle(5,10),categoryId: Mock.Random.pick(categories).id,price: Mock.Random.float(10,1000,2,2),stock: Mock.Random.integer(0,999)}));// 订单接口 - 关联商品和用户 Mock.mock('/api/orders',{'list|20':[{'id|+1':10000,'orderNo':/2023[0-9]{12}/,'userId|1-1000':1,'items|1-5':function(){// 从goods里随机选几个,并计算总价const selectedGoods =[];const count = Mock.Random.integer(1,5);let totalAmount =0;for(let i =0; i < count; i++){const g = goods[Mock.Random.integer(0, goods.length -1)];const quantity = Mock.Random.integer(1,3); selectedGoods.push({goodsId: g.id,goodsName: g.name,price: g.price,quantity: quantity,subtotal:(g.price * quantity).toFixed(2)}); totalAmount += g.price * quantity;}this.totalAmount = totalAmount.toFixed(2);return selectedGoods;},'status|1':['待付款','待发货','已发货','已完成'],'createTime':'@datetime'}]});骚操作5:模拟WebSocket实时数据
Mock.js只能拦截HTTP请求,WebSocket它管不了。但你可以用额外的库模拟WebSocket:
// 用mock-socket库模拟WebSocketimport{ Server, WebSocket }from'mock-socket';// 创建Mock WebSocket服务器const mockServer =newServer('ws://localhost:8080/realtime'); mockServer.on('connection',socket=>{ console.log('[Mock WebSocket] 客户端已连接');// 模拟实时推送消息const interval =setInterval(()=>{ socket.send(JSON.stringify({type:'notification',data:{id: Date.now(),title: Mock.Random.ctitle(5,10),time:newDate().toLocaleTimeString()}}));},5000);// 模拟响应客户端消息 socket.on('message',data=>{const msg =JSON.parse(data);if(msg.type ==='heartbeat'){ socket.send(JSON.stringify({type:'pong'}));}}); socket.on('close',()=>{clearInterval(interval);});});// 前端代码完全不用改,直接连ws://localhost:8080/realtime就行const ws =newWebSocket('ws://localhost:8080/realtime'); ws.onmessage=(e)=>{ console.log('收到消息:',JSON.parse(e.data));};骚操作6:Mock文件模块化组织
项目大了,Mock文件也会很乱。建议按业务模块组织:
mock/ ├── index.js # 入口,控制开关 ├── utils.js # 工具函数(生成数据、延迟等) ├── modules/ │ ├── user.js # 用户相关 │ ├── order.js # 订单相关 │ ├── goods.js # 商品相关 │ └── auth.js # 登录鉴权 └── data/ # 静态数据JSON ├── cities.json └── departments.json // mock/modules/user.jsimport{ mock, Random }from'mockjs';import{ success, error, paginate }from'../utils.js';exportdefaultfunctionuserModule(){// 列表mock(/\/api\/users\?.*/,'get',(options)=>{const params =newURLSearchParams(options.url.split('?')[1]);returnsuccess(paginate(generateUsers(100), params));});// 详情mock(/\/api\/users\/\d+/,'get',(options)=>{const id = options.url.match(/\/api\/users\/(\d+)/)[1];returnsuccess(generateUser(id));});// 编辑mock(/\/api\/users\/\d+/,'put',(options)=>{const body =JSON.parse(options.body);if(!body.name){returnerror('用户名不能为空');}returnsuccess({...body,updateTime:newDate().toISOString()});});}functiongenerateUsers(count){return Array.from({length: count },(_, i)=>generateUser(i +1));}functiongenerateUser(id){returnmock({ id,'name':'@cname','email':'@email','phone':/1[3-9]\d{9}/,'avatar':`@image(100x100, @color, @word)`,'status|1':[0,1],'createTime':'@datetime'});}// mock/utils.jsexportconstsuccess=(data)=>({code:200, data,msg:'success'});exportconsterror=(msg, code =500)=>({ code, msg,data:null});exportconstpaginate=(allData, params)=>{const page =parseInt(params.get('page'))||1;const size =parseInt(params.get('size'))||10;const start =(page -1)* size;return{list: allData.slice(start, start + size),total: allData.length, page, size };};行了别卷了,再写下去后端要提刀过来了
差不多得了,Mock终究是替身,真刀真枪还得靠后端接口。这篇东西就当是个备忘录,下次再遇到接口没好被产品催命的时候,翻出来瞅一眼,能省点头发是点头发。
说实话,Mock.js这玩意儿用好了是真香,用不好也是真坑。我见过有人拿Mock当长期方案,最后上线的时候发现Mock逻辑和真实接口对不上,改代码改到凌晨三点。也见过有人完全不用Mock,后端接口延迟一周,他就在那儿干瞪眼一周,效率低到被老板约谈。
我的建议:
- Mock只是开发阶段的拐杖,不是假肢。接口一旦ready,立刻切到真实环境测试,别恋战。
- 保持Mock和接口文档同步,最好是后端先出Swagger文档,你照着文档写Mock,而不是凭想象瞎编。
- 复杂业务别硬Mock,文件上传、大文件下载、实时通信这些,Mock成本太高,不如直接等后端。
- 上线前 triple check,确保所有Mock都已关闭,别让假数据流到生产环境,那可是事故。
要是真把Mock当永久方案,那咱俩还是绝交吧,太天真了。但要是完全不会Mock,每次被后端卡脖子,那也得反思反思,工具箱里是不是缺了这把瑞士军刀。
总之,前端这条路,工具是死的,人是活的。Mock.js用熟了,确实能让你在"等接口"的焦虑中稍微喘口气,但也别沉迷这种虚假的安全感。毕竟,代码最终还是要在真实的网络环境、真实的数据、真实的用户操作下跑起来,那才是真正的考验。
下次见,希望你的Network面板里,再也没有404和CORS报错。
欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。
推荐: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等工具 |
吾辈才疏学浅,摹写之作,恐有瑕疵。望诸君海涵赐教。望轻喷,嘤嘤嘤
非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。愿斯文对汝有所裨益,纵其简陋未及渊博,亦足以略尽绵薄之力。倘若尚存阙漏,敬请不吝斧正,俾便精进!
