前端开发面试题总结-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 和标记清除/整理算法。

相关推荐
csbysj2020几秒前
jEasyUI 创建分割按钮
开发语言
醇氧几秒前
用 CC Switch (cc-sw) 配置 Claude Code 接入 阿里云百炼 (Dashscope)
人工智能·学习·阿里云·ai·云计算
FmZero1 分钟前
后端全栈路线(9小时前端速成)
前端·vscode·学习
wjs20242 分钟前
前端控制器模式(Front Controller Pattern)
开发语言
万世浮华戏骨3 分钟前
Web 后端 Python 基础安全
前端·python·安全
Dontla5 分钟前
JWT认证流程(JSON Web Token)
前端·数据库·json
雾岛听蓝1 小时前
Qt开发核心笔记:从HelloWorld到对象树内存管理与坐标体系详解
开发语言·经验分享·笔记·qt
無限進步D5 小时前
Java 运行原理
java·开发语言·入门
是苏浙5 小时前
JDK17新增特性
java·开发语言
余人于RenYu6 小时前
Claude + Figma MCP
前端·ui·ai·figma