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

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

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

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

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

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

    var obj = {};//全局建一个对象
obj.n = ;
var arr = [];//全局建一个数组
arr.n = ;
var fun = function(){};//全局建一个函数
fun.n = ;
var reg = /\d/;////全局建一个正则
reg.n = ; function bang(p){//形参p 改变形参p的n属性 回头看看会不会影响实参
p.n = ;
}
bang(obj);
console.log(obj)//{n:2} //obj.n 已经改变 1->2 影响了实参 全局变量obj
bang(arr);
console.log(arr.n)//2 arr也沦陷 arr.n值变为2
bang(fun);
console.log(fun.n)//2 fun.n也变了
bang(reg);
console.log(reg.n)//2 reg.n也变了

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

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

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

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

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

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

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

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

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

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

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

var arr = [{n:{nn:}},{n:{nn:}}]

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

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

var arr = [{n:{nn:}},{n:{nn:}}]

var newArr = arr.map(v=>v);//已经用map“复制”了个newArr,但它真的“干净”么?
newArr[].n.nn = ;//我修改了newArr,会不会改变arr呢 console.log(newArr)//[{n:{nn:99}},{n:{nn:3}}]
console.log(arr)//[{n:{nn:99}},{n:{nn:3}}] arr改变了,很明显map的复制没什么用
var arr = [{n:{nn:}},{n:{nn:}}]

//你会说这样直接复制太简单了,肯定会影响,我见过下面这样写的
var newArr = arr.map(v=>{
var vv = Object.assign({},v);
return vv;
});
newArr[].n.nn = ;//我修改了newArr,会不会改变arr呢 console.log(newArr)//[{n:{nn:99}},{n:{nn:3}}]
console.log(arr)//[{n:{nn:99}},{n:{nn:3}}] arr改变了

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

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

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

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

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

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

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

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

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

、Math.max 和 Math.min 求极值

//Math.max求极值简单方便  Math.min同理
Math.max.apply( null, [,,])//
Math.max.apply( null, [,,,null])//
Math.max.apply( null, [,,,''])//11 //但Math.max对参数类型兼容比较低,如下操作都会失败 Math.min同理
Math.max.apply( null, [,,,undefined]) //NaN
Math.max.apply( null, [,,,'string']) //NaN // 我们可以用map加一层容错处理
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. AtCoder Grand Contest 038 简要题解

    从这里开始 比赛目录 Problem A 01 Matrix Code #include <bits/stdc++.h> using namespace std; typedef bool ...

  2. Visual Studio 调试系列10 附加到正在运行的进程

    系列目录     [已更新最新开发文章,点击查看详细] 可将 Visual Studio 调试器附加到本地或远程计算机上正在运行的进程. 进程运行后,在 Visual Studio 中选择“调试” & ...

  3. 【07月15日】A股滚动市盈率PE最低排名

    ​仅根据最新的市盈率计算公式进行排名,无法对未来的业绩做出预测. 方大集团(SZ000055) - 滚动市盈率PE:2.53 - 滚动市净率PB:1.13 - 滚动年化股息收益率:4.01% - 建筑 ...

  4. 使用GitHub的仓库以及介绍

    一.创建仓库 假如Responsitory name是Hello-World Description一栏中可以设置仓库的说明. Public, Private  在这一栏可以选择Public还是Pri ...

  5. HTTP之Web服务器是如何进行工作的!

    Web服务器是如何进行工作的 ====================文章摘自<HTTP权威指南>====================== 1.  建立连接—接收一个客户端的连接,或者 ...

  6. SpringBoot整合mybatis及注意事项

    SpringBoot整合mybatis及注意事项 主要步骤 添加依赖 mybatis 在配置文件中配置数据源信息 编写pojo mapper接口 mapeer映射文件 手动配置mybatis的包扫描 ...

  7. [转帖]ZEROCONF是什么

    ZEROCONF是什么 xjsunjie关注0人评论9867人阅读2014-05-13 16:12:18 https://blog.51cto.com/xjsunjie/1410592 奇怪 两个文档 ...

  8. [sonarqube的使用] sonarlint在idea&eclipse中安装与使用

    介绍 ​ 代码质量管理的开源平台,用于管理源代码的质量 通过插件形式,可以支持包括java,C#,C/C++,PL/SQL,Cobol,JavaScrip,Groovy等等二十几种编程语言的代码质量管 ...

  9. 使用Jenkins来实现内部的持续集成流程(下)

    目录 配置项目构建 添加任务 添加源代码地址和登录凭据 添加构建触发器  TFS添加WebHook  添加构建步骤 后端UI  API端  配置项目构建 1.添加任务 2.添加源代码地址和登录凭据 添 ...

  10. 一、hexo+github搭建个人博客的过程记录

    前提: 1.新建一个github仓库 2.安装配置Node.js 3.安装配置Git 前提 步骤1.新建一个github仓库 打开github网站,(注册)登录账号,新建一个仓库; 注:==仓库名称要 ...