简单聊一聊JS中的循环引用及问题
本文主要从 JS 中为什么会出现循环引用,垃圾回收策略中引用计数为什么有很大的问题,以及循环引用时的对象在使用 JSON.stringify 时为什么会报错,怎样解决这个问题简单谈谈自己的一些理解。
1. 什么是循环引用
当对象 1 中的某个属性指向对象 2,对象 2 中的某个属性指向对象 1 就会出现循环引用,(当然不止这一种情况,不过原理是一样的)下面通过代码和内存示意图来说明一下。
function circularReference() {
let obj1 = {
};
let obj2 = {
b: obj1
};
obj1.a = obj2;
}
上述代码在内存中的示意图
从上图可以看出 obj1 中的 a 属性引用 obj2,obj2 中的 b 属性引用 obj1,这样就构成了循环引用。
2.JS 中引用计数垃圾回收策略的问题
先简单讲一下 JS 中引用垃圾回收策略大体是什么样的一个原理,当一个变量被赋予一个引用类型的值时,这个引用类型的值的引用计数加 1。就像是代码中的 obj1 这个变量被赋予了 obj1 这个对象的地址,obj1 这个变量就指向了这个 obj1(右上)这个对象,obj1(右上)的引用计数就会加1.当变量 obj1的值不再是 obj1(右上)这个对象的地址时,obj1(右上)这个对象的引用计数就会减1.当这个 obj1(右上)对象的引用计数变成 0 后,垃圾收集器就会将其回收,因为此时没有变量指向你,也就没办法使用你了。
看似很合理的垃圾回收策略为什么会有问题呢?
就是上面讲到的循环引用导致的,下面来分析一下。当 obj1 这个变量执行 obj1 这个对象时,obj1 这个对象的引用计数会加 1,此时引用计数值为 1,接下来 obj2 的 b 属性又指向了 obj1 这个对象,所以此时 obj1 这个对象的引用计数为 2。同理 obj2 这个对象的引用计数也为2.
当代码执行完后,会将变量 obj1 和 obj2 赋值为 null,但是此时 obj1 和 obj2 这两个对象的引用计数都为1,并不为 0,所以并不会进行垃圾回收,但是这两个对象已经没有作用了,在函数外部也不可能使用到它们,所以这就造成了内存泄露。
在现在广泛采用的标记清除回收策略中就不会出现上面的问题,标记清除回收策略的大致流程是这样的,最开始的时候将所有的变量加上标记,当执行 cycularReference 函数的时候会将函数内部的变量这些标记清除,在函数执行完后再加上标记。这些被清除标记又被加上标记的变量就被视为将要删除的变量,原因是这些函数中的变量已经无法被访问到了。像上述代码中的 obj1 和 obj2 这两个变量在刚开始时有标记,进入函数后被清除标记,然后函数执行完后又被加上标记被视为将要清除的变量,因此不会出现引用计数中出现的问题,因为标记清除并不会关心引用的次数是多少。
3. 循环引用的对象使用 JSON.stringify 为什么会报错
JSON.stringify 用于将一个 JS 对象序列化为一个 JSON 字符串,假设现在我们要将 obj1 这个对象序列化为 JSON 字符串,现在我们先将 obj1 这个对象打印出来看一下。
function circularReference() {
let obj1 = {
};
let obj2 = {
b: obj1
};
obj1.a = obj2;
console.log(obj1);
}
circularReference();
结果如下所示:
obj1 这个对象和 obj2 会无限相互引用,JSON.tostringify 无法将一个无限引用的对象序列化为 JOSN 字符串。
下面是 MDN 的解释:
JSON.stringify() 将值转换为相应的JSON格式:
- 转换值如果有 toJSON() 方法,该方法定义什么值将被序列化。
- 非数组对象的属性不能保证以特定的顺序出现在序列化后的字符串中。
- 布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值。
undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成null(出现在数组中时)。函数、undefined 被单独转换时,会返回 undefined,如JSON.stringify(function(){})orJSON.stringify(undefined).- 对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误。
- 所有以 symbol 为属性键的属性都会被完全忽略掉,即便
replacer参数中强制指定包含了它们。- Date 日期调用了 toJSON() 将其转换为了 string 字符串(同Date.toISOString()),因此会被当做字符串处理。
- NaN 和 Infinity 格式的数值及 null 都会被当做 null。
- 其他类型的对象,包括 Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性。
我们可以从加粗的字体中看到,对包含循环引用的对象执行 JSON.stringify,会抛出错误。
解决方法
一个自然的想法能不能消除循环引用,一个 JSON 扩展包 做到了这一点, 使用 JSON.decycle 可以去除循环引用。为了方便测试我直接在 JSON 扩展包的 Github 仓库中下载了 cycle.js 这个函数,将下面这段代码赋值到最下面,然后利用 node 运行进行测试,问题得到解决,结果如下图所示。
function circularReference() {
let obj1 = {
};
let obj2 = {
b: obj1
};
obj1.a = obj2;
let c = JSON.decycle(obj1);
console.log(JSON.stringify(c));
}
circularReference();
运行结果
完,如有不恰当之处,欢迎指正哦。
简单聊一聊JS中的循环引用及问题的更多相关文章
- js中的循环语句
js中的循环语句可分为三种:1.while:2.do……while:3.for. while的语法为 while (exp) { //statements;} var a=1,b=0; whil ...
- js中的循环
js中的循环是我们经常要用到的,现在进行一些归纳. 一.javascript种的循环. 1.循环对象 var o = { name: 'Jack', age: 20, city: 'Beijing' ...
- js中for循环的研究
转自:http://blog.csdn.net/lushuaiyin/article/details/8541500 <html> <body> <b><ce ...
- java和js中for循环的区别
java中for循环,先执行语句后循环 for (int i=1;i<10;i++){ for(int b=1;b<=i;b++){ System.out.print(b+"*& ...
- JS中的循环嵌套 BOM函数
[嵌套循环特点] 外层循环转一次,内层循环转一圈 外层循环控制行数,内层循环控制每行元素个数 [做 ...
- 如何在 iOS 中解决循环引用的问题
稍有常识的人都知道在 iOS 开发时,我们经常会遇到循环引用的问题,比如两个强指针相互引用,但是这种简单的情况作为稍有经验的开发者都会轻松地查找出来. 但是遇到下面这样的情况,如果只看其实现代码,也很 ...
- JS中for循环变量作用域--解决for循环异步执行的问题
被这个问题困惑了很久,终于在网上找到了答案,感谢~ 现在分享给大家~ js中如何让一个for循环走完之后,再去执行下面的语句? 这涉及for循环变量作用域的问题,js中作用域只有函数作用域和全局作用域 ...
- 【FastJSON】解决FastJson中“$ref 循环引用”的问题
0.开发环境 SSH,EasyUI,MySQL 1.需求要求: (1)首先获取所有的贷款订单数据,即List <LoanOrder>. (2)然后从单个贷款订单实体LoanOrder去访问 ...
- swift闭包中解决循环引用的问题
swift中可以通过三种方法解决循环引用的问题 利用类似oc方法解决循环引用weak var weakSelf = self weak var weakSelf = self loadData = { ...
随机推荐
- Android ViewGroup点击效果(背景色)
在开发Android应用的界面时,我们必然会用到本文ViewGroup,尤其是FrameLayout,LinearLayout,RelativeLayout等ViewGroup的子类: 在一些情况下, ...
- Unity 鼠标控制视角功能和动画播放冲突解决办法
环境是 unity 5.6.4 情况说明: 1 模型动画单独播放是没问题的. 2 鼠标控制模型是没问题的. 3 在start中播放模型动画,即使鼠标控制视角代码还挂载着,但是模型却无法用鼠标旋转等操作 ...
- Python--day20--序列化模块
序列化:转向一个字符串数据类型 序列 ———— 字符串 序列化和反序列化的概念: 序列化三种方法:json pickle shelve json模块:json模块提供了四个方法dumps和load ...
- 三分钟学会@Autowired@Qualifier@Primary注解
三分钟学会@Autowired@Qualifier@Primary注解 2018.10.08 20:24 154浏览 今天主要简单的跟大家介绍一下spring自动装配相关的@Autowired,@Qu ...
- 2016.1.22 扩充临时表空间解决ora-01652错误
今天运行一个复杂查询时报错ora-01652 无法通过128 扩展temp段, 网上说是临时表空间大小不够,运行了脚本调整临时表空间,问题解决 alter database tempfile '/ap ...
- 用一维数组实现栈(C++编程思想 p120)
1 实现思路 向栈中插入4个元素后的状态 执行过程分析: 2 代码实现 clib.h 接口定义 typedef struct CStashTag { int ele_size; //栈中每个元素的占用 ...
- [转]ECMAScript 2016,2017 和 2018 中所有新功能的示例
很难追踪 JavaScript(ECMAScript)中的新功能. 想找到有用的代码示例更加困难. 因此,在本文中,我将介绍 TC39 已完成 ES2016,ES2017 和 ES2018(最终草案) ...
- Mysql怎样控制replace替换的次数?
我想把“ABC是ABC”替换成“123是ABC”,也就是找出第一个ABC替换成123,MYSQL命令应该怎么写? UPDATE data SET body=REPLACE(body, 'ABC', ' ...
- HDU 1542"Atlantis"(线段树+扫描线求矩形面积并)
传送门 •题意 给你 n 矩形,每个矩形给出你 $(x_1,y_1),(x_2,y_2)$ 分别表示这个矩形的左下角和右上角坐标: 让你求这 n 个矩形并的面积: 其中 $x \leq 10^{5} ...
- WPF 解决弹出模态窗口关闭后,主窗口不在最前
本文告诉大家如何解决这个问题,在 WPF 的软件,弹出一个模态窗口.使用另一个窗口在模态窗口前面.从任务栏打开模态窗口.关闭模态窗口.这时发现,主窗口会在刚才使用的另一个窗口下面 这是 Windows ...