前端面试题和对应答案及解释

本文介绍了闭包的概念及其在函数封装中的应用,事件冒泡和事件捕获的传播机制以及两者区别,防抖和节流技术在控制函数执行频率上的作用,VirtualDOM的优化原理,以及异步函数如何简化Promise处理。同时,讨论了跨域请求的挑战和解决方案,如JSONP和CORS策略。
  1. 请解释一下什么是闭包,并举一个实际应用的例子。

答案:闭包是指函数可以访问其词法作用域之外的变量。当内部函数引用了外部函数的变量时,就创建了一个闭包。闭包可以用来实现数据的封装和保护。 例如,下面的代码创建了一个计数器函数,利用闭包来实现数据的封装:

function createCounter() {
  let count = 0;
  
  function increment() {
    count++;
    console.log(count);
  }
  
  return increment;
}

const counter = createCounter();
counter();  // 输出:1
counter();  // 输出:2

在上面的例子中,createCounter函数返回了一个内部函数increment,而increment函数引用了外部函数createCounter的变量count。每次调用counter函数,都会增加count的值并输出。这里的count变量被封装在闭包中,外部无法直接访问和修改,实现了数据的保护。

  1. 请解释一下什么是事件冒泡和事件捕获,并说明它们之间的区别。

答案:事件冒泡和事件捕获是两种不同的事件传播机制。

  • 事件冒泡:当一个元素上的事件被触发时,事件会从最具体的元素开始,然后逐级向上传播到较不具体的元素。也就是说,事件会先触发目标元素上的处理函数,然后再逐级向上触发其父元素的处理函数,直到顶层的元素。这种传播方式被称为事件冒泡。

  • 事件捕获:与事件冒泡相反,事件捕获是从顶层元素开始,然后逐级向下传播到最具体的元素。也就是说,事件会先触发顶层元素上的处理函数,然后再逐级向下触发子元素的处理函数,直到目标元素。这种传播方式被称为事件捕获。

区别:事件冒泡和事件捕获的主要区别在于事件传播的起始点和传播的顺序。事件冒泡从目标元素开始,逐级向上传播;事件捕获从顶层元素开始,逐级向下传播。大多数浏览器会默认使用事件冒泡,但也可以通过addEventListener方法的第三个参数设置为true来使用事件捕获

  1. 请解释一下什么是防抖和节流,并举一个实际应用的例子。

