一、复杂数据类型-“对象”的地址引用方式,不理解清楚,会出大乱子

复习一下基础概念(老司机略过):

JS的数据可以分为简单类型(数字、字符串、布尔值、null和undefined)和 复杂数据类型(对象),主要的不同是:简单数据类型是栈内存直接引用,复杂数据类型是地址引用的,放在堆内存中,所以也叫引用类型(堆栈概念庞大,这里不讲,百度资料很多)。下边讲到的诸多问题的导火索,就是这个地址引用。

这里不谈“js中一切皆对象”这个言论,因为数字、字符串、布尔值等貌似‘对象’,因为他们有用方法,但请看定义,【js中‘对象’是可变的键控集合】,很显然,数字、字符串等不满足,因为他们不可变,所以说,js中俗定意义来讲,能称满足对象属性的大众意义数据类型是:对象、数组、函数、正则。

记住这四个家伙,他们都是地址引用,接下来的诸多场景,出现问题,他们都有份。

1、场景一:函数形参引用复杂类型,请小心赋值,会改变实参的哦 ,下面是错误情况

  1. var obj = {};//全局建一个对象
  2. obj.n = ;
  3. var arr = [];//全局建一个数组
  4. arr.n = ;
  5. var fun = function(){};//全局建一个函数
  6. fun.n = ;
  7. var reg = /\d/;////全局建一个正则
  8. reg.n = ;
  9.  
  10. function bang(p){//形参p 改变形参p的n属性 回头看看会不会影响实参
  11. p.n = ;
  12. }
  13. bang(obj);
  14. console.log(obj)//{n:2} //obj.n 已经改变 1->2 影响了实参 全局变量obj
  15. bang(arr);
  16. console.log(arr.n)//2 arr也沦陷 arr.n值变为2
  17. bang(fun);
  18. console.log(fun.n)//2 fun.n也变了
  19. bang(reg);
  20. console.log(reg.n)//2 reg.n也变了

PS:同理参数是数组的,在函数内改变形参内的值,也会改变实参

有一些看似解决的方案,但实际并没有解决,或是遗留隐患,希望你不要中枪,PS:下面主要讨论对象和数组。(因为实际中少有给正则变量增加属性,后边则不讨论正则,function用作形参时大多体现在回调上,基本也不会去给函数赋一个属性,构建函数也是在原型上赋值新属性,所以函数也pass)

失败方案1:形参使用解构语句,或使用Object.assign重复制对象

失败原因:解构与Object.assign都是潜复制

  1. var obj = {
  2. n:,
  3. x:{
  4. nn:
  5. }
  6. };//全局建一个对象 注意obj.x也是一个对象
  7.  
  8. function bang({...p}){//形参p 改变形参p的n属性 p.x.nn属性 回头看看会不会影响实参
  9. p.n = ;
  10. p.x.nn = ;
  11. }
  12. bang(obj);
  13. console.log(obj.n)//1 //解构确实保证了一级属性 没有改变
  14. console.log(obj.x.nn)//2 //但是二级属性还是受到了污染
  15. //为什么会这样?
  16. //因为解构只是浅复制,只是对第一层简单类型的数据实现复制,而对象类型的属性复制的只是地址,引用的还是一个堆内存中的存储位置

失败方案2:使用Object.assign复制对象

失败原因:解构与Object.assign都是潜复制

  1. var obj = {
  2. n:,
  3. x:{
  4. nn:
  5. }
  6. };//全局建一个对象 注意obj.x也是一个对象
  7.  
  8. function bang({...p}){//先assign 形参p 改变形参p的n属性 p.x.nn属性 回头看看会不会影响实参
  9. var pp = Object.assign({},p);
  10. pp.n = ;
  11. pp.x.nn = ;
  12. }
  13. bang(obj);
  14. console.log(obj.n)//1 //解构确实保证了一级属性 没有改变
  15. console.log(obj.x.nn)//2 //但是二级属性还是受到了污染
  16. //为什么会这样?
  17. //因为Object.assign同样也是浅复制

