2024前端面试题库 - JavaScript专题

302 阅读5分钟

考点

  1. 基础语法
  • 变量、数据类型、运算符
  • 流程控制(if/else、for、while等)
  • 函数
  1. 对象和面向对象编程
  • 对象创建和访问(字面量、构造函数、class等)
  • 继承和原型链
  • this关键字和作用域
  • ES6新增特性(箭头函数、模板字符串、let/const等)
  1. 异步编程
  • 回调函数、事件监听和发布订阅模式
  • Promise和async/await
  • 定时器和setImmediate
  1. DOM操作
  • 元素选取和属性操作
  • 事件绑定和委托
  • 样式操作和动画效果
  1. BOM操作
  • 浏览器窗口和屏幕尺寸
  • URL解析和hash路由
  • localStorage、sessionStorage和cookie
  1. AJAX和HTTP
  • XMLHttpRequest
  • HTTP请求方法和状态码
  • RESTful API设计和跨域问题
  1. 框架和库
  • jQuery
  • React、Angular和Vue等主流前端框架
  • lodash和underscore等常用的JavaScript工具库
  1. 测试和调试
  • 单元测试和集成测试
  • 调试技巧和Chrome DevTools使用
  1. 性能优化和网络安全
  • 页面加载优化和渲染性能
  • 垃圾回收机制和内存优化
  • XSS、CSRF和点击劫持等常见安全问题

试题

基础语法

如何判断一个变量类型?这些判断方法的原理是什么?

  • typeof
typeof "hello"         // string
typeof 3.14            // number
typeof NaN             // number
typeof true            // boolean
typeof undefined       // undefined
typeof null            // object
typeof []              // object
typeof {}              // object
typeof function(){}    // function

null/[]/{}都判断为对象了。

  • Object.prototype.toString()
Object.prototype.toString.call("hello") // "[object String]" Object.prototype.toString.call(3.14) // "[object Number]" Object.prototype.toString.call(true) // "[object Boolean]" Object.prototype.toString.call(undefined) // "[object Undefined]" Object.prototype.toString.call(null) // "[object Null]" Object.prototype.toString.call([]) // "[object Array]" Object.prototype.toString.call({}) // "[object Object]" Object.prototype.toString.call(function(){}) // "[object Function]"
  • instanceof
var str = "hello";
console.log(str instanceof String);       // false

var strObj = new String("hello");
console.log(strObj instanceof String);    // true

var num = 3.14;
console.log(num instanceof Number);       // false

var numObj = new Number(3.14);
console.log(numObj instanceof Number);    // true

var bool = true;
console.log(bool instanceof Boolean);     // false

var boolObj = new Boolean(true);
console.log(boolObj instanceof Boolean);  // true

var arr = [];
console.log(arr instanceof Array);        // true

var obj = {};
console.log(obj instanceof Object);       // true

function fn(){}
console.log(fn instanceof Function);      // true

instanceof 运算符只能用于检查对象类型,不能用于原始类型(例如字符串、数值等)的判断。此外,instanceof 运算符还具有原型链的特性,即如果对象的原型链上出现了对应的构造函数,则也会返回 true

Object.prototype.toString()typeof底层是通过判断一个值的内部 [[Class]] 属性的值来确定其类型,底层是通过判断一个值的内部 [[Class]] 属性的值来确定其类型,

推荐用Object.prototype.toString()会更准确。

JS对象如何拷贝

  • 浅拷贝
    • Object.assign
    • 扩展运算符 {...}
  • 深拷贝
    • JSON的序列化反序列化
    • 递归深拷贝
// 使用递归函数进行深拷贝
function deepCopy(obj) {
  // 先判断是不是对象。不是或者是null直接返回
  if (typeof obj !== 'object' || obj === null) {
    return obj;
  }

  // 再兼容下对象和数组的结果集。
  var copy = Array.isArray(obj) ? [] : {};

  // 遍历对象的keys依次深度递归拷贝即可
  Object.keys(obj).forEach(function(key) {
    copy[key] = deepCopy(obj[key]);
  });

  return copy;
}

var obj5 = deepCopy(obj1);
console.log(obj5);    // { a: 1, b: { c: 2 } }
console.log(obj1 === obj5);    // false

什么是闭包,有什么优缺点?

闭包是:能够读取其他函数内部变量的函数。简单理解就是函数中嵌套函数

