2024年前端面试高频题(二)

1,969 阅读8分钟

前端高频面试题2.png

1. 什么是事件冒泡和事件捕获

事件流有三个阶段:事件捕获、事件目标、事件冒泡。在事件捕获阶段,事件从文档的根节点向下传播到目标元素;在事件冒泡阶段,事件从目标元素向上传播到文档的根节点。

事件冒泡与事件捕获的区别

  • 事件捕获:事件从 document → parent → child(从外到内)
  • 事件冒泡:事件从 child → parent → document(从内到外)

事件委托通过将事件处理程序绑定到其父元素而不是每个子元素,来减少事件处理程序的数量。当子元素触发事件时,事件会冒泡到父元素,父元素可以通过事件对象的target属性找到事件目标,从而执行相应的事件处理程序。

使用事件委托可以提高性能的原因:

  • 减少内存消耗:将事件处理程序绑定到父元素,减少了每个子元素上的事件处理程序,减少内存消耗。
  • 更少的DOM操作:当添加/删除子元素时,不需要重新绑定事件处理程序,提高页面性能。
  • 动态添加事件处理程序:对于动态创建的子元素,也可以通过父元素委托事件处理程序。

例如,下面的示例代码演示了如何使用事件委托实现点击列表项高亮:

<ul id="list">
  <li>Item 1</li>
  <li>Item 2</li>
  <li>Item 3</li>
</ul>

<script>
document.getElementById('list').addEventListener('click', function(event) {
  if (event.target.tagName === 'LI') {
    event.target.classList.toggle('active');
  }
});
</script>

在上面的代码中,通过将click事件处理程序绑定到父元素list上,当列表项被点击时,通过事件委托机制实现了点击列表项高亮的效果,提高性能。

2. JavaScript中的原型和原型链是什么

2.1 定义

JavaScript是基于原型的语言,每个对象都有一个内部属性指向其原型对象,当访问一个对象的属性时,如果该对象没有该属性,JavaScript会在其原型链上查找。

2.2 原型及原型链

  • 每个构造函数都有一个prototype属性,实例对象通过__proto__Object.getPrototypeOf()访问其原型。
  • 原型链是指通过对象之间的原型关系构成的链条,形成多层级的属性查找机制。

2.3 示例代码

function Animal(name) {
    this.name = name;
}
Animal.prototype.speak = function() {
    console.log(`${this.name} makes a noise.`);
};

const dog = new Animal('Dog');
dog.speak(); // 输出:Dog makes a noise.
console.log(dog.__proto__ === Animal.prototype); // true

3. 如何实现深拷贝和浅拷贝

  • 浅拷贝:只复制对象的第一层属性,引用类型的属性仍指向原有对象。可以使用Object.assign或扩展运算符...
const shallowCopy = Object.assign({}, obj);
// 或
const shallowCopy = { ...obj };
  • 深拷贝:递归地复制对象的所有层级属性,可以利用JSON.stringifyJSON.parse
const deepCopy = JSON.parse(JSON.stringify(obj));

也可以使用第三方库如lodashcloneDeep函数。

4. 什么是闭包

4.1 定义

闭包是一个函数,能够“记住”并访问它的词法作用域,即使是在其外部被执行的时候。简单来说,闭包使得内部函数能够访问其外部函数的变量。

闭包是指在一个函数内部定义的子函数可以访问其父函数的局部变量,并且在父函数执行完毕后仍然可以保留对这些变量的引用。通过闭包,可以实现数据的封装和保护,避免全局变量污染和数据泄露。

闭包的实现机制是在父函数的作用域链中创建一个闭包作用域,将局部变量绑定到闭包作用域中。当子函数访问这些局部变量时,会通过作用域链的查找过程找到对应的变量,从而实现数据的封装和保护。

4.2 工作原理

当一个函数被声明时,它会创建一个作用域,该作用域在函数执行时会一直保持。如果在这个函数内再定义另一个函数(内部函数),这个内部函数可以访问外部函数的所有变量,包括参数。

4.3 应用场景

1. 数据封装和私有变量

function createCounter() {
    let count = 0; // 这个变量是私有的
    return {
        increment: function() {
            count++;
            return count;
        },
        decrement: function() {
            count--;
            return count;
        },
        getCount: function() {
            return count;
        }
    };
}

const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.getCount()); // 2

2. 函数工厂

function makeMultiplier(multiplier) {
    return function(x) {
        return x * multiplier;
    };
}

const double = makeMultiplier(2);
console.log(double(5)); // 10

3. 防抖与节流
使用闭包可实现防抖与节流功能,优化事件处理。