2、场景二:数组方法slice只传递引用,不是真正意义的复制

  1. //slice只是传递引用,不是真正意义的复制
  2. var arr = [{a:},{a:}]; //arr [{a:11},{a:11}]
  3. var arr1 = arr.slice(); //arr1 [{a:11},{a:11}]
  4. arr1[].a =
  5. //arr1[0]指向的还是arr[0]的引用地址
  6. //arr1 [{a:22},{a:11}] arr [{a:22},{a:11}]
  7. //concat也存在同样的问题 [].concat(arr)  

还有forEach、map、filter等,回调中的引用会不会也受地址引用的影响,答案是肯定的。如果在回调中修改了形参,会修改原数组,这是我们都不想要的。

  1. var arr = [{n:{nn:}},{n:{nn:}}]
  2.  
  3. //我们常用map来加工数组,生产新数组,但如果在回调中修改了item,是否会影响到原数组呢?
  4. var newArr = arr.map(item=>{
  5. item.n.nn++;
  6. return item;
  7. })
  8. console.log(newArr)//[{n:{nn:2}},{n:{nn:3}}] 我们得到了加工完毕的新数组
  9. console.log(arr)//[{n:{nn:2}},{n:{nn:3}}] 但原数组arr也被影响了

会不会有这种想法,想要使用map实现数组复制的,但这种复制没什么用哦

  1. var arr = [{n:{nn:}},{n:{nn:}}]
  2.  
  3. var newArr = arr.map(v=>v);//已经用map“复制”了个newArr,但它真的“干净”么?
  4. newArr[].n.nn = ;//我修改了newArr,会不会改变arr呢
  5.  
  6. console.log(newArr)//[{n:{nn:99}},{n:{nn:3}}]
  7. console.log(arr)//[{n:{nn:99}},{n:{nn:3}}] arr改变了,很明显map的复制没什么用
  1. var arr = [{n:{nn:}},{n:{nn:}}]
  2.  
  3. //你会说这样直接复制太简单了,肯定会影响,我见过下面这样写的
  4. var newArr = arr.map(v=>{
  5. var vv = Object.assign({},v);
  6. return vv;
  7. });
  8. newArr[].n.nn = ;//我修改了newArr,会不会改变arr呢
  9.  
  10. console.log(newArr)//[{n:{nn:99}},{n:{nn:3}}]
  11. console.log(arr)//[{n:{nn:99}},{n:{nn:3}}] arr改变了

总结:[对象通过引用来传递,他们永远不会被复制],函数形参引用和“=”等号赋值传递的都是引用地址,解构赋值和Object.assign都是浅复制,复制的对象属性、数组属性都是地址指针。

解决办法,jQuery的$.extend()方法已经封装了深拷贝,项目中没有依赖jQuery的可以自己封装一个deepCope方法,网上资源有的是。

此处有但是转折:地址引用虽然给我们带来了一些麻烦,但是事有两面性,有时候利用这个特性,能帮我们简化一些问题。比如数组的include方法,就可以有如下操作

  1. var arr1 = [{n:},{n:}]
  2. var arr2 = [{n:},{n:}]
  3. var newArr= arr1.concat(arr2);//arr1、arr2合成了一个新数组 newArr
  4.  
  5. //在后边的使用中,我又想知道newArr中的元素都出自哪里,就可以用includes查出
  6. newArr.forEach(v=>{
  7. console.log(arr1.includes(v)) // true true false false
  8. })
  9.  
  10. //因为引用的是地址,includes对比的也是地址,所以对于追踪来说反而有利
  11. //这个场景在dom中的列表点击上,比较常见

