前端人别踩坑:slice()克隆数据的真相与骚操作

@[toc]( 前端人别踩坑:slice()克隆数据的真相与骚操作)
前端人别踩坑:slice()克隆数据的真相与骚操作
开篇先唠两句
说实话,写这篇文章的时候,我手都在抖。不是激动,是想起了当年那个让我差点滚蛋的线上事故。
那时候我刚入行两年,觉得自己可牛了,什么ES6新特性、什么设计模式,张口就来。结果呢?一个slice()把我打回原形。那天晚上我蹲在出租屋的马桶上改bug,一边改一边骂自己是个憨憨。所以这篇文章,你们就当是一个老前端在群里发语音,想到哪说到哪,但句句都是血泪教训。
你是不是也干过这事儿?看到网上说slice()可以克隆数组,啪一下就写上去了,很快啊。然后本地测试没问题,提交代码,部署上线,美滋滋准备下班。结果半夜两点运维打电话来说数据乱了,用户投诉说购物车里的商品莫名其妙变成了别人的。你一脸懵逼打开代码,发现就是那一行.slice()惹的祸。
今天咱们就把这块掰开揉碎讲清楚,不是为了显得我多懂,是真的不想看到兄弟们再踩这个坑。毕竟,能早点下班陪对象,谁愿意对着电脑屏幕掉头发呢?
这俩slice()到底是个啥玩意儿
首先咱们得搞清楚一件事:JavaScript里有两个slice(),一个在String原型上,一个在Array原型上。这俩长得像,名字一样,但骨子里完全不是一回事。就像你叫张伟,隔壁工位也叫张伟,但人家是总监你是码农,能一样吗?
String.prototype.slice()
字符串的slice()最简单,它就是从字符串里切一段出来给你。字符串在JS里是不可变的(immutable),也就是说你切出来的新字符串和原来的那个,除了内容可能一样,内存地址完全是两码事。
// 字符串的slice(),简单得很const str ="Hello World";const sliced = str.slice(0,5); console.log(sliced);// "Hello" console.log(str === sliced);// false,虽然内容一样,但是新实例 console.log(str);// "Hello World",原字符串纹丝不动// 再来个骚点的,负数索引const fileName ="document.pdf";const extension = fileName.slice(-3);// 从倒数第三个开始切 console.log(extension);// "pdf"// 或者这样玩,提取文件名不要后缀const name = fileName.slice(0,-4);// 切到倒数第四个之前 console.log(name);// "document"看到没,字符串的slice()就是一把剪刀,咔嚓一下,给你一段新的。原字符串?动都不带动的,因为JS规定字符串不能改,你想改?只能生成新的。
Array.prototype.slice()
数组的slice()就复杂多了,也是我们今天的主角。它的作用是从数组里提取一段,返回一个新数组。听起来和字符串差不多对吧?但问题就出在这里——数组是引用类型,里面的元素如果是对象,那事情就开始变得有趣了。
// 基础用法,看着很美好const arr =[1,2,3,4,5];const newArr = arr.slice(0); console.log(newArr);// [1, 2, 3, 4, 5] console.log(arr === newArr);// false,看起来是新的数组 console.log(arr[0]=== newArr[0]);// true,但里面的元素是同一个引用// 这时候你改newArr的基本类型,原数组没事 newArr[0]=999; console.log(arr[0]);// 还是1,没问题// 但是!如果数组里有对象呢?const objArr =[{name:"张三"},{name:"李四"}];const clonedArr = objArr.slice(0); clonedArr[0].name ="王五";// 修改克隆数组里的对象属性 console.log(objArr[0].name);// "王五"!!!原数组也变了,惊不惊喜?// 更离谱的,你push一个新元素到克隆数组,原数组确实不会变 clonedArr.push({name:"赵六"}); console.log(objArr.length);// 还是2,这倒是没问题看到问题了吗?slice()返回的新数组,确实是个新数组(内存地址不一样),但数组里面的元素,如果是引用类型(对象、数组、函数等),那指向的还是原来的那个东西。这就叫浅克隆——只克隆了最外面那层壳,里面的东西还是共享的。
底层原理其实挺简单
咱们来扒一扒slice()的底裤,看看它到底干了啥。其实规范里写得明明白白:
- 创建一个新数组
- 把原数组指定范围的元素,按顺序塞到新数组里
- 返回这个新数组
关键点就在第二步——“把元素塞进去”。对于基本类型(number、string、boolean、null、undefined、symbol、bigint),塞进去的是值的副本;对于引用类型,塞进去的是引用的副本。啥意思?就是指针复制了一份,但指向的还是同一个内存地址。
画个图你们就懂了:
原数组 arr: [ 指针A, 指针B, 指针C ] ↓ ↓ ↓ 对象1 对象2 对象3 克隆数组 cloned: [ 指针A', 指针B', 指针C' ] ↓ ↓ ↓ 对象1 对象2 对象3 ← 还是原来的对象! 指针A和指针A'虽然不一样(数组位置不同),但存的地址值是一样的 所以当你通过cloned[0].name修改的时候,其实是在修改指针A指向的那个对象,而原数组arr[0]也是指向同一个对象,当然就一起变了。
浅克隆的真相来了
好,现在咱们进入核心环节。我当年就是没搞懂这个"浅克隆"的概念,才栽了大跟头。所谓浅克隆(Shallow Clone),就是只克隆对象的第一层属性。如果属性值是基本类型,没问题;如果是引用类型,克隆出来的新对象和原对象会共享这些引用类型的数据。
数组里套对象?直接芭比Q了
来,看个真实的业务场景。假设你在做一个电商后台,需要复制一个商品列表,然后对新列表做一些修改,最后保存为"促销版本"。
// 商品列表,每个商品是个对象const products =[{id:1,name:"iPhone 15",price:5999,stock:100},{id:2,name:"MacBook Pro",price:14999,stock:50},{id:3,name:"AirPods Pro",price:1999,stock:200}];// 当年我就是这么写的,觉得万无一失const promotionProducts = products.slice(0);// 然后给促销商品打八折 promotionProducts.forEach(item=>{ item.price = item.price *0.8;// 打八折});// 保存促销版本...saveToDatabase(promotionProducts);// 第二天发现,原商品价格也变了!!! console.log(products[0].price);// 4799.2,原价5999没了!我当时看到数据库里的数据,冷汗都下来了。所有商品的原价都变成了促销价,这意味着什么?意味着用户看到的价格全是错的,订单金额全是错的,财务对账对不上,老板差点把我祭天。
为啥会这样?因为slice()只克隆了数组这一层,数组里的每个商品对象,还是原来那些对象。你修改promotionProducts[0].price,其实就是修改products[0]指向的那个对象的price属性。两个数组都指向同一个商品对象,改一个当然全变了。
字符串倒是没问题
那为啥字符串没事?因为字符串是不可变的,你"修改"字符串其实是生成新的字符串,原字符串永远在那,动不了。
const str ="hello";const newStr = str.slice(0);// 你想改newStr?只能重新赋值,原str永远不变 newStr = newStr +" world"; console.log(str);// 还是"hello"但数组和对象不一样,它们是可变的(mutable)。你拿到引用就能直接改里面的内容,而不需要重新赋值。
画个图告诉你为什么引用类型会翻车
再来个更直观的例子,看看嵌套结构有多恐怖:
// 一个看似简单的配置对象数组const configs =[{name:"数据库配置",settings:{host:"localhost",port:3306}},{name:"缓存配置",settings:{host:"localhost",port:6379}}];const devConfigs = configs.slice(0);// 我想改开发环境的配置,不动生产环境 devConfigs[0].settings.host ="dev-server";// 结果生产环境的host也变了!!! console.log(configs[0].settings.host);// "dev-server" console.log(configs === devConfigs);// false,数组不一样 console.log(configs[0].settings === devConfigs[0].settings);// true,但里面的对象是一样的!看到没?slice()只保证了configs和devConfigs是两个不同的数组,但数组里的对象settings,还是同一个引用。这就好比你复制了一份文件夹列表,但文件夹里的文件还是原来的,你改复制列表里的文件,原文件也跟着变。
实际项目里我踩过的那些雷
理论讲完了,咱们来点真实的。我这些年因为slice()浅克隆的问题,踩过的坑能写一本血泪史。
案例一:电商购物车数据造假事件
那是2021年,我在一家电商公司做前端。当时有个需求,用户点击"对比商品"时,要把选中的商品从购物车列表里复制出来,做一个对比浮窗。我当时心想,这不简单吗?
// 购物车数据const cartList =[{skuId:"1001",name:"耐克运动鞋",price:899,attrs:{color:"红色",size:"42"},quantity:1},// ... 其他商品];// 我的"聪明"做法const compareList = cartList.filter(item=> selectedIds.includes(item.skuId)).slice(0);// 用户在对比浮窗里修改数量,看看总价 compareList.forEach(item=>{// 这里有个交互,用户可以在对比时调整数量看价格变化 item.quantity =3;// 假设用户想看3件多少钱 item.attrs.color ="蓝色";// 甚至想换颜色看看});我当时测试的时候,只看了对比浮窗的数据,没问题。结果上线后,有用户反馈:我在对比页面改了数量,怎么购物车里也变了?更离谱的是,颜色也变了,但我明明没选那个颜色啊!
产品经理拿着用户截图来找我,脸都是绿的。我一看代码,当场想抽自己。slice()克隆出来的compareList,里面的每个商品对象和cartList是共享的。用户改对比页面的数量,购物车的数量也跟着变;改颜色属性,购物车的颜色也变了。这在用户看来,就是"数据造假",“我明明没操作,数据自己变了”。
那天晚上我修复bug,把代码改成了深克隆,还写了整整一页的复盘文档。从那以后,我看到slice()克隆对象数组,第一反应就是警惕。
案例二:用户信息嵌套对象凉凉
还有个更隐蔽的坑。有一次做用户管理后台,需要复制一个用户的信息,用来创建"相似用户"。
const user ={id:10086,name:"张三",profile:{age:25,address:{city:"北京",detail:"朝阳区xxx"}},permissions:["read","write"]};// 我想复制这个用户,改改信息变成新用户const newUser = user.profile.slice(0);// 等等,profile是对象,不是数组!// 实际上我当时用的是const newUser ={...user };// 展开运算符也是浅克隆!// 或者const newUser = Object.assign({}, user);// 同样是浅克隆这里我犯了个错,以为展开运算符...是深克隆,结果它和slice()一样,也是浅克隆。然后我修改newUser.profile.address.city = "上海",原用户user的地址也变成了上海。两个不同的用户,地址却永远同步变化,这数据还能要吗?
更惨的是,这个问题是在数据入库后才发现的,因为创建新用户时,原用户的地址也被改了,导致物流信息全错。那次我差点被扣绩效,幸好老大看我态度好,只让我请全组喝了奶茶。
产品经理当场就炸了
说个搞笑的事。那次电商项目出bug后,产品经理在群里@我,说"用户投诉数据造假,你怎么回事?"。我当时还在家打游戏,看到消息手一抖,英雄都送塔了。
我回了一句"可能是引用问题",产品经理直接语音过来:“什么引用?什么引用!用户看到的是价格变了!你跟我说引用?”。我当场语塞,不知道怎么解释浅克隆深克隆。最后憋出一句"就是数据复制的时候没复制全",产品经理更懵了:“复制还有复制不全的?你当这是复印身份证呢?”
后来我在周会上用画图的方式解释了半小时,产品经理才似懂非懂地说:"哦,就是说你们程序员的复制,有时候是复印,有时候是贴快捷方式?"我一拍大腿:“对对对,就是快捷方式!我用的那个方法,其实是贴快捷方式!”
从此我在组里多了个外号:“快捷方式工程师”。
什么时候能用什么时候别用
好了,血泪史讲完了,咱们说点实用的。slice()不是不能用,而是要看场景。就像菜刀能切菜也能切手,关键看你怎么用。
一维数组纯基本类型,放心用
如果你的数组里全是数字、字符串、布尔值这些基本类型,slice()就是安全的,随便克隆。
// 这些场景,slice()完全没问题// 1. 数字数组const scores =[85,92,78,96,88];const scoresCopy = scores.slice(0); scoresCopy[0]=100;// 修改副本 console.log(scores[0]);// 还是85,原数组安全// 2. 字符串数组const tags =["JavaScript","Vue","React"];const tagsCopy = tags.slice(); tagsCopy.push("Node.js");// 添加新元素 console.log(tags.length);// 还是3,原数组没影响// 3. 布尔值数组const flags =[true,false,true];const flagsCopy = flags.slice(0,2);// 只克隆前两个 flagsCopy[0]=false; console.log(flags[0]);// 还是true// 4. 混合基本类型const mixed =[1,"two",true,null,undefined,Symbol('foo')];const mixedCopy = mixed.slice(); mixedCopy[0]=999; console.log(mixed[0]);// 还是1这些场景下,slice()性能还不错,代码也简洁,用就完事了。
字符串随便搞,不会出事
字符串的slice()前面说了,因为不可变性,随便怎么切都不会影响原字符串。
const text ="JavaScript是一门很棒的语言";const part1 = text.slice(0,10);const part2 = text.slice(-6);const part3 = text.slice(4,-6);// 随便你怎么切,text永远不变 console.log(text);// "JavaScript是一门很棒的语言"嵌套结构、对象数组,赶紧跑
只要你的数组或对象里有引用类型,赶紧跑,别用slice()做克隆。
// 这些场景,slice()会坑死你// 1. 对象数组 - 高危const users =[{name:"张三"},{name:"李四"}];const usersCopy = users.slice(); usersCopy[0].name ="王五";// 原数组也变了,危险!// 2. 二维数组 - 高危const matrix =[[1,2],[3,4]];const matrixCopy = matrix.slice(); matrixCopy[0][0]=999;// matrix[0][0]也变成999了,危险!// 3. 数组里套函数 - 高危const fns =[()=> console.log(1),()=> console.log(2)];const fnsCopy = fns.slice();// 虽然函数一般不会被修改,但万一有人给函数加属性呢? fnsCopy[0].customProp ="haha"; console.log(fns[0].customProp);// "haha",也共享了// 4. 复杂嵌套对象 - 极高危const data ={list:[{id:1,items:[{name:"a"},{name:"b"}]}]};// 这种结构,slice()连第一层都搞不定,因为data是对象不是数组// 就算你用Object.assign,也是浅克隆,嵌套的items还是共享的需要深克隆?JSON.parse(JSON.stringify())或者structuredClone()安排上
那遇到嵌套结构怎么办?上深克隆啊!
// 方案1:JSON序列化(最简单,但有缺陷)const deepClone =JSON.parse(JSON.stringify(original));// 缺陷演示:const obj ={name:"测试",date:newDate(),// 会变成字符串fn:function(){},// 会丢失undefinedKey:undefined,// 会丢失symbolKey:Symbol('test'),// 会丢失circular:null// 循环引用会报错};// obj.circular = obj; // 加上这行JSON.stringify直接报错const cloned =JSON.parse(JSON.stringify(obj)); console.log(cloned.date);// "2023-10-27T..." 字符串,不是Date对象 console.log(cloned.fn);// undefined,函数没了// 方案2:structuredClone()(现代浏览器支持,推荐)const deepClone2 =structuredClone(original);// 这个能处理Date、RegExp、Map、Set等,但还是不能处理函数和循环引用const obj2 ={date:newDate(),map:newMap([['key','value']]),set:newSet([1,2,3])};const cloned2 =structuredClone(obj2); console.log(cloned2.date instanceofDate);// true,保留了类型 console.log(cloned2.map instanceofMap);// true// 方案3:lodash的cloneDeep(最稳妥,老项目常用)// import cloneDeep from 'lodash/cloneDeep';// const deepClone3 = cloneDeep(original);// 方案4:自己写递归(面试常考,实际项目慎用)functiondeepClone(obj, hash =newWeakMap()){if(obj ===null)returnnull;if(typeof obj !=='object')return obj;if(obj instanceofDate)returnnewDate(obj);if(obj instanceofRegExp)returnnewRegExp(obj);if(hash.has(obj))return hash.get(obj);// 处理循环引用const cloneObj =newobj.constructor(); hash.set(obj, cloneObj);for(let key in obj){if(obj.hasOwnProperty(key)){ cloneObj[key]=deepClone(obj[key], hash);}}return cloneObj;}出问题了怎么排查
万一你不确定自己是不是遇到了浅克隆的问题,怎么排查?我来分享几个我常用的调试技巧。
先console.log()看看引用地址是不是一样
最简单的方法,直接打印看看两个变量是不是指向同一个东西。
const original =[{name:"test"}];const cloned = original.slice();// 看数组本身 console.log(original === cloned);// false,数组是新的// 看数组里的元素 console.log(original[0]=== cloned[0]);// true!元素是同一个对象,这就是问题所在// 如果这行返回true,那你修改cloned[0]的属性,original[0]也会变用Object.is()或者===对比一下
严格相等运算符===可以帮你判断两个变量是不是同一个引用。
const a ={x:1};const b = a;const c ={x:1}; console.log(a === b);// true,同一个引用 console.log(a === c);// false,内容一样但不同引用// 对于数组里的元素const arr =[a];const arrCopy = arr.slice(); console.log(arr[0]=== arrCopy[0]);// true,共享同一个对象浏览器开发者工具里看内存引用
Chrome DevTools里有个Memory面板,可以拍堆快照(Heap Snapshot),看两个数组是不是共享了同一个对象。不过这个有点高级,一般console.log就够了。
实在不行加个断点一步步跟
最笨但最有效的方法,在修改克隆数据的地方加断点,然后单步执行,观察原数据的变化。
const list =[{value:1}];const copy = list.slice();// 在这里打断点debugger; copy[0].value =999;// 然后观察list[0].value的变化几个实用骚操作分享
虽然slice()有坑,但用好了也是神器。分享几个我在项目中常用的骚操作。
slice(0)比concat()性能稍好一点点
很多人用concat()来克隆数组,其实slice(0)性能稍微好一点点,虽然现代引擎优化后差别不大,但心理安慰嘛。
// 两种克隆方式const arr =[1,2,3];const copy1 = arr.slice(0);const copy2 = arr.concat();// slice(0)通常快一点点,因为concat要处理参数// 但如果数组很大,这点差异可以忽略配合展开运算符…用起来更香
ES6的展开运算符...也是浅克隆,和slice()效果一样,但写起来更现代。
const arr =[1,2,3];// 老派写法const copy1 = arr.slice(0);// 现代写法const copy2 =[...arr];// 两者都是浅克隆,效果完全一样// 但展开运算符更直观,一眼就能看出是"展开所有元素"不过要注意,展开运算符也是浅克隆!该翻车的地方还是会翻车。
const arr =[{x:1}];const copy =[...arr]; copy[0].x =999;// arr[0].x也会变成999大数组切片处理,避免一次性加载太多
slice()的本意是"切片",用来做大数据分页处理很合适。
// 假设有10万条数据,不能一次性渲染const bigData =newArray(100000).fill(0).map((_, i)=>({id: i,data:"xxx"}));// 分页处理,每次只处理100条const pageSize =100;const currentPage =1;const pageData = bigData.slice((currentPage -1)* pageSize, currentPage * pageSize );// 这样就不会一次性占用太多内存函数式编程里经常这么玩,immutable那套
在Redux或者Vuex里,经常需要保持数据不可变,这时候slice()配合其他方法很好用。
// Redux reducer里的常见用法functionreducer(state =[], action){switch(action.type){case'ADD_ITEM':// 返回新数组,不修改原数组return[...state, action.payload];case'REMOVE_ITEM':// 用slice和展开运算符组合return[...state.slice(0, action.index),...state.slice(action.index +1)];case'UPDATE_ITEM':// 修改某一项,返回新数组return state.map((item, index)=> index === action.index ?{...item,...action.payload }// 展开运算符也是浅克隆,但这里item是新的: item );default:return state;}}看到没,这里slice()配合展开运算符,实现了不可变更新。但注意,如果item本身有嵌套对象,还是要用深克隆。
性能这块也得聊聊
用slice()的时候,性能也是要考虑的,特别是数据量大的时候。
slice()时间复杂度O(n),数据量大时注意
slice()要遍历指定范围的元素,然后复制到新数组,时间复杂度是O(n)。如果数组很大,这个操作会比较耗时。
// 测试一下性能const bigArr =newArray(1000000).fill(0).map((_, i)=> i); console.time('slice');const copy = bigArr.slice(0); console.timeEnd('slice');// 大概几毫秒到几十毫秒// 如果只切一部分,会快一些 console.time('slice partial');const partial = bigArr.slice(0,1000); console.timeEnd('slice partial');// 快很多浅克隆比深克隆快多了,看场景选
虽然slice()是O(n),但深克隆可能是O(n^m),因为嵌套结构要递归。
const nested =newArray(1000).fill(0).map(()=>({a:{b:{c:{d:1}}}})); console.time('shallow');const shallow = nested.slice(); console.timeEnd('shallow');// 很快,只复制一层 console.time('deep');const deep =JSON.parse(JSON.stringify(nested)); console.timeEnd('deep');// 慢很多,要递归所有层级所以如果你确定数据只有一层,用slice()没问题;如果有嵌套,该用深克隆还得用,别为了性能牺牲正确性。
移动端别随便克隆大数组,内存会爆
移动端内存有限,克隆大数组可能导致OOM(Out of Memory)。
// 在移动端,这种操作很危险const hugeData =fetchHugeData();// 几十万条数据const copy = hugeData.slice();// 内存瞬间翻倍// 更好的做法是,不要克隆,直接操作原数据(如果业务允许)// 或者用视图模式,不复制数据有次克隆10万条数据,页面直接卡死
说个真事。有次我在做大数据表格,用户要求能"复制当前视图"。我直接用slice()克隆了10万条数据,结果页面卡了3秒,浏览器提示"页面无响应"。
后来优化成虚拟滚动+按需加载,只克隆可视区域的数据,才解决问题。所以性能优化不只是算法复杂度,还要考虑用户体验。
替代方案有哪些
最后总结一下slice()的替代方案,各种场景都有。
structuredClone()新API,深克隆神器
现代浏览器(Chrome 98+、Firefox 94+、Safari 15.4+)支持的原生深克隆API。
const original ={date:newDate(),map:newMap([['key','value']]),set:newSet([1,2,3]),array:[1,2,{nested:true}]};const cloned =structuredClone(original);// 优点:原生支持,能处理Date、Map、Set、ArrayBuffer等// 缺点:不能克隆函数、DOM节点、原型链,循环引用会报错lodash的cloneDeep(),老项目还在用
最稳妥的深克隆方案,处理循环引用也没问题。
// npm install lodashimport cloneDeep from'lodash/cloneDeep';const obj ={a:{b:{c:1}}}; obj.self = obj;// 循环引用const cloned =cloneDeep(obj);// 没问题,能正确处理 console.log(cloned.self === cloned);// true,循环引用也保留了自己写递归函数,练手不错
面试常考,但实际项目建议用成熟的库。
functiondeepClone(obj, hash =newWeakMap()){// 处理nullif(obj ===null)returnnull;// 处理基本类型if(typeof obj !=='object')return obj;// 处理Dateif(obj instanceofDate)returnnewDate(obj);// 处理RegExpif(obj instanceofRegExp)returnnewRegExp(obj);// 处理循环引用if(hash.has(obj))return hash.get(obj);// 处理数组if(Array.isArray(obj)){const cloneArr =[]; hash.set(obj, cloneArr);for(let i =0; i < obj.length; i++){ cloneArr[i]=deepClone(obj[i], hash);}return cloneArr;}// 处理对象const cloneObj ={}; hash.set(obj, cloneObj);for(let key in obj){if(obj.hasOwnProperty(key)){ cloneObj[key]=deepClone(obj[key], hash);}}return cloneObj;}// 测试const test ={a:1,b:{c:2},d:[1,2,3]}; test.circular = test;// 循环引用const cloned =deepClone(test); console.log(cloned.b === test.b);// false,深克隆成功 console.log(cloned.circular === cloned);// true,循环引用正确处理Immutable.js,大型项目可以考虑
Facebook出的不可变数据库,适合超大型项目。
import{ Map, List }from'immutable';const list =List([1,2,3]);const newList = list.push(4);// 返回新列表,原列表不变const map =Map({a:1,b:2});const newMap = map.set('c',3);// 返回新map,原map不变// 内部用持久化数据结构,性能很好// 但API和原生JS不一样,学习成本高最后说点掏心窝子的
写到这,我想起了刚入行时老大跟我说的一句话:“别迷信任何方法,理解原理最重要。”
slice()本身没问题,它是JavaScript里很有用的方法。问题出在我们不了解它的边界,盲目使用。就像一把瑞士军刀,你用它来拧螺丝没问题,但要是用来撬保险柜,那肯定要出事。
别迷信某个方法,理解原理最重要
我见过太多人(包括我自己),网上看到某个"技巧"就拿来用,不去深究为什么。slice()克隆数组、展开运算符复制对象、JSON序列化深克隆,这些技巧流传很广,但每种都有适用场景和坑。只有理解了背后的内存模型、引用类型vs基本类型、浅克隆vs深克隆,才能在实际项目中做出正确选择。
我当年也是踩坑踩多了才懂
说真的,我现在能写这篇文章,不是因为看了多少书,是因为踩了太多坑。每次线上事故,每次深夜改bug,都是一次深刻的学习。所以兄弟们,别怕踩坑,但踩了坑一定要复盘,搞清楚为什么会错,下次才能避开。
现在看到slice()克隆第一反应就是警惕
现在我写代码,只要看到slice()用于克隆,第一反应就是:这里的数据结构是什么?有没有嵌套对象?需不需要深克隆?这种警惕性可能让我多写几行代码,但能避免很多潜在的bug。
希望兄弟们别走我老路,早点下班陪对象
最后,说点实在的。咱们写代码是为了生活,不是为了加班。因为一个简单的slice()问题导致线上事故,然后熬夜修bug、写复盘、被批评,值得吗?不值。所以花点时间把这些基础概念搞清楚,工作中多留个心眼,能省下很多时间。
省下来的时间干嘛?陪对象啊!打游戏啊!睡觉啊!哪个不比加班香?
好了,今天就聊到这。这篇文章就像我在群里发语音,有点碎,有点乱,但都是真心话。如果你觉得有用,下次遇到slice()的问题,能想起这篇文章,我就知足了。
记住:代码是死的,人是活的,别让一行.slice()毁了你的周末。
欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。
推荐: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等工具 |
吾辈才疏学浅,摹写之作,恐有瑕疵。望诸君海涵赐教。望轻喷,嘤嘤嘤
非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。愿斯文对汝有所裨益,纵其简陋未及渊博,亦足以略尽绵薄之力。倘若尚存阙漏,敬请不吝斧正,俾便精进!
