谓无规矩不成方圆,前端时间在团队 code-review 中发现,不同时期不同开发人员写的代码可谓五花八门。因此我们提出了一些相关代码方面的规范,希望日后能形成团队的编码规范。
本文主要针对一些 JavaScript 进行优化,使之更加健壮,可读性更强,更以维护。
gitthub地址:github.com/Michael-lzg…
上一篇:code-review之前端代码规范
JavaScript 条件语句在我们平时的开发中是不可避免要用到的,但是很多时候我们的代码写的并不好,一连串的 if-else 或者多重嵌套判断都会使得代码很臃肿,下面举例进行优化。
需求:现在有 4 个产品,分别是手机、电脑、电视机、游戏机,当然每个产品显示的价格不一样。
1、最简单的方法:if 判断
let commodity = {
phone: '手机',
computer: '电脑',
television: '电视',
gameBoy: '游戏机',
}
function price(name) {
if (name === commodity.phone) {
console.log(1999)
} else if (name === commodity.computer) {
console.log(9999)
} else if (name === commodity.television) {
console.log(2999)
} else if (name === commodity.gameBoy) {
console.log(3999)
}
}
price('手机') // 9999
缺点:代码太长了,维护和阅读都很不友好
2、好一点的方法:Switch
let commodity = {
phone: '手机',
computer: '电脑',
television: '电视',
gameBoy: '游戏机',
}
const price = (name) => {
switch (name) {
case commodity.phone:
console.log(1999)
break
case commodity.computer:
console.log(9999)
break
case commodity.television:
console.log(2999)
break
case commodity.gameBoy:
console.log(3999)
break
}
}
price('手机') // 9999
3、更优的方法: 策略模式
策略模式利用组合、委托和多态等技术和思想,可以有效地避免多重条件选择语句。它提供了对开放—封闭原则的完美支持,将算法封装在独立的 strategy 中,使得它们易于切换,易于理解,易于扩展。
const commodity = new Map([
['phone', 1999],
['computer', 9999],
['television', 2999],
['gameBoy', 3999],
])
const price = (name) => {
return commodity.get(name)
}
price('phone') // 1999
includes 是 ES7 新增的 API,与 indexOf 不同的是 includes 直接返回的是 Boolean 值,indexOf 则 返回的索引值, 数组和字符串都有 includes 方法。
需求:我们来实现一个身份认证方法,通过传入身份 Id 返回对应的验证结果
传统方法
function verifyIdentity(identityId) {
if (identityId == 1 || identityId == 2 || identityId == 3 || identityId == 4) {
return '你的身份合法,请通行!'
} else {
return '你的身份不合法'
}
}
includes 优化
function verifyIdentity(identityId) {
if ([1, 2, 3, 4].includes(identityId)) {
return '你的身份合法,请通行!'
} else {
return '你的身份不合法'
}
}
在 JavaScript 中,我们可以使用 for(), while(), for(in),for(in)几种循环,事实上,这三种循环中 for(in) 的效率极差,因为他需要查询散列键,所以应该尽量少用。
for 循环是最传统的语句,它以变量 i 作为索引,以跟踪访问的位置,对数组进行操作。
var arr = ['a', 'b', 'c']
for (var i = 0; i < arr.length; i++) {
console.log(arr[i]) //结果依次a,b,c
}
以上的方法有一个问题:就是当数组的长度到达百万级时,arr.length 就要计算一百万次,这是相当耗性能的。所以可以采用以下方法就行改良。
var arr = ['a', 'b', 'c']
for (var i = 0, length = arr.length; i < length; i++) {
console.log(arr[i]) //结果依次a,b,c
}
此时 arr.length 只需要计算一次,优化了性能。
for-in 一般用来来遍历对象的属性的,不过属性需要 enumerable(可枚举)才能被读取到。同时 for-in 也可以遍历数组,遍历数组的时候遍历的是数组的下标值。
var obj = { 0: 'a', 1: 'b', 2: 'c' }
for (var key in obj) {
console.log(key) //结果为依次为0,1,2
}
var arr = ['a', 'b', 'c']
for (var key in a) {
console.log(key) //结果为依次为0,1,2
}
for-of 语句看着有点像 for-in 语句,但是和 for-of 语句不同的是它不可以循环对象,只能循环数组。
var arr = ['a', 'b', 'c']
for (var value of arr) {
console.log(value) // 结果依次为a,b,c
}
for-of 比 for-in 循环遍历数组更好。for-of 只要具有 Iterator 接口的数据结构,都可以使用它迭代成员。它直接读取的是键值。for-in 需要穷举对象的所有属性,包括自定义的添加的属性也能遍历到。且 for-in 的 key 是 String 类型,有转换过程,开销比较大。
所以在开发过程中循环数组尽量避免使用 for-in。
数组去重是实际开发处理数据中经常遇到的,方法有很多,这里就不一一例举了。
1、最传统的方法:利用数组的 indexOf 下标属性来查询。
function unique4(arr) {
var newArr = []
for (var i = 0; i < arr.length; i++) {
if (newArr.indexOf(arr[i]) === -1) {
newArr.push(arr[i])
}
}
return newArr
}
console.log(unique4([1, 1, 2, 3, 5, 3, 1, 5, 6, 7, 4]))
// [1, 2, 3, 5, 6, 7, 4]
2、优化:利用 ES6 的 Set 方法。
Set 本身是一个构造函数,用来生成 Set 数据结构。Set 函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。Set 对象允许你存储任何类型的值,无论是原始值或者是对象引用。它类似于数组,但是成员的值都是唯一的,没有重复的值。
function unique4(arr) {
return Array.from(new Set(arr)) // 利用Array.from将Set结构转换成数组
}
console.log(unique4([1, 1, 2, 3, 5, 3, 1, 5, 6, 7, 4]))
// [1, 2, 3, 5, 6, 7, 4]
箭头函数表达式的语法比函数表达式更简洁。所以在开发中更推荐使用箭头函数。特别是在 vue 项目中,使用箭头函数不需要在更 this 重新赋一个变量。
// 使用functions
var arr = [5, 3, 2, 9, 1]
var arrFunc = arr.map(function (x) {
return x * x
})
console.log(arrFunc)
// 使用箭头函数
var arr = [5, 3, 2, 9, 1]
var arrFunc = arr.map((x) => x * x)
要注意的是,箭头函数不绑定 arguments,取而代之用 rest 参数…解决。
// 不能使用 arguments
let fun1 = (b) => {
console.log(arguments)
}
fun1(2, 92, 32, 32) // Uncaught ReferenceError: arguments is not defined
// 使用rest 参数
let fun2 = (...c) => {
console.log(c)
}
fun2(3, 82, 32, 11323) // [3, 82, 32, 11323]
创建多个 dom 元素时,先将元素 append 到 DocumentFragment 中,最后统一将 DocumentFragment 添加到页面。
常规方法;
for (var i = 0; i < 1000; i++) {
var el = document.createElement('p')
el.innerHTML = i
document.body.appendChild(el)
}
使用 DocumentFragment 优化多次 append
var frag = document.createDocumentFragment()
for (var i = 0; i < 1000; i++) {
var el = document.createElement('p')
el.innerHTML = i
frag.appendChild(el)
}
document.body.appendChild(frag)
更优的方法:使用一次 innerHTML 赋值代替构建 dom 元素
var html = []
for (var i = 0; i < 1000; i++) {
html.push('<p>' + i + '</p>')
}
document.body.innerHTML = html.join('')
系统进程不再用到的内存,没有及时释放,就叫做内存泄漏(memory leak)。当内存占用越来越高,轻则影响系统性能,重则导致进程崩溃。
引起内存泄漏的原因
1、未声明变量或者使用 this 创建的变量(this 的指向是 window)都会引起内存泄漏
function fn() {
a = "Actually, I'm a global variable"
}
fn()
function fn() {
this.a = "Actually, I'm a global variable"
}
fn()
解决方法:
2、在 vue 单页面应用,声明的全局变量在切换页面的时候没有清空
<template>
<div id="home">
这里是首页
</div>
</template>
<script>
export default {
mounted() {
window.test = {
// 此处在全局window对象中引用了本页面的dom对象
name: 'home',
node: document.getElementById('home')
}
}
}
</script>
解决方案: 在页面卸载的时候顺便处理掉该引用。
destroyed () {
window.test = null // 页面卸载的时候解除引用
}
闭包引起的内存泄漏原因:闭包可以维持函数内局部变量,使其得不到释放。
function fn() {
var a = "I'm a"
return function () {
console.log(a)
}
}
解决:将事件处理函数定义在外部,解除闭包,或者在定义事件处理函数的外部函数中,删除对 dom 的引用。
由于项目中有些页面难免会碰到需要定时器或者事件监听。但是在离开当前页面的时候,定时器如果不及时合理地清除,会造成业务逻辑混乱甚至应用卡死的情况,这个时就需要清除定时器事件监听,即在页面卸载(关闭)的生命周期函数里,清除定时器。
methods:{
resizeFun () {
this.tableHeight = window.innerHeight - document.getElementById('table').offsetTop - 128
},
setTimer() {
this.timer = setInterval(() => { })
},
clearTimer() {//清除定时器
clearInterval(this.timer)
this.timer = null
}
},
mounted() {
this.setTimer()
window.addEventListener('resize', this.resizeFun)
},
beforeDestroy() {
window.removeEventListener('resize', this.resizeFun)
this.clearTimer()
}
在前端开发的过程中,我们经常会需要绑定一些持续触发的事件,如 resize、scroll、mousemove 等等,但有些时候我们并不希望在事件持续触发的过程中那么频繁地去执行函数。这时候就用到防抖与节流。
案例 1:远程搜索时需要通过接口动态的获取数据,若是每次用户输入都接口请求,是浪费带宽和性能的。
<Select :remote-method="remoteMethod">
<Option v-for="item in temoteList" :value="item.value" :key="item.id">{{item.label}}</Option>
</Select>
<script>
function debounce(fn, wait) {
let timeout = null
return function () {
if (timeout !== null) clearTimeout(timeout)
timeout = setTimeout(fn, wait)
}
}
export default {
methods:{
remoteMethod:debounce(function (query) {
// to do ...
}, 200),
}
}
<script>
案例 2:持续触发 scroll 事件时,并不立即执行 handle 函数,当 1000 毫秒内没有触发 scroll 事件时,才会延时触发一次 handle 函数。
function debounce(fn, wait) {
let timeout = null
return function () {
if (timeout !== null) clearTimeout(timeout)
timeout = setTimeout(fn, wait)
}
}
function handle() {
console.log(Math.random())
}
window.addEventListener('scroll', debounce(handle, 1000))
默认情况下,浏览器是同步加载 js 脚本,解析 html 过程中,遇到 <script> 标签就会停下来,等脚本下载、解析、执行完后,再继续向下解析渲染。
如果 js 文件体积比较大,下载时间就会很长,容易造成浏览器堵塞,浏览器页面会呈现出“白屏”效果,用户会感觉浏览器“卡死了”,没有响应。此时,我们可以让 js 脚本异步加载、执行。
<script src="path/to/home.js" defer></script>
<script src="path/to/home.js" async></script>
上面代码中,<script> 标签分别有 defer 和 async 属性,浏览器识别到这 2 个属性时 js 就会异步加载。也就是说,浏览器不会等待这个脚本下载、执行完毕后再向后执行,而是直接继续向后执行
defer 与 async 区别:
作者:lzg9527 链接:https://segmentfault.com/a/1190000023254297 来源:SegmentFault 思否 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。Java学习、面试;文档、视频资源免费获取作者:lzg9527 链接:https://segmentfault.com/a/1190000023254297 来源:SegmentFault 思否 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
avaScript 语句
JavaScript 语句向浏览器发出的命令。语句的作用是告诉浏览器该做什么。
下面的 JavaScript 语句向 id="demo"的 HTML 元素输出文本 "Hello World":
分号 ;
分号用于分隔 JavaScript 语句。
通常我们在每条可执行的语句结尾添加分号。
使用分号的另一用处是在一行中编写多条语句。
提示:您也可能看到不带有分号的案例。
在 JavaScript 中,用分号来结束语句是可选的。
JavaScript 代码
JavaScript 代码(或者只有 JavaScript)是 JavaScript 语句的序列。
浏览器会按照编写顺序来执行每条语句。
本例将操作两个 HTML 元素:
document.getElementById("demo").innerHTML="Hello World"; document.getElementById("myDIV").innerHTML="How are you?";
JavaScript 代码块
JavaScript 语句通过代码块的形式进行组合。
块由左花括号开始,由右花括号结束。
块的作用是使语句序列一起执行。
JavaScript 函数是将语句组合在块中的典型例子。
下面的例子将运行可操作两个 HTML 元素的函数:
function myFunction() { document.getElementById("demo").innerHTML="Hello World"; document.getElementById("myDIV").innerHTML="How are you?"; }
JavaScript 对大小写敏感。
JavaScript 对大小写是敏感的。
当编写 JavaScript 语句时,请留意是否关闭大小写切换键。
函数 getElementById 与 getElementbyID 是不同的。
同样,变量 myVariable 与 MyVariable 也是不同的。
空格
注:JavaScript 会忽略多余的空格。您可以向脚本添加空格,来提高其可读性。下面的两行代码是等效的:
var name="Hello"; var name = "Hello";
对代码行进行折行
您可以在文本字符串中使用反斜杠对代码行进行换行。下面的例子会正确地显示:
document.write("Hello \ World!"); //正确的的折行 document.write \ ("Hello World!"); //错误的折行
提示:JavaScript 是脚本语言。浏览器会在读取代码时,逐行地执行脚本代码。而对于传统编程来说,会在执行前对所有代码进行编译。
变量
变量是存储信息的容器
变量可以使用短名称(比如 x 和 y),也可以使用描述性更好的名称(比如 age, sum, totalvolume)。
提示:JavaScript 语句和 JavaScript 变量都对大小写敏感。
一个好的编程习惯是,在代码开始处,统一对需要的变量进行声明。
您可以在一条语句中声明很多变量。该语句以 var 开头,并使用逗号分隔变量即可:
var name="Gates", age=56, job="CEO"; //也可以多行 var name="Gates", age=56, job="CEO";
var carname;
var carname="Volvo"; var carname;
数据类型
字符串、数字、布尔、数组、对象、Null、Undefined
当您向变量分配文本值时,应该用双引号或单引号包围这个值。
当您向变量赋的值是数值时,不要使用引号。如果您用引号包围数值,该值会被作为文本来处理。
1.字符串
字符串是存储字符(比如 "Bill Gates")的变量。
字符串可以是引号中的任意文本。您可以使用单引号或双引号:
var answer="Nice to meet you!";
var answer="He is called 'Bill'";
var answer='He is called "Bill"'; //内有引号的字符串
2.数字 数字可以带小数点,也可以不带;极大或极小的数字可以通过科学(指数)计数法来书写:var z=123e-5;
3.布尔 布尔(逻辑)只能有两个值:true 或 false。布尔常用在条件测试中
4.数组
var cars=new Array(); cars[0]="Audi"; cars[1]="BMW"; cars[2]="Volvo"; var cars=new Array("Audi","BMW","Volvo");//(condensed array) var cars=["Audi","BMW","Volvo"];//(literal array)
5.对象
对象由花括号分隔。在括号内部,对象的属性以名称和值对的形式 (name : value) 来定义。属性由逗号分隔:
//person 对象有三个属性 var person={firstname:"Bill", lastname:"Gates", id:5566};
6.Null
7.Undefined
JavaScript 拥有动态类型。这意味着相同的变量可用作不同的类型:
JavaScript共有八种数据类型,分别是 Undefined、Null、Boolean、Number、String、Object、Symbol、BigInt。
其中 Symbol 和 BigInt 是ES6 中新增的数据类型:
这些数据可以分为原始数据类型和引用数据类型(复杂数据类型),他们在内存中的存储方式不同。
Undefined 和 Null 都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null。
null == undefined // true
null === undefined //false
instanceof运算符适用于检测构造函数的prototype属性上是否出现在某个实例对象的原型链上
instanceof 运算符的原理是基于原型链的查找。当使用 obj instanceof Constructor 进行判断时,JavaScript 引擎会从 obj 的原型链上查找 Constructor.prototype 是否存在,如果存在则返回 true,否则继续在原型链上查找。如果查找到原型链的顶端仍然没有找到,则返回 false。
instanceof运算符只能用于检查某个对象是否是某个构造函数的实例,不能用于基本类型的检查,如string、number等
typeof与instanceof 都是判断数据类型的方法,区别如下:
这是 JavaScript 语言的一个历史遗留问题,在第一版JS代码中用32位比特来存储值,通过值的1-3位来识别类型,前三位为000表示对象类型。而null是一个空值,二进制表示都为0,所以前三位也就是000,所以导致 typeof null 返回 "object"
因为浮点数运算的精度问题。在计算机运行过程中,需要将数据转化成二进制,然后再进行计算。 因为浮点数自身小数位数的限制而截断的二进制在转化为十进制,就变成0.30000000000000004,所以在计算时会产生误差。
解决方案
let x=(0.1*10+0.2*10)/10;
console.log(x===0.3)
(n1 + n2).toFixed(2)
Object.prototype.toString.call(obj).slice(8,-1) === 'Array';
obj.__proto__ === Array.prototype;
Array.isArrray(obj);
obj instanceof Array
类数组也叫伪数组,类数组和数组类似,但不能调用数组方法,常见的类数组有arguments、通过document.getElements获取到的内容等,这些类数组具有length属性。
转换方法
Array.prototype.slice.call(arrayLike)
Array.prototype.splice.call(arrayLike, 0)
Array.prototype.concat.apply([], arrayLike)
Array.from(arrayLike)
Array.propotype.slice.call()是什么 比如Array.prototype.slice.call(arguments)这句里,就是把 arguments 当做当前对象。
也就是说 要调用的是 arguments 的 slice 方法,而typeof arguments="Object" 而不是 Array
它没有slice这个方法,通过这么Array.prototype.slice.call调用,JS的内部机制应该是 把arguments对象转化为Array
它们都是字符串方法,用于截取字符串的一部分,主要区别在于参数不同
const str = "Hello, World!";
console.log(str.substring(0, 5)); // 输出: "Hello"
console.log(str.substr(7, 5)); // 输出: "World"
都是浅拷贝
new操作符用来创建一个对象,并将该对象绑定到构造函数的this上。
new操作符的执行过程:
「手写代码-实现一个new操作符」
for...in和for...of都是JavaScript中的循环语句,而for…of 是ES6新增的遍历方式,允许遍历一个含有iterator接口的数据结构(数组、对象等)并且返回各项的值,和ES3中的for…in的区别如下
总结:for...in 循环主要是为了遍历对象而生,不适用于遍历数组;for...of 循环可以用来遍历数组、类数组对象,字符串、Set、Map 以及 Generator 对象。
为什么不能遍历对象
for…of是作为ES6新增的遍历方式,能被其遍历的数据内部都有一个遍历器iterator接口,而数组、字符串、Map、Set内部已经实现,普通对象内部没有,所以在遍历的时候会报错。想要遍历对象,可以给对象添加一个Symbol.iterator属性,并指向一个迭代器即可
在迭代器里面,通过Object.keys获取对象所有的key,然后遍历返回key 、value。
var obj = {
a:1,
b:2,
c:3
};
obj[Symbol.iterator] = function*(){
var keys = Object.keys(obj);
for(var k of keys){
yield [k,obj[k]]
}
};
for(var [k,v] of obj){
console.log(k,v);
}
AJAX是 Asynchronous JavaScript and XML 的缩写,指的是通过 JavaScript 的 异步通信,从服务器获取 XML 文档从中提取数据,再更新当前网页的对应部分,而不用刷新整个网页。 创建AJAX请求的步骤:
const SERVER_URL = "/server";
let xhr = new XMLHttpRequest();
// 创建 Http 请求
xhr.open("GET", url, true);
// 设置状态监听函数
xhr.onreadystatechange = function() {
if (this.readyState !== 4) return;
// 当请求成功时
if (this.status === 200) {
handle(this.response);
} else {
console.error(this.statusText);
}
};
// 设置请求失败时的监听函数
xhr.onerror = function() {
console.error(this.statusText);
};
// 设置请求头信息
xhr.responseType = "json";
xhr.setRequestHeader("Accept", "application/json");
// 发送 Http 请求
xhr.send(null);
ajax
axios
fetch
两个方法都是用来遍历循环数组,区别如下:
尾调用就是在函数的最后一步调用函数,在一个函数里调用另外一个函数会保留当前执行的上下文,如果在函数尾部调用,因为已经是函数最后一步,所以这时可以不用保留当前的执行上下文,从而节省内存。但是ES6的尾调用只能在严格模式下开启,正常模式是无效的。
「手写代码-手写深拷贝」
「手写代码-手写浅拷贝」
Set
Map
map和Object都是用键值对来存储数据,区别如下:
它们是 JavaScript 中的两种不同的键值对集合,主要区别如下:
Promise是异步编程的一种解决方案,将异步操作以同步操作的流程表达出来,避免了地狱回调。
Promise的实例有三个状态:
Promise的实例有两个过程:
Promise构造函数接收一个带有resolve和reject参数的回调函数。
Promise的缺点:
Promise.all() 和 Promise.allSettled() 都是用来处理多个 Promise 实例的方法,它们的区别在于以下几点:
async/await其实是Generator 的语法糖,它能实现的效果都能用then链来实现,它是为优化then链而开发出来的。通过async关键字声明一个异步函数, await 用于等待一个异步方法执行完成,并且会阻塞执行。 async 函数返回的是一个 Promise 对象,如果在函数中 return 一个变量,async 会把这个直接量通过 Promise.resolve() 封装成 Promise 对象。如果没有返回值,返回 Promise.resolve(undefined)
// ES6 模块
import { foo } from './module';
export const bar = 'bar';
// CommonJS 模块
const foo = require('./commonjs');
exports.bar = 'bar';
每个实例对象都有一个__proto__属性指向它的构造函数的原型对象,而这个原型对象也会有自己的原型对象,一层一层向上,直到顶级原型对象null,这样就形成了一个原型链。
当访问对象的一个属性或方法时,当对象身上不存在该属性方法时,就会沿着原型链向上查找,直到查找到该属性方法位置。
原型链的顶层原型是Object.prototype,如果这里没有就只指向null
利用Object.create()方法,将子类的原型指向父类,实现继承父类的方法属性,修改时也不影响父类。
function Parent(name) {
this.name = name;
this.colors = ['red', 'green', 'blue'];
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
function Child(name, age) {
// 执行父类构造函数
Parent.call(this, name);
this.age = age;
}
// 将子类的原型 指向父类
Child.prototype = Object.create(Parent.prototype);
// 此时的狗早函数为父类的 需要指回自己
Child.prototype.constructor = Child;
Child.prototype.sayAge = function() {
console.log(this.age);
};
var child1 = new Child('Tom', 18);
child1.sayName(); // 'Tom'
child1.sayAge(); // 18
闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,创建的函数可以访问到当前函数的局部变量。
闭包优点:
闭包缺点:
使用场景:
闭包并不一定会造成内存泄漏,如果在使用闭包后变量没有及时销毁,可能会造成内存泄漏的风险。只要合理的使用闭包,就不会造成内存泄漏。
作用域是一个变量或函数的可访问范围,作用域控制着变量或函数的可见性和生命周期。
作用域链: 变量在指定的作用域中没有找到,会依次向一层作用域进行查找,直到全局作用域。这个查找的过程被称为作用域链。
在 JavaScript 中,连续多次调用 bind 方法,最终函数的 this 上下文是由第一次调用 bind 方法的参数决定的
const obj1 = { name: 'obj1' };
const obj2 = { name: 'obj2' };
const obj3 = { name: 'obj3' };
function getName() {
console.log(this.name);
}
const fn1 = getName.bind(obj1).bind(obj2).bind(obj3);
fn1(); // 输出 "obj1"
垃圾回收:JavaScript代码运行时,需要分配内存空间来储存变量和值。当变量不再参与运行时,就需要系统收回被占用的内存空间。如果不及时清理,会造成系统卡顿、内存溢出,这就是垃圾回收。
在 V8 中,会把堆分为新生代和老生代两个区域,新生代中存放的是生存时间短的对象,老生代中存放生存时间久的对象:
副垃圾回收器主要负责新⽣代的垃圾回收。大多数的对象最开始都会被分配在新生代,该存储空间相对较小,分为两个空间:from 空间(对象区)和 to 空间(空闲区)。
主垃圾回收器主要负责⽼⽣代中的垃圾回收。存储一些占用空间大、存活时间长的数据,采用标记清除算法进行垃圾回收。
主要分为标记、清除两个阶段。
对⼀块内存多次执⾏标记清除算法后,会产⽣⼤量不连续的内存碎⽚。⽽碎⽚过多会导致⼤对象⽆法分配到⾜够的连续内存,于是⼜引⼊了另外⼀种算法——标记整理。
标记整理的标记过程仍然与标记清除算法⾥的是⼀样的,先标记可回收对象,但后续步骤不是直接对可回收对象进⾏清理,⽽是让所有存活的对象都向⼀端移动,然后直接清理掉这⼀端之外的内存。
一个对象被引用一次,引用数就+1,反之就-1。当引用为0,就会出发垃圾回收。
这种方式会产生一个问题,在循环引用时,引用数永远不会为0,无法回收。
作者:wakaka378
链接:https://juejin.cn/post/7270471613547249699
*请认真填写需求信息,我们会在24小时内与您取得联系。