、原型继承的问题,原型链中慎用引用类型的属性,不要修改实例继承的原型的引用类型属性,因为这将改变原型的值,造成不好的连锁反应。(原型的这些东西比较抽象,老手不用说就明白,新手怎么说也晕乎,还是直接上demo吧)

  1. //方法 以参数为原型,创建实例
  2. function creatObject (o){
  3. var Fun = function(){}
  4. Fun.prototype = o;
  5. return new Fun();
  6. }
  7. //创建目标原型
  8. var peo = {
  9. name:'中国人',
  10. body:{
  11. height:,
  12. weight:
  13. }
  14. }
  15. //以peo为原型,创建继承了其属性的实例 stu
  16. var stu = creatObject(peo);
  17. var teacher = creatObject(peo);
  18. console.log(stu.body.weight);//140
  19. //重点:修改实例stu继承的原型属性 body.weight 会反射到原型peo中的去 又会进而影响其他所有继承peo的实例
  20. //这是原型链本身经典问题:就是原型中的引用类型属性会被实例共享,也就能在实例中重写原型属性,造成连锁灾难
  21. //所以尽量在构造函数中,而不是在原型上定义属性;开发中尽量少单独使用原型链,最好在没搞清原型继承关系前,不要轻易使用原型链
  22. stu.body.weight = ;
  23. console.log(peo.body.weight)//
  24. console.log(teacher.body.weight)//

三、合理使用链式写法和try cache,保持必要的代码容错,提升代码的健壮性。避免常见的卡死级错误,如 Uncaught TypeError: Cannot read property 'XX' of undefined 

  上面这个报错信息大家应该都很熟悉,很常见,但危害很大,能直接卡死进程,导致下面的程序都无法进行,经常在新手代码中出现,还有就是对接api接口,因为数据格式不稳定时产生(api接口数据中没有出现期待的字段)。解决办法有try catch 和 链式写法,个人推荐一般场景用链式就足够了,但像转JSON的时候,就要用try catch了。

  1. //1、链式写法 设置默认值,增强容错的同时,还very优雅简洁
  2. var data = {};
  3. var array = [];
  4.  
  5. //下面这样就会出问题 因为data.d是undefined undefined是没有属性的,调用undefined的属性就会报错
  6. var errData = data.d.d; //Uncaught TypeError: Cannot read property 'd' of undefined
  7.  
  8. //怎样避免这样的错误
  9. //我推荐链式写法,干净利索,如下
  10.  
  11. //错误写法
  12. var res = data.a.b; // error
  13. var res1 = array[].a;// error
  14. //正确写法
  15. var res = (data.a||{}).b;
  16. var res1 = (array[]||{}).a;
  1. // 2、try catch解决json转换问题 这里不谈try catch的缺陷,万事两面性,只看取舍
  2. var jsonStr = '{n:11}';
  3.  
  4. //json格式不对 报错 Uncaught SyntaxError: Unexpected token n in JSON at position 1
  5. // var jsonData = JSON.parse(jsonStr) //error
  6.  
  7. //这个情景经常在对接api接口时碰到,后台没有给我们返回期望值,导致程序报错崩溃,怎么办?
  8. //好的程序应该在这里预先做好容错,保证代码的健壮性
  9. var jsonData;
  10. try{
  11. jsonData = JSON.parse(jsonStr)
  12. }catch(err){
  13. jsonData = {};//当出现问题时,我们要给一个容错的默认值
  14. console.warn("JSON转换失败:",err)
  15. }
  16. console.log(jsonData)

、Math.max 和 Math.min 求极值

  1. //Math.max求极值简单方便 Math.min同理
  2. Math.max.apply( null, [,,])//
  3. Math.max.apply( null, [,,,null])//
  4. Math.max.apply( null, [,,,''])//11
  5.  
  6. //但Math.max对参数类型兼容比较低,如下操作都会失败 Math.min同理
  7. Math.max.apply( null, [,,,undefined]) //NaN
  8. Math.max.apply( null, [,,,'string']) //NaN
  9.  
  10. // 我们可以用map加一层容错处理
  11. Math.max.apply( null, [,,,'string',,undefined,null,'',-].map(v=>Number(v)||)) //

原创博文,求收藏、引用,不要复制粘贴