为什么会产生闭包?

一般函数执行之后内部的变量便会销毁,但是内部函数的作用域链引用了这个变量(外部函数的执行上下文Active Object)。因此这个变量不会销毁。从而形成了闭包。

优点:

  1. 模块化:代码避免全局变量的污染。
  2. 私有化:变量私有化,并且可以外部增删查改。
  3. 生命周期:延长局部变量的生命周期。

缺点:

  1. 内存:占用越来越高。所以一些dom对象用完最好销毁掉。

参考

JavaScript中有哪几种数据类型?它们之间有什么区别?

  • 什么是函数声明?什么是函数表达式?它们之间有什么区别?
  • 如何将字符串转换为数字?

对象和面向对象编程

什么是原型链?如何利用原型链实现继承?

  • 当我们访问一个对象的属性时,如果该对象本身没有该属性,那么就会到它的原型对象上查找,如果还是没有,则会继续沿着原型链往上查找,直至找到Object.prototype为止。
  • 我们可以通过给函数的原型属性添加方法来使得该函数创建的所有实例对象都具有这个方法。利用这个原理,我们可以使用原型链实现继承。
function Animal(name) {
  this.name = name;
}

Animal.prototype.sayName = function() {
  console.log(`My name is ${this.name}.`);
}

function Dog(name, color) {
  Animal.call(this, name);
  this.color = color;
}

Dog.prototype = Object.create(Animal.prototype);

Dog.prototype.constructor = Dog;

Dog.prototype.sayColor = function() {
  console.log(`My color is ${this.color}.`);
}

const dog1 = new Dog('Tom', 'white');
dog1.sayName(); // My name is Tom.
dog1.sayColor(); // My color is white.

说明下:

  • 传参:创建实例对象时,通过显示绑定this的方式向构造函数(父级)传参。
  • Object.create:创建一个新对象,使用现有的对象来作为新创建对象的原型。
  • Dog.prototype.constructor指向Dog本身。这是因为在将原型对象重写时,constructor属性会丢失,我们需要手动设置回来。

参考

  • 如何创建一个自定义对象?

  • 如何遍历一个对象的属性?

  • 如何定义并调用一个构造函数?

异步编程

  • 什么是回调函数?如何解决回调地狱问题?
  • Promise如何使用?如何链式调用?
  • async/await与Promise有什么区别?async函数如何处理异常?

DOM操作

  • 如何获取一个元素的属性值?
  • 如何创建和添加一个新的DOM节点?
  • 如何给元素添加事件监听?
  • 如何实现一个动画效果?

BOM操作

  • 如何获取浏览器窗口大小?
  • 如何判断当前页面是否支持localStorage?
  • 如何实现前端路由?

AJAX和HTTP

  • XMLHttpRequest如何使用?
  • GET和POST请求有什么区别?
  • 如何设置请求头和响应头?
  • 如何处理跨域请求?

框架和库

  • React如何使用?如何实现组件间的通信?
  • Vue如何使用?如何实现双向绑定?
  • jQuery有哪些常用的DOM操作方法?

测试和调试

  • 如何在Chrome DevTools中进行代码调试?
  • 什么是单元测试?如何编写一个简单的单元测试用例?

性能优化和网络安全

  • 如何减少页面加载时间?
  • 什么是垃圾回收机制?如何手动触发垃圾回收?
  • 如何防止网站遭受XSS攻击?

手写编程

实现EventEmitter

type Listener = (...args: any[]) => void;

class EventEmitter {
  private events: Record<string, Listener[]> = {};
  public on(eventName: string, listener: Listener): void {
    if (!this.events[eventName]) {
      this.events[eventName] = [];
    }
    this.events[eventName].push(listener);
  }
  public emit(eventName: string, ...args: any[]): void {
    const listeners = this.events[eventName];
    if (listeners) {
      listeners.forEach(listener => listener(...args));
    }
  }
  public off(eventName: string, listener: Listener): void {
    const listeners = this.events[eventName];
    if (listeners) {
      this.events[eventName] = listeners.filter(l => l !== listener);
    }
  }
  public once(eventName: string, listener: Listener): void {
    const wrapper = (...args: any[]) => {
      listener(...args);
      this.off(eventName, wrapper);
    };
    this.on(eventName, wrapper);
  }
}

export default EventEmitter