前端开发面试题总结-JavaScript篇(一)

文章目录

JavaScript高频问答

一、作用域与闭包

1.什么是闭包(Closure)?闭包有什么应用场景和潜在问题?

● 为什么⾯试官喜欢问闭包?

在 JavaScript 中,闭包(closure)是⼀个重要的概念。它不仅是理解 JavaScript 作⽤域和作⽤域链的 关键,还是实现⼀些⾼级特性和设计模式的基础。

● 什么是闭包?

闭包是指⼀个函数能够访问并操作其⽗函数作⽤域中的变量,即使该⽗函数已经执⾏完毕,离开了执⾏ 环境。在 JavaScript 中,函数内部定义的函数,由于作⽤域链的关系,可以形成闭包。

● 使⽤场景

封装变量, 闭包可以⽤来封装变量,使其不受外界的⼲扰,同时⼜可以通过返回的函数来访问和操作这些变量

typescript 复制代码
function createCounter() {
  let count = 0;
 
  return function() {
    return ++count;
  };
 }
 
const counter = createCounter();
console.log(counter()); // 输出: 1
console.log(counter()); // 输出: 

在上述例⼦中,我们通过 createCounter 函数创建了⼀个计数器,并返回了⼀个闭包函数。这个闭包函数 可以访问和操作 count 变量,⽽ count 变量是被封装的,外界⽆法直接访问。

模块化开发, 闭包在模块化开发中发挥了重要的作⽤。通过闭包,我们可以创建私有变量和⽅法,避免全局命名冲突 和变量污染。

typescript 复制代码
 const module = (function() {
  let privateVariable = 1;
 
  function privateMethod() {
    console.log('私有⽅法');
  }
 
  return {
    publicMethod: function() {
      console.log('公开⽅法');
    }
  };
 })();
 
module.publicMethod(); // 输出: 公开⽅法
module.privateMethod(); // 输出: Uncaught TypeError: module.privateMethod is not a function

在这个例⼦中,我们使⽤⽴即执⾏函数创建了⼀个匿名的函数作⽤域,并返回了⼀个具有公开⽅法的对 象。在函数作⽤域内定义的 privateVariable 和 privateMethod 是私有的,外界⽆法直接访问。

实现缓存 闭包可以⽤于实现数据缓存,特别是在频繁调⽤的情况下提⾼性能

typescript 复制代码
function createCache() {
   const cache = {};
   return function(key, value) {
       if (typeof value !== 'undefined') { // 设置缓存
         cache[key] = value;
       } else { // 获取缓存
         return cache[key];
       }
   };
}
const cache = createCache();
cache('name', 'Tom'); // 设置缓存
console.log(cache('name')); // 输出: Tom

在这个例⼦中,我们通过 createCache 函数创建了⼀个⽤于缓存数据的闭包函数。当使⽤闭包函数设置缓 存时,将数据存储在 cache 对象中。当使⽤闭包函数获取缓存时,从 cache 对象中查找并返回数据。

实现私有方法 对于⾯向对象编程来说,私有⽅法是⼀种封装数据和⾏为的重要⽅式,可以防⽌外部直接访问和修改内 部状态。闭包可以帮助我们实现私有⽅法

typescript 复制代码
 function createPerson(name) {
   const greeting = 'Hello, ' + name;
   return {
     sayHello: function() {
       console.log(greeting);
     }
   };
 }
 const person = createPerson('Tom');
 person.sayHello(); // 输出: Hello, Tom
 person.greeting; // undefined

在这个例⼦中,我们通过 问和操作 createPerson 函数创建了⼀个对象,其中包含⼀个闭包函数 greeting 变量。外界⽆法直接访问 sayHello,它可以访 greeting 变量,从⽽确保了数据的私有性。

事件处理 闭包在事件处理中⾮常常⻅,特别是在循环或定时器等异步操作中。使⽤闭包可以保存循环变量或定时 器的参数,并确保在回调函数执⾏时以正确的值进⾏处理。例如:

typescript 复制代码
for (var i = 0; i < 5; i++) {
   (function(index) {
     setTimeout(function() {
       console.log(index);
     }, 1000);
   })(i);
 }

在这个例⼦中,我们使⽤⽴即执⾏函数创建了⼀个新的函数作⽤域,每次循环都将 i 的值传递给⽴即执 ⾏函数的参数 index,从⽽在定时器回调函数执⾏时正确地打印每次循环的值。

