安徽网站建,阿里云支持wordpress,网站建设经典教材,wordpress 付费支持文章目录 函数提升上下文函数释放拓展-垃圾回收机制垃圾回收之触发应用 函数提升上下文 函数提升#xff08;Hoisting#xff09; 概念#xff1a;在JavaScript中#xff0c;函数声明会被提升到当前作用域的顶部。这意味着可以在函数声明之前调用函数。例如#xff1a; sa… 文章目录 函数提升上下文函数释放拓展-垃圾回收机制垃圾回收之触发应用 函数提升上下文 函数提升Hoisting 概念在JavaScript中函数声明会被提升到当前作用域的顶部。这意味着可以在函数声明之前调用函数。例如 sayHello();
function sayHello() {console.log(Hello!);
}原理当JavaScript引擎在执行代码时会首先扫描整个作用域例如全局作用域或者函数作用域找到所有的函数声明并将它们“提升”到作用域的顶部。但需要注意的是函数表达式不会提升。例如 // 这会报错因为函数表达式不会提升
sayHi();
var sayHi function() {console.log(Hi!);
};在上面的代码中var sayHi这个变量声明会被提升但是赋值也就是函数表达式部分不会提升。所以在调用sayHi的时候它的值是undefined调用就会出错。 函数执行上下文Execution Context 概念执行上下文是JavaScript中一个非常重要的概念它定义了函数执行时的环境。当一个函数被调用时就会创建一个新的执行上下文。这个执行上下文包括变量对象VO、作用域链和this的值。创建阶段 变量对象VO在执行上下文的创建阶段会创建变量对象。对于函数执行上下文参数会作为变量对象的属性函数内部声明的变量也会添加到变量对象中。例如 function add(num1, num2) {var result;result num1 num2;return result;
}
add(3, 5);在add函数的执行上下文中变量对象在创建阶段会有num1、num2和result这几个属性其中num1和num2会被初始化为传入的参数值3和5result会被初始化为undefined。 作用域链Scope Chain作用域链是由当前执行上下文的变量对象和它外层执行上下文的变量对象组成的一个链表。它用于查找变量的值。例如在一个嵌套函数中 var globalVar 10;
function outer() {var outerVar 20;function inner() {var innerVar 30;console.log(globalVar outerVar innerVar);}inner();
}
outer();在inner函数的执行上下文中作用域链首先会查找自己的变量对象中的innerVar然后查找outer函数执行上下文的变量对象中的outerVar最后查找全局变量对象中的globalVar。 this值this的值取决于函数的调用方式。在全局环境中this指向window在浏览器环境下。在对象方法中this指向调用该方法的对象。例如 var obj {name: John,sayName: function() {console.log(this.name);}
};
obj.sayName(); // 输出 John执行阶段在执行阶段JavaScript引擎会逐行执行函数中的代码对变量进行赋值操作执行函数调用等。 函数内部使用的对象 函数参数作为对象函数可以接收对象作为参数然后在函数内部对这个对象进行操作。例如 function updateObject(obj) {obj.property new value;return obj;
}
var myObject {property: original value
};
var updatedObject updateObject(myObject);
console.log(updatedObject.property); // 输出 new value在函数内部创建对象可以在函数内部使用new关键字创建对象如果是构造函数的话或者使用对象字面量来创建对象。例如 function createObject() {var newObject {name: New Object,description: This is a newly created object.};return newObject;
}
var createdObject createObject();
console.log(createdObject.name); // 输出 New Object监听事件清除释放 在DOM中清除事件监听器在浏览器环境下当给DOM元素添加事件监听器后需要在适当的时候清除它以避免内存泄漏等问题。如果使用addEventListener添加事件监听器可以使用removeEventListener来清除。例如 var button document.getElementById(myButton);
function handleClick() {console.log(Button clicked!);
}
button.addEventListener(click, handleClick);
// 之后想要清除事件监听器
button.removeEventListener(click, handleClick);注意事项在使用removeEventListener时传递的函数必须是和添加监听器时完全相同的函数引用。如果是使用匿名函数添加的监听器想要清除就会比较复杂。例如下面这种情况就很难正确地清除监听器 button.addEventListener(click, function() {console.log(Anonymous function click!);
});一种解决方法是将匿名函数赋值给一个变量然后在removeEventListener中使用这个变量 var anonymousClickHandler function() {console.log(Anonymous function click!);
};
button.addEventListener(click, anonymousClickHandler);
// 清除监听器
button.removeEventListener(click, anonymousClickHandler);函数释放 JavaScript的内存管理基础 在JavaScript中内存管理主要是由垃圾回收器Garbage CollectorGC来自动完成的。垃圾回收器会定期扫描内存找出那些不再被使用的对象并释放它们所占用的内存空间。当一个对象没有任何引用指向它时就会被垃圾回收器认为是“垃圾”从而被回收。例如在下面的代码中 function createObject() {let myObject {name: Example};return myObject;
}
let newObject createObject();
// 此时myObject对象仍然被newObject引用不会被回收
newObject null;
// 现在没有引用指向myObject对象了它会在下次垃圾回收时被回收函数内部局部变量的释放 手动设置为null 在函数内部如果有一些比较占用内存的对象如大型数组、复杂的对象等可以在函数执行结束前手动将它们设置为null。例如 function processData() {let largeArray new Array(1000000).fill(0);// 对大型数组进行一些操作...// 操作完成后手动将其设置为nulllargeArray null;
}当把largeArray设置为null后就切断了对这个大型数组对象的引用。这样在下一次垃圾回收时这个数组对象占用的内存就有更大的机会被回收。不过需要注意的是设置为null并不意味着立即释放内存只是告诉垃圾回收器这个对象可以被回收了。 让变量超出作用域 函数内部的局部变量在函数执行结束后会自动超出作用域。例如 function limitedScope() {let localVariable This is a local variable;console.log(localVariable);
}
limitedScope();
// 在这里localVariable已经超出了作用域它所占用的内存会由垃圾回收器来管理当函数limitedScope执行完毕后localVariable就不再存在于当前的执行上下文中它所占用的内存会在适当的时候被垃圾回收器回收。但是如果这个变量所引用的对象还被其他地方如全局变量或者闭包引用那么它不会被回收。 闭包中的内存释放 理解闭包对内存的影响 闭包是指有权访问另一个函数内部变量的函数。当一个函数返回一个闭包时这个闭包会保留对其外部函数的变量的引用即使外部函数已经执行完毕。例如 function outerFunction() {let outerVariable Im from outer function;return function innerFunction() {console.log(outerVariable);};
}
let closureFunction outerFunction();
closureFunction();
// 此时即使outerFunction已经执行完毕
// outerVariable仍然被closureFunction引用不会被回收释放闭包中的内存 要释放闭包中引用的内存可以通过将闭包函数设置为null来切断引用。例如 closureFunction null;
// 现在没有引用指向outerVariable了它会在下次垃圾回收时被回收另外如果闭包中的变量是一个比较复杂的对象也可以在闭包内部手动将其设置为null来帮助垃圾回收。 处理事件监听器和定时器对内存的影响 事件监听器 在函数内部添加的事件监听器如果没有正确移除会导致内存泄漏。例如在一个函数中给DOM元素添加了一个点击事件监听器 function addEventListenerFunction() {let button document.getElementById(myButton);button.addEventListener(click, function() {console.log(Button clicked);});
}
addEventListenerFunction();每次调用这个函数都会添加一个新的点击事件监听器但是这些监听器不会自动被移除。要释放内存需要在合适的时候如组件卸载或者不再需要监听事件时使用removeEventListener来移除事件监听器。 定时器 类似地在函数内部设置的定时器如setTimeout或setInterval也可能会导致内存问题。例如 function setTimerFunction() {let timerId setTimeout(function() {console.log(Timer expired);}, 1000);
}
setTimerFunction();要释放定时器占用的资源可以使用clearTimeout对于setTimeout或者clearInterval对于setInterval来取消定时器。例如 function cancelTimerFunction() {let timerId setTimeout(function() {console.log(Timer expired);}, 1000);clearTimeout(timerId);
}
cancelTimerFunction();拓展-垃圾回收机制 垃圾回收的概念和重要性 概念垃圾回收Garbage CollectionGC是一种自动内存管理机制用于回收程序中不再使用的内存。在JavaScript等高级编程语言中开发人员不需要手动分配和释放内存来存储对象和数据垃圾回收器会自动处理这些事情。这大大简化了编程工作但也需要开发人员理解其基本原理以避免潜在的内存泄漏等问题。重要性如果没有垃圾回收机制随着程序的运行内存中会积累大量不再被使用的对象导致内存泄漏。内存泄漏会逐渐耗尽系统的内存资源最终可能使程序崩溃或者系统运行缓慢。例如在一个长期运行的Web应用程序中如果存在内存泄漏用户在浏览页面时会发现页面越来越卡顿甚至浏览器可能会因为内存耗尽而无响应。 引用计数垃圾回收算法 原理 引用计数算法是一种比较简单的垃圾回收算法。它的基本思想是为每个对象维护一个引用计数。当一个对象被创建并赋值给一个变量时它的引用计数为1当有新的变量引用这个对象时引用计数加1当一个引用该对象的变量不再使用例如变量被重新赋值或者超出了作用域引用计数减1。当一个对象的引用计数为0时就表示这个对象不再被使用可以被回收。例如在下面的代码中 let a {name: objectA
};
let b a;
// 此时对象a的引用计数为2
a null;
// 引用计数减1变为1
b null;
// 引用计数变为0对象a可以被垃圾回收局限性 循环引用问题是引用计数算法的主要缺陷。当两个或多个对象相互引用形成一个循环时它们的引用计数永远不会为0即使这些对象从程序的其他部分无法访问。例如 function createCycle() {let obj1 {};let obj2 {};obj1.other obj2;obj2.other obj1;
}
createCycle();在这个例子中obj1和obj2相互引用它们的引用计数都为2自身的引用和对方的引用。即使createCycle函数执行完毕这两个对象在引用计数算法下也不会被回收从而导致内存泄漏。 标记 - 清除垃圾回收算法 原理 标记 - 清除算法是现代JavaScript引擎中常用的垃圾回收算法之一。它的基本过程包括两个阶段标记阶段和清除阶段。在标记阶段垃圾回收器从一组被称为“根”roots的对象开始这些根对象通常包括全局对象在浏览器环境中是window对象、当前执行栈中的变量等。然后沿着对象之间的引用关系进行遍历标记所有从根对象可达的对象。在清除阶段垃圾回收器遍历整个堆内存回收那些没有被标记的对象即将它们所占用的内存释放掉。例如在一个简单的JavaScript程序中假设全局变量globalObj引用了一个对象这个对象又引用了其他对象垃圾回收器会从globalObj开始标记所有可达的对象然后清除那些不可达的对象。 与引用计数算法对比 标记 - 清除算法能够很好地解决循环引用的问题。在上面的循环引用示例中虽然obj1和obj2相互引用但如果它们无法从根对象到达那么在标记 - 清除算法下它们会在清除阶段被回收。不过标记 - 清除算法也有一些缺点。它在标记和清除过程中需要暂停程序的执行称为“STW”Stop - The - World这可能会导致短暂的性能卡顿。而且清除后的内存空间是不连续的可能会产生内存碎片影响后续内存分配的效率。 标记 - 整理垃圾回收算法 原理 标记 - 整理算法是在标记 - 清除算法的基础上发展而来的。它同样包括标记阶段标记从根对象可达的对象。在清除阶段它不是简单地回收未标记的对象而是将所有存活的被标记的对象向一端移动然后将剩余的内存空间一次性清理掉。这样就解决了标记 - 清除算法产生内存碎片的问题。 性能考虑 标记 - 整理算法虽然解决了内存碎片问题但在移动对象的过程中也需要消耗更多的时间和资源。因此不同的JavaScript引擎会根据具体的应用场景和性能需求选择合适的垃圾回收算法或者结合使用多种算法。例如V8引擎在新生代对象刚创建时所处的内存区域主要使用复制算法一种特殊的高效的内存回收算法通过将存活对象复制到新的内存空间来实现回收在老生代对象经过一段时间后从新生代晋升到的内存区域可能会结合使用标记 - 清除和标记 - 整理算法。 分代垃圾回收以V8引擎为例 新生代和老生代的划分 V8引擎将内存分为新生代和老生代两个区域。新生代主要用于存储新创建的对象通常这些对象的生命周期较短。老生代用于存储经过多次垃圾回收后仍然存活的对象这些对象的生命周期较长。新生代内存空间相对较小一般采用更高效的复制算法进行垃圾回收。在复制算法中新生代内存被划分为两个等大小的区域称为From空间和To空间。当进行垃圾回收时将From空间中存活的对象复制到To空间然后清空From空间最后将From空间和To空间的角色互换。 对象晋升 当一个对象在新生代中经过多次垃圾回收后仍然存活它会被晋升到老生代。晋升的条件可能包括对象的存活时间达到一定阈值、对象的大小超过一定限制等。老生代的垃圾回收相对复杂因为其中的对象数量较多且生命周期较长通常会结合使用标记 - 清除和标记 - 整理算法。
垃圾回收之触发应用 垃圾回收器运行的情况 内存达到一定阈值 不同的JavaScript引擎如V8引擎等有自己的内存管理策略。当内存占用达到一定的阈值时垃圾回收器就会自动启动。这个阈值是由JavaScript引擎内部设定的目的是平衡性能和内存使用。例如在浏览器环境中V8引擎会监控堆内存用于存储对象等数据的使用情况。当堆内存的占用接近其容量上限时垃圾回收器就会被触发开始清理那些不可达的对象释放内存空间。 程序空闲时段 为了尽量减少垃圾回收对程序性能的影响JavaScript引擎通常会选择在程序相对空闲的时段运行垃圾回收。比如当事件循环Event Loop中的任务队列暂时为空没有正在执行的JavaScript代码且浏览器没有其他高优先级的任务如页面渲染时垃圾回收器就可能会启动。这就像是在打扫房间选择在房间没人活动的时候进行打扫以避免干扰正常的活动。 全局变量或对象的生命周期结束理论情况 在一个理想的模型中如果一个全局变量所引用的对象不再被需要且这个全局变量被重新赋值或者删除那么垃圾回收器应该回收这个对象。例如在一个简单的脚本中 let globalObject {property: value
};
// 使用globalObject进行一些操作...
globalObject null;
// 理论上此时这个对象应该在之后被垃圾回收不过在实际情况中由于JavaScript引擎的复杂性和各种优化策略即使这样设置为null垃圾回收器也不一定会立即运行而是会根据上述提到的内存阈值和空闲时间等因素来决定何时运行。 无法主动触发垃圾回收机制在标准JavaScript中 在标准的JavaScript规范中没有提供直接触发垃圾回收的方法。这是因为垃圾回收是一个复杂的过程由JavaScript引擎自动管理目的是确保内存的高效利用和程序的性能稳定。如果允许随意触发垃圾回收可能会导致性能问题例如频繁地触发垃圾回收可能会打断程序的正常执行导致程序卡顿。虽然不能直接触发但可以通过优化代码来间接地影响垃圾回收的效果。例如及时释放不再使用的对象引用如将变量设置为null避免创建不必要的全局变量以及正确地管理闭包等这些做法可以让垃圾回收器更容易识别出哪些对象是不可达的从而更高效地回收内存。