每日一篇——23秋招JS面经(3)
⭐JS垃圾回收机制
-
项目中,如果存在大量不被释放的内存(堆/栈/上下文),页面性能会变得很慢。当某些代码操作不能被合理释放,就会造成内存泄漏。我们尽可能减少使用闭包,因为它会消耗内存。
-
浏览器垃圾回收机制/内存回收机制:
浏览器的
Javascript具有自动垃圾回收机制(GC:Garbage Collecation),垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存。标记清除:在
js中,最常用的垃圾回收机制是标记清除:浏览器会为内存中所有的变量添加标记,然后再遍历上下文,为其中所有变量及被变量引用的变量清除标记,这样剩下被标记的就是待回收对象。 谷歌浏览器:“查找引用”,浏览器不定时去查找当前内存的引用,如果没有被占用了,浏览器会回收它;如果被占用,就不能回收。 IE浏览器:“引用计数法”,当前内存被占用一次,计数累加1次,移除占用就减1,减到0时,浏览器就回收它。 -
优化手段:内存优化 ; 手动释放:取消内存的占用即可。
(1)堆内存:fn = null 【null:空指针对象】
(2)栈内存:把上下文中,被外部占用的堆的占用取消即可。
(3)防止出现隐藏类,在一开始调用构造函数生成实例时就传入需要的变量,防止动态添加和删除属性
(4)静态分配和对象池
-
内存泄漏
在 JS 中,常见的内存泄露主要有 4 种,全局变量、闭包、DOM 元素的引用、定时器
⭐JS 中 this 的五种情况
- 作为普通函数执行时,
this指向window。 - 当函数作为对象的方法被调用时,
this就会指向该对象。 - 构造器调用,
this指向返回的这个对象。 - 箭头函数捕获的是最近一层非箭头函数作用域的
this值,不会绑定到字面量复制的对象上。 - 基于Function.prototype上的
apply 、 call 和 bind调用模式,这三个方法都可以显示的指定调用函数的 this 指向。apply接收参数的是数组,call接受参数列表,bind方法通过传入一个对象,返回一个this绑定了传入对象的新函数。这个函数的this指向除了使用new时会被改变,其他情况下都不会改变。若为空默认是指向全局对象window。
⭐原型 && 原型链
原型关系:
- 每个 class都有显示原型 prototype
- 每个实例都有隐式原型 _ proto_
- 实例的_ proto_指向对应 class 的 prototype
原型: 在 JS 中,每当定义一个对象(函数也是对象)时,对象中都会包含一些预定义的属性。其中每个函数对象都有一个prototype 属性,这个属性指向函数的原型对象。
原型链:函数的原型对象上的constructor默认指向函数本身,原型对象除了有原型属性外,为了实现继承,还有一个原型链指针_proto_,该指针是指向上一层的原型对象,而上一层的原型对象的结构依然类似。因此可以利用_proto_一直指向Object的原型对象上,而Object原型对象用Object.prototype. proto__ = null表示原型链顶端。如此形成了js的原型链继承。同时所有的js对象都有Object的基本方法
特点: JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。
⭐new运算符的实现机制
- 首先创建了一个新的
空对象 设置原型,将对象的原型设置为函数的prototype对象。- 让函数的
this指向这个对象,执行构造函数的代码(为这个新对象添加属性) - 判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。
⭐请解释一下什么是事件委托(或称事件代理),以及它在前端开发中的作用是什么?
事件委托(或称事件代理)是一种在前端开发中常用的技术,它利用了事件冒泡机制,将事件处理程序绑定到一个父元素上,而不是直接绑定到每个子元素上。当事件触发时,事件会经过冒泡阶段依次向上传播至父元素,父元素可以根据事件的目标元素来判断应该执行哪个事件处理函数。
事件委托的作用主要有以下几点:
- 减少事件处理程序的数量:通过将事件处理程序绑定到父元素上,可以避免为每个子元素都绑定事件处理程序,减少内存消耗。
- 动态绑定事件:对于通过 JavaScript 动态添加的子元素,使用事件委托可以自动为它们绑定事件处理程序,无需额外的操作。
- 简化代码结构:使用事件委托可以将事件处理程序集中管理,使代码更加简洁、可读性更高。
举个例子,假设有一个列表的父元素,里面包含了多个子元素(项),我们需要为每个子元素绑定点击事件,实现点击子元素时改变其样式。使用事件委托可以这样处理:
// 获取父元素
const parentElement = document.getElementById('parent');
// 绑定点击事件到父元素
parentElement.addEventListener('click', function(event) {
// 判断事件目标是否是子元素(项)
if (event.target && event.target.nodeName === 'LI') {
// 执行事件处理逻辑,例如改变样式
event.target.classList.toggle('active');
}
});
通过将点击事件绑定到父元素上,无论新增或删除了子元素,甚至是动态生成的子元素,都会自动触发相应的事件处理逻辑,大大简化了代码的管理和维护。