实现回调函数 闭包在处理回调函数时⾮常有⽤,特别是在处理异步操作的结果或处理事件的响应时。闭包能够保存局 部变量和状态,并在回调函数被调⽤时使⽤。例如:

typescript 复制代码
function fetchData(url, callback) {
   // 发送⽹络请求获取数据
  setTimeout(function() {
     const data = 'Some data';
     callback(data);
   }, 2000);
 }

 fetchData('https://example.com', function(data) {
   console.log(data);
 );

在这个例⼦中,我们定义了⼀个 JavaScript fetchData 函数⽤于异步获取数据。在获取到数据后,通过闭包将数据传 递给回调函数并执⾏回调函数,从⽽实现对数据的处理和使⽤。

实现递归 闭包在递归算法中经常被使⽤,可以保存递归中的状态和结果,并确保在每次递归调⽤时使⽤正确的 值。例如 :

typescript 复制代码
function factorial(n) {
   if (n === 1) {
     return 1;
   } else {
     return n * factorial(n - 1);
   }
 }
 console.log(factorial(5)); // 输出: 120

在这个例⼦中, factorial 函数使⽤递归的⽅式计算阶乘。在每次递归调⽤时,通过闭包保存当前的状态和 结果,并随着递归的进⾏传递给下⼀次的递归调⽤。

闭包在 JavaScript 中具有重要的意义,除了以上的⼏个场景之外, 它还可以解决函数柯⾥化和迭代器以 及链式调⽤等其他问题。掌握闭包的概念和使⽤场景,对于编写⾼效、安全的 JavaScript 代码⾮常有帮助

2.解释 JavaScript 的作用域链(Scope Chain)

作用域链是变量查找的机制。函数执行时,会先从当前作用域查找变量,如果找不到则向外层作用域逐级查找,直到全局作用域。作用域链在函数定义时确定,与调用位置无关(词法作用域)。

typescript 复制代码
  let num1 = 100
    
  function sum(){
    let num2 = 200
    function sum2(){
      console.log(num1)
    }
    sum2()
  }
  sum()

二、原型与继承

3.原型链是什么?如何实现继承?

● 原型链:每个对象都有一个 __proto__ 属性指向其构造函数的原型对象(prototype),形成链式结构,实现属性和方法的继承。

实现继承(ES5):

○ 原型链继承

○ 构造函数继承

○ 组合式继承

○ 原型式继承

○ 寄生式继承

○寄生组合式继承

typescript 复制代码
function Parent(name) { this.name = name; }
Parent.prototype.say = function() { console.log(this.name); };
function Child(name) { Parent.call(this, name); }
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

ES6+

class类结合extends和super函数实现继承

4.typeof 返回的数据类型

typeof 适合检测简单数据类型和函数,但对引用类型(如数组、对象)区分能力有限,需结合其他方法(如 instanceof、Array.isArray())使用。

5.typeof 和 instanceof 的区别

● typeof:检测基本数据类型(返回类型字符串,如 "number"、"string")。

● instanceof:检测引用类型(判断对象是否是某个构造函数的实例,如 [] instanceof Array → true)。

● 适用场景:

○ typeof:快速判断基本类型或检查变量是否定义。

○ instanceof:确认对象的具体类型(如数组、自定义类)。

6.instanceof 的原理是什么?

typeOf只能判断简单数据的数据类型, 如果要去判断复杂数据的数据类型则用instanceof, 原理如下:

instanceof 通过检查对象的原型链中是否存在构造函数的 prototype 属性:

typescript 复制代码
function myInstanceof(obj, Constructor) {
  let proto = obj.__proto__;
  while (proto) {
    if (proto === Constructor.prototype) return true;
    proto = proto.__proto__;
  }
  return false;
}

三、异步与事件循环

7.解释事件循环(Event Loop)和宏任务/微任务

● 事件循环:JavaScript 是单线程的, 需要结合事件循环去处理异步,代码在执行之前会有预解析, 把同步代码放在主线程, 把异步代码放在事件循环;

● 事件调用又分为调用栈、任务队列(宏任务队列和微任务队列)。

● 执行顺序:同步代码 → 微任务(如 Promise.then)→ 宏任务(如 setTimeout)。

typescript 复制代码
console.log(1);
setTimeout(() => console.log(2), 0);
Promise.resolve().then(() => console.log(3));
console.log(4);
// 输出顺序:1 → 4 → 3 → 2

8.Promise 和 async/await 的区别

● Promise:通过链式调用 .then 处理异步,解决回调地狱,但嵌套多时仍不够直观。

○ 常用的静态方法: Promise.all(), Promise.race(), Promise.resolve(), Promise.reject(), Promise.allSettled()...

● async/await:基于 Promise 的语法糖,用同步写法处理异步,更易读。

typescript 复制代码
async function fetchData() {
  try {
    const res = await fetch('url');
    const data = await res.json();
  } catch (error) {
    console.error(error);
  }
}

四、ES6+ 特性

9.let、const 和 var 的区别

答案:

● var:函数作用域,存在变量提升(声明前访问为 undefined)。

● let/const:块级作用域,不存在变量提升,存在暂时性死区(声明前访问报错)。

● const:声明常量,赋值后不可修改(对象属性可修改, 数组的内容可修改)。

javascript 复制代码
// 1. var存在变量提升
var dog
console.log(dog)
console.log('666')
// var dog = '旺财'
dog = '旺财'

10.箭头函数与普通函数的区别

答案:

● 箭头函数没有自己的 this,继承外层作用域的 this。

● 不能用作构造函数(无法 new)。

● 没有 arguments 对象,可用剩余参数(...args)替代。

● 箭头函数没有prototype, 普通函数具备prototype。

javascript 复制代码
// 1. 不能用作构造函数
// 正确的
function Person(){}
new Person()
// 错误的
const Dog = ()=>{}
new Dog()

// 2. 没有 arguments 对象
function sum(num1, num2){
  // 函数内置的一个对象
  // 用于存放调用函数时传入的所有实参
  console.log(arguments)
}
sum(100, 200)

// 报错: arguments is not defined
const sum1 = (num1)=>{
  console.log(arguments)
}
sum1(100)

五、高频手写代码题

11.手写防抖(Debounce)和节流(Throttle)

javascript 复制代码
// 防抖:多次触发后只执行一次
// 使用场景: 搜索框
function debounce(fn, delay) {
  let timer;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

// 节流:固定时间内只执行一次
// 使用场景: scroll滚动, resize可视区域变化
function throttle(fn, delay) {
  let last = 0;
  return function(...args) {
    const now = Date.now();
    if (now - last >= delay) {
      fn.apply(this, args);
      last = now;
    }
  };
}

12.手写深拷贝(Deep Clone)

javascript 复制代码
function deepClone(obj, map = new Map()) {
  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;
}

六、性能优化

13.如何避免 JavaScript 中的内存泄漏?

答案:

● 常见原因:

a. 未清理的全局变量、定时器或事件监听。

b. 闭包中意外保留的大对象。

c. DOM 引用未释放(如已移除节点的引用)。

● 解决方法:

○ 使用 let/const 替代 var,减少全局变量。

○ 及时清除定时器(clearTimeout)、事件监听(removeEventListener)。

○ 使用弱引用(WeakMap/WeakSet)管理对象。

14.JavaScript 的垃圾回收机制是什么?常见算法有哪些?

答案:

● 机制:自动回收不再使用的内存,防止内存泄漏。

● 常见算法:

○ 标记清除(Mark-Sweep):标记所有可达对象,清除未标记的对象(主流浏览器使用)。

○ 引用计数(Reference Counting):记录对象被引用次数,归零时回收(无法处理循环引用,已淘汰)。

○ 分代回收(Generational Collection):V8 引擎将内存分为新生代(短生命周期)和老生代(长生命周期),分别使用 Scavenge 和标记清除/整理算法。

相关推荐
阿阳微客22 分钟前
Steam 搬砖项目深度拆解:从抵触到真香的转型之路
前端·笔记·学习·游戏
德育处主任Pro1 小时前
『React』Fragment的用法及简写形式
前端·javascript·react.js
CodeBlossom1 小时前
javaweb -html -CSS
前端·javascript·html
CodeCraft Studio1 小时前
【案例分享】如何借助JS UI组件库DHTMLX Suite构建高效物联网IIoT平台
javascript·物联网·ui
朝新_1 小时前
【多线程初阶】阻塞队列 & 生产者消费者模型
java·开发语言·javaee
立莹Sir1 小时前
Calendar类日期设置进位问题
java·开发语言
打小就很皮...2 小时前
HBuilder 发行Android(apk包)全流程指南
前端·javascript·微信小程序
风逸hhh2 小时前
python打卡day46@浙大疏锦行
开发语言·python
火兮明兮3 小时前
Python训练第四十三天
开发语言·python
集成显卡3 小时前
PlayWright | 初识微软出品的 WEB 应用自动化测试框架
前端·chrome·测试工具·microsoft·自动化·edge浏览器