答案:防抖和节流是用来控制函数执行频率的两种常用技术。

  • 防抖(Debounce):防抖的原理是在事件触发后等待一段时间,如果在这段时间内再次触发了同样的事件,则重新计时。如果一定时间内没有再次触发事件,才执行相应的函数。防抖常用于输入框输入检测,例如在用户输入结束后才执行某个搜索操作,以减少频繁的请求。下面是一个简单的防抖函数示例:
  • function debounce(func, delay) {
      let timer;
      
      return function() {
        clearTimeout(timer);
        timer = setTimeout(func, delay);
      }
    }
    
    const handleInput = debounce(function() {
      console.log("Input debounced");
    }, 500);
    
    // 在输入框输入时触发该函数
    

    在上面的例子中,handleInput函数使用防抖技术,在用户输入时会触发该函数。如果在500ms内再次输入,计时器会被重新计时,直到用户输入结束后才执行函数。

  • 节流(Throttle):节流的原理是在一定时间间隔内只执行一次函数。如果在这段时间内多次触发了同样的事件,只有第一次触发会执行函数,之后的触发都会被忽略。节流常用于限制函数的执行频率,例如在滚动事件中执行一些耗时的操作。下面是一个简单的节流函数示例:
  • function throttle(func, delay) {
      let canRun = true;
      
      return function() {
        if (canRun) {
          func();
          canRun = false;
          setTimeout(function() {
            canRun = true;
          }, delay);
        }
      }
    }
    
    const handleScroll = throttle(function() {
      console.log("Scroll throttled");
    }, 1000);
    
    // 在滚动事件中触发该函数
    

    在上面的例子中,handleScroll函数使用节流技术,在滚动事件中触发该函数。在每次触发后的1000ms内,只有第一次触发会执行函数,之后的触发都会被忽略。

  • 请解释一下什么是 Virtual DOM(虚拟 DOM)?

  • 答案:Virtual DOM 是一个程序概念,它是由 JavaScript 对象表示的轻量级 DOM。虚拟 DOM 具有与真实 DOM 相似的结构,但只是存在于内存中,而不是浏览器中的实际 DOM 树。当数据更新时,虚拟 DOM 会与实际 DOM 进行比较,并计算出最小的更新量,然后只更新需要更改的部分,以提高性能和效率。

    虚拟 DOM 的优势在于它可以减少对实际 DOM 的直接操作次数,从而提高渲染性能。通过使用虚拟 DOM,可以将多个 DOM 更新合并为一次,减少了对实际 DOM 的访问和操作,从而提高了应用的性能。

  • 请解释一下什么是异步函数(Async/Await)?
  • 答案:异步函数是一种用于处理异步操作的 JavaScript 函数。它的特点是可以使用 await 关键字来暂停函数的执行,等待一个 Promise 对象的结果,并将结果作为函数返回值。异步函数基于 Promise,使得异步操作的代码可以以同步的方式编写,更加清晰和易于理解。

    例如,下面是一个使用异步函数的例子:

  • async function fetchData() {
      try {
        const response = await fetch('http://example.com/data');
        const data = await response.json();
        console.log(data);
      } catch (error) {
        console.error(error);
      }
    }
    
    fetchData();
    

    在上面的例子中,fetchData 函数使用 await 关键字暂停函数的执行,等待 fetch 返回的 Promise 对象结果。当 Promise 对象被解析后,结果会赋值给 response 变量。然后再次使用 await 关键字等待 response.json() 结果的解析。通过使用异步函数,可以以更加清晰和同步的方式编写异步操作的代码。

  • 请解释一下什么是跨域请求(Cross-Origin Request)?如何解决跨域问题?
  • JSONP:通过动态创建 <script> 标签来实现跨域请求,服务器返回的数据需要包装在回调函数中。但是这种方法只能用于 GET 请求,且存在安全风险。
  • CORS:通过服务器设置响应头来允许跨域请求。服务器需要设置 Access-Control-Allow-Origin 头部字段,指定允许访问的域。这种方法支持各种 HTTP 请求方法,并且比 JSONP 更安全。
  • 代理服务器:通过在同源域名下设置一个代理服务器,将跨域请求转发到目标服务器。这种方法需要服务器端的支持,适用于复杂的请求场景。
  • 答案:跨域请求是指在浏览器中,当前网页的域与请求的目标资源的域不同,即存在跨域。出于安全原因,浏览器会限制跨域请求的访问,防止恶意的操作。

    解决跨域问题的常用方法有:

