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

前端人别踩坑: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()的底裤,看看它到底干了啥。其实规范里写得明明白白:

  1. 创建一个新数组
  2. 把原数组指定范围的元素,按顺序塞到新数组里
  3. 返回这个新数组

关键点就在第二步——“把元素塞进去”。对于基本类型(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()只保证了configsdevConfigs是两个不同的数组,但数组里的对象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等工具

吾辈才疏学浅,摹写之作,恐有瑕疵。望诸君海涵赐教。望轻喷,嘤嘤嘤

非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。愿斯文对汝有所裨益,纵其简陋未及渊博,亦足以略尽绵薄之力。倘若尚存阙漏,敬请不吝斧正,俾便精进!
在这里插入图片描述

Read more

2026年AI Agent实战:从玩具到生产力的落地手册(附源码)

2026年AI Agent实战:从玩具到生产力的落地手册(附源码)

欢迎文末添加好友交流,共同进步! “ 俺はモンキー・D・ルフィ。海贼王になる男だ!” * 前言 * 目录 * 一、AI Agent 的核心架构 * 1.1 什么是AI Agent? * 1.2 2026年Agent技术栈全景 * 二、从零搭建生产级Agent框架 * 2.1 项目结构设计 * 2.2 核心代码:Agent基类 * 2.3 记忆管理系统 * 三、三大核心技术实现 * 3.1 ReAct框架:推理+行动协同 * 3.2 工具调用系统 * 3.3 任务规划器 * 四、实战案例:智能客服Agent * 4.1 场景分析

By Ne0inhk
Trae AI IDE 全网最全的使用教程

Trae AI IDE 全网最全的使用教程

Trae AI IDE 全网最全的使用教程 近期,字节发布了一款 AI Coding 产品 —— Trae,它是一款对标 Cursor 和 Windsurf 的全新 IDE,也是一款真正为中文开发者量身定制的工具,可谓是中文开发者的福音。 其优雅的 UI、丝滑的交互、母语级的支持、更高的 AI 集成度、更‮然自‬的交‮式互‬对话开发、更‮‬精准的 AI 生‮效成‬果,都让你感到亲切和惊艳! 它不再是一个工具,而是一个能 “思考” 和 “共创” 的协作者,帮助你更灵活的调用 AI 参与项目,实现更高效率、更好效果的开发体验。 一、安装下载

By Ne0inhk
被问爆的Agent实战:从0到1搭建可落地AI智能体

被问爆的Agent实战:从0到1搭建可落地AI智能体

🎁个人主页:User_芊芊君子 🎉欢迎大家点赞👍评论📝收藏⭐文章 🔍系列专栏:AI 文章目录: * 【前言】 * 一、先搞懂:2026年爆火的AI Agent,到底是什么? * 1.1 Agent的核心定义 * 1.2 Agent的4大核心能力 * 1.3 2026年Agent的3个热门落地场景 * 二、框架选型:2026年6大主流Agent框架,新手该怎么选? * 三、实战环节:从0到1搭建可落地的“邮件处理Agent”(全程代码+步骤) * 3.1 实战准备:环境搭建(10分钟搞定) * 3.1.1 安装Python环境 * 3.1.2 创建虚拟环境(避免依赖冲突) * 3.

By Ne0inhk
人工智能:自然语言处理与计算机视觉的融合应用

人工智能:自然语言处理与计算机视觉的融合应用

人工智能:自然语言处理与计算机视觉的融合应用 学习目标 💡 理解自然语言处理(NLP)与计算机视觉(CV)融合的基本概念和重要性 💡 掌握NLP与CV融合的主要方法和技术 💡 学会使用前沿模型(如CLIP、ALIGN、ViLT)进行多模态融合 💡 理解融合应用的场景(如图像字幕生成、视觉问答、多模态检索) 💡 通过实战项目,开发一个图像字幕生成应用 重点内容 * NLP与CV融合的基本概念 * 主要融合方法和技术 * 前沿融合模型(CLIP、ALIGN、ViLT) * 融合应用场景(图像字幕生成、视觉问答、多模态检索) * 实战项目:图像字幕生成应用开发 一、NLP与CV融合的基本概念 1.1 多模态学习的重要性 多模态学习(Multimodal Learning)是指处理和理解来自多个模态(如文本、图像、音频)的数据的过程。NLP与CV的融合是多模态学习的一个重要分支,它结合了文本理解和图像分析的能力,使计算机能够更全面地理解和解释现实世界的信息。 1.

By Ne0inhk