function debounce(func, delay) {
    let timeout;
    return function(...args) {
        const context = this;
        clearTimeout(timeout);
        timeout = setTimeout(() => func.apply(context, args), delay);
    };
}

window.addEventListener('resize', debounce(() => {
    console.log('Resize event handled!');
}, 300));

4.4 闭包的优点

  • 数据封装:通过闭包,可以将变量封装起来,防止外部访问,保持数据的私密性。
  • 保持状态:在异步编程和回调中,闭包可以保持对外部变量的引用。

4.5 闭包的缺点

  • 内存泄露:由于闭包保持对外部变量的引用,可能导致外部变量无法被垃圾回收,从而造成内存泄漏。
  • 性能问题:过多的闭包创建可能导致性能下降,特别是在高频调用的场景中。

5. 什么是DOM和BOM

DOM(文档对象模型)
DOM是一个将HTML和XML文档表示为对象的接口,它允许程序动态访问和更新文档的内容、结构和样式。通过DOM,JavaScript可以操作页面的各个元素,包括添加、修改或删除元素。

BOM(浏览器对象模型)
BOM代表浏览器对象模型,它提供了与浏览器交互的接口,使JavaScript能够和浏览器窗口进行交互。例如,BOM可以用来操作浏览器的历史记录、窗口大小、导航栏等。

6. 什么是虚拟DOM

定义

虚拟DOM是一个轻量级、独立于平台的DOM表示形式,它是通过JavaScript对象树来模拟真实的DOM结构。当页面中的数据发生变化时,框架会计算出新的虚拟DOM树与旧的虚拟DOM树的差异,然后只更新这些变化的部分到真实DOM中,而不是重新渲染整个页面。

工作原理

  1. 每次更新组件时,先生成一个新的虚拟DOM树。
  2. 将新的虚拟DOM与旧的虚拟DOM进行比较(称为“差异化”)。
  3. 根据比较结果,进行最小数量的真实DOM操作。

使用虚拟DOM可以提升性能的原因:

  • 减少重排和重绘:只更新变化的部分可以减少浏览器的重排和重绘次数,提高页面性能。
  • 缩短渲染时间:通过比对虚拟DOM的差异,框架可以精确地更新DOM,减少不必要的DOM操作,加快页面渲染速度。

7. 如何优化网页加载速度

7.1 使用CDN

内容分发网络(CDN)可以将内容分布在全球多个服务器上,用户的请求会被自动路由到离用户最近的服务器,减少加载时间。

7.2 压缩资源文件

使用Gzip等算法压缩HTML、CSS和JavaScript文件,减少文件的大小,提高加载速度。可以在服务器端配置:

# Apache配置
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css application/javascript ""

7.3 减少HTTP请求

  • 合并CSS和JavaScript文件,减少请求数量。
  • 利用CSS Sprites将多个小图像合并成一张图像,以减少请求数。

7.4 延迟加载(Lazy Load)

对非关键内容(如图片、视频)使用延迟加载,只有在用户即将看到它们时,才请求数据。

<img data-src="image.jpg" class="lazyload" />
<script>
    document.addEventListener("DOMContentLoaded", function() {
        var images = document.querySelectorAll("img.lazyload");
        for (var img of images) {
            img.src = img.getAttribute('data-src');
        }
    });
</script>

7.5 使用浏览器缓存

在HTTP响应中设置适当的缓存策略,以便用户在再次访问时可以从缓存中快速加载资源。

Cache-Control: public, max-age=31536000

7.6 图片优化

使用适当格式(如WebP、JPEG)和正确大小的图片,同时确保压缩图片以减少加载时间。

7.7 代码分割

使用现代JavaScript构建工具(如Webpack)进行代码分割,只在需要时加载特定的JavaScript模块。

8. JavaScript中的数据类型

基本类型:

Number String Boolean Undefined null symbol

复杂类型(引用类型):

Object Array Function

存储在内存中的位置不同:

基本数据类型存储在栈中;引⽤类型的对象存储于堆中

当我们把变量赋值给⼀个变量时,解析器⾸先要确认的就是这个值是基本类型值还是引⽤类型值 声明变量时不同的内存地址分配:

总结

简单类型的值存放在栈中,在栈中存放的是对应的值;引⽤类型对应的值存储在堆中,在栈中存放的是指向堆内存的地址

简单类型赋值,是⽣成相同的值,两个对象对应不同的地址;复杂类型赋值,是将保存对象的内存地址赋值给另⼀个变量。也就是两个变量指向堆内存中同⼀个对象