### 防抖与节流 - **问题**:请说明防抖与节流的区别及实现。 - **答案**:防抖是指在一定时间内,只有最后一次调用函数才会被执行;节流是指在一定时间内,函数只能被调用一次。 ```javascript // 防抖实现 function debounce(func, delay) { let timer = null; return function() { const context = this; const args = arguments; clearTimeout(timer); timer = setTimeout(() => { func.apply(context, args); }, delay); }; } // 节流实现 function throttle(func, delay) { let flag = true; return function() { if (!flag) return; const context = this; const args = arguments; flag = false; setTimeout(() => { func.apply(context, args); flag = true; }, delay); }; } ``` ### 闭包 - **问题**:请说明闭包应用场景与内存泄漏风险。 - **答案**:闭包的应用场景包括实现私有变量、函数柯里化等。但如果闭包使用不当,会导致内存泄漏,因为闭包会引用外部函数的变量,使得这些变量无法被垃圾回收。例如: ```javascript function outer() { const data = 'some data'; return function inner() { console.log(data); }; } const closure = outer(); closure(); ``` ### 数组方法 - **问题**:`['1','2','3'].map(parseInt)`的结果及原因是什么? - **答案**:结果是`[1, NaN, NaN]`。`map`方法会给回调函数传递三个参数:当前元素、当前索引数组本身。`parseInt`函数接收两个参数:要解析的字符串进制数。当`map`调用`parseInt`时,会将索引作为进制数传递给`parseInt`,所以`parseInt('1', 0)`结果为`1`,`parseInt('2', 1)`中`1`不是有效的进制数,结果为`NaN`,`parseInt('3', 2)`中`3`不是二进制数,结果为`NaN`。 ### 继承方式 - **问题**:请说明JavaScript继承的多种实现方式。 - **答案**:常见的继承方式有原型链、组合继承等。原型继承示例如下: ```javascript function Parent() { this.name = 'Parent'; } Parent.prototype.sayHello = function() { console.log('Hello from Parent'); }; function Child() { this.name = 'Child'; } Child.prototype = Object.create(Parent.prototype); Child.prototype.constructor = Child; const child = new Child(); child.sayHello(); ``` ### 事件循环 - **问题**:请说明事件循环机制与宏任务/微任务执行顺序。 - **答案**:事件循环是JavaScript的执行机制,它负责处理异步任务。宏任务微任务是异步任务的分类。执行顺序是先执行同步代码,遇到微任务会将其放入微任务队列,遇到宏任务会将其放入宏任务队列。同步代码执行完后,会先清空微任务队列,再从宏任务队列中取出一个任务执行,然后再清空微任务队列,如此循环。 ### Promise - **问题**:请说明Promise核心原理与手写实现。 - **答案**:Promise是一种异步编程的解决方案,有三种状态:pending(进行中)、fulfilled(已成功)、rejected(已失败)。一旦状态改变,就不会再变。手写Promise的简单实现如下: ```javascript const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected'; class MyPromise { constructor(executor) { this.status = PENDING; this.value = undefined; this.reason = undefined; this.onFulfilledCallbacks = []; this.onRejectedCallbacks = []; const resolve = (value) => { if (this.status === PENDING) { this.status = FULFILLED; this.value = value; this.onFulfilledCallbacks.forEach(callback => callback()); } }; const reject = (reason) => { if (this.status === PENDING) { this.status = REJECTED; this.reason = reason; this.onRejectedCallbacks.forEach(callback => callback()); } }; try { executor(resolve, reject); } catch (error) { reject(error); } } then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function' ? onRejected : error => { throw error }; const newPromise = new MyPromise((resolve, reject) => { const handleFulfilled = () => { try { const result = onFulfilled(this.value); resolve(result); } catch (error) { reject(error); } }; const handleRejected = () => { try { const result = onRejected(this.reason); resolve(result); } catch (error) { reject(error); } }; if (this.status === FULFILLED) { setTimeout(handleFulfilled, 0); } else if (this.status === REJECTED) { setTimeout(handleRejected, 0); } else { this.onFulfilledCallbacks.push(handleFulfilled); this.onRejectedCallbacks.push(handleRejected); } }); return newPromise; } } ``` ### ES6+新特性 - **问题**:请解释ES6可选链空值合并运算符的使用场景。 - **答案**:可选链运算符`?.`用于在访问对象属性或调用方法时,避免因对象为`null`或`undefined`而抛出错误。空值合并运算符`??`用于在左侧值为`null`或`undefined`时,返回右侧的值。例如: ```javascript const obj = { prop1: { prop2: 'value' } }; const value = obj?.prop1?.prop2; const fallback = null ?? 'default'; ``` ### this指向 - **问题**:请说明this指向的四种绑定规则及优先级。 - **答案**:this指向的四种绑定规则是默认绑定、隐式绑定、显式绑定new绑定。优先级从低到高为:默认绑定 < 隐式绑定 < 显式绑定 < new绑定。 ### 深拷贝 - **问题**:请手写深拷贝函数,同时解决循环引用问题。 - **答案**: ```javascript function deepClone(obj, map = new WeakMap()) { if (typeof obj !== 'object' || obj === null) { return obj; } if (map.has(obj)) { return map.get(obj); } const clone = Array.isArray(obj) ? [] : {}; map.set(obj, clone); for (const key in obj) { if (obj.hasOwnProperty(key)) { clone[key] = deepClone(obj[key], map); } } return clone; } ``` ### 内存泄漏 - **问题**:请说明JavaScript内存泄漏的常见场景及排查方法。 - **答案**:常见场景包括未清除的定时器、未移除的事件监听器、闭包使用不当等。排查方法可以使用浏览器的开发者工具,如Chrome的Memory面板,通过快照对比来找出内存泄漏的原因。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值