js语法中一些容易被忽略,但会造成严重后果的细节的更多相关文章

  1. 关于js语法中的一些难点(预解析,变量提前,作用域)

    ******标题很吓人************ 其实就是一个小小的例子 ,从例子中简单的分析一下作用域.预解析和变量提前的概念 <!DOCTYPE html> <html> & ...

  2. 【坑】js语法中一些小细节 不注意也出坑 随笔记下 留待后查

    1.switch case内 区分数字 与 字符 ',bl; switch(+lv){ :bl = 1.7;break; :bl = 1.55;break; :bl = 1.4;break; ; } ...

  3. js语法没有任何问题但是就是不走,检查js中命名的变量名,用 service-area错误,改service_area (原)

    js语法没有任何问题但是就是不走,检查js中命名的变量名,用 service-area错误,改service_area

  4. Mongodb中的js语法

    定义一个变量 > var len = 10; For循环 这里的db和data都可以作为对象 save是方法 接收一个临时定义的对象 > for(var i = 0; i < len ...

  5. JS 正则表达式中的特殊字符

    正则表达式中的特殊字符 字符 含意 \ 做为转意,即通常在"\"后面的字符不按原来意义解释,如/b/匹配字符"b",当b前面加了反斜杆后/\b/,转意为匹配一个 ...

  6. webpack 之 js语法检查eslint

    webpack 之 js语法检查eslint // 用来拼接绝对路径的方法 const {resolve} = require('path') const HtmlWebpackPlugin = re ...

  7. js文件中函数前加分号和感叹号是什么意思?

    本文转自:http://blog.csdn.net/h_o_w_e/article/details/51388500 !function(){}();   !有什么用? 从语法上来开,JavaScri ...

  8. Handlebars.js循环中索引(@index)使用技巧(访问父级索引)

    使用Handlebars.js过程中,难免会使用循环,比如构造数据表格.而使用循环,又经常会用到索引,也就是获取当前循环到第几次了,一般会以这个为序号显示在页面上. Handlebars.js中获取循 ...

  9. js jquery中 的数据类型

    任何一门语言, buguan 是动态的, 还是像C语言的, 都有严格的 类型 "概念的", 这个是由于 编译器和解释器要求的, 需要的. 所以在是使用像 js, jquey ,ph ...

随机推荐

  1. html头部中各式各样的meta

    在写网页的过程中,第一步就是创建一个html文档.如下是最简单的 html5 文档. <!DOCTYPE html> <html lang="en"> &l ...

  2. 微信小程序开发注意事项

    1.小程序方法是异步的,开发过程要注意此点,避免在需要同步执行过程中的错误,尤其是在app.js处理登入的时候要特别注意. 2.小程序api.组件依赖微信的版本,注意版本的兼容,可以通过版本判断当前的 ...

  3. EF Code Frist 执行 nuget命令

    1.Enable-Migrations -EnableAutomaticMigrations2.Add-Migration InitialCreate3.Update-Database -Verbos ...

  4. IntelliJ idea 撤回(已经commit未push的)操作

    VSC  => Git => reset head => 退回到上次commit => 退回到第2次提交之前 => 退回到指定commit版本

  5. 云计算入门(一)、使用vagrant+virtualbox安装虚机

    一.vagrant和virtaulbox简介 Vagrant是一个基于Ruby的工具,用于创建和部署虚拟化开发环境,我们可以使用它来干如下这些事: 建立和删除虚拟机配置虚拟机运行参数管理虚拟机运行状态 ...

  6. Java中转换为二进制的几种实现

    public class HexUtil { private static final String[] DIGITS_UPPER = {"0", "1", & ...

  7. 分布式任务调度平台XXL-JOB快速使用与问题总结

    1.XXL-JOB简介 XXL-JOB is a lightweight distributed task scheduling framework. It's core design goal is ...

  8. java如何消除太多的if else判断?

    1.简介 if判断语句是很多编程语言的重要组成部分.但是,若我们最终编写了大量嵌套的if语句,这将使得我们的代码更加复杂和难以维护. 让我们看看能否使用别的方式来做呢. 设计模式是为了更好的代码重用性 ...

  9. (转)WEB页面导出为Word文档后分页&横向打印的方法

    <html>    <HEAD>        <title>WEB页面导出为Word文档后分页&横向打印的方法 </title>    < ...

  10. 我是如何一步步编码完成万仓网ERP系统的(五)产品库设计 1.产品类别

    https://www.cnblogs.com/smh188/p/11533668.html(我是如何一步步编码完成万仓网ERP系统的(一)系统架构) https://www.cnblogs.com/ ...