Skip to content

JavaScript 基础

一、数据类型

1. JavaScript 有哪些数据类型?

JavaScript 有以下数据类型:

原始类型(Primitive Types):

  • number - 数字
  • string - 字符串
  • boolean - 布尔值
  • undefined - 未定义
  • null - 空值
  • symbol - 符号(ES6)
  • bigint - 大整数(ES2020)

引用类型(Reference Types):

  • object - 对象(包括数组、函数、日期等)

2. null 和 undefined 的区别?

  • undefined:表示变量声明了但未赋值,或者函数没有返回值
  • null:表示空值,通常用于明确表示变量为空
javascript
let a;           // undefined
let b = null;    // null

typeof undefined  // "undefined"
typeof null       // "object" (历史遗留问题)

3. 类型检测的方式有哪些?

方式一:typeof

javascript
typeof 123           // "number"
typeof "hello"       // "string"
typeof true          // "boolean"
typeof undefined     // "undefined"
typeof null          // "object" (bug)
typeof []            // "object"
typeof {}            // "object"
typeof function(){}  // "function"

方式二:instanceof

javascript
[] instanceof Array        // true
{} instanceof Object       // true
new Date() instanceof Date // true

方式三:Object.prototype.toString

javascript
Object.prototype.toString.call(123)        // "[object Number]"
Object.prototype.toString.call([])         // "[object Array]"
Object.prototype.toString.call({})         // "[object Object]"
Object.prototype.toString.call(null)       // "[object Null]"

方式四:Array.isArray()

javascript
Array.isArray([])  // true
Array.isArray({})  // false

二、作用域和闭包

1. 什么是作用域?

作用域(Scope)决定了变量和函数的可访问性。

作用域类型:

  • 全局作用域:在整个程序中都可访问
  • 函数作用域:只在函数内部可访问
  • 块作用域:使用 letconst 声明的变量具有块作用域
javascript
// 全局作用域
var globalVar = "global";

function test() {
  // 函数作用域
  var functionVar = "function";
  
  if (true) {
    // 块作用域
    let blockVar = "block";
    const blockConst = "block";
  }
}

2. 什么是闭包?

闭包(Closure)是指函数可以访问其外部作用域中的变量,即使外部函数已经执行完毕。

javascript
function outer() {
  let count = 0;
  
  function inner() {
    count++;
    console.log(count);
  }
  
  return inner;
}

const counter = outer();
counter(); // 1
counter(); // 2
counter(); // 3

闭包的应用:

  • 数据私有化
  • 模块化
  • 函数柯里化

3. var、let、const 的区别?

特性varletconst
作用域函数作用域块作用域块作用域
变量提升是(TDZ)是(TDZ)
重复声明允许不允许不允许
修改值可以可以不可以
javascript
// var
console.log(a); // undefined
var a = 1;

// let/const
console.log(b); // ReferenceError
let b = 2;

三、this 指向

1. this 的指向规则

普通函数:

  • 默认指向全局对象(浏览器中是 window,严格模式下是 undefined
  • 作为对象方法调用时指向该对象
  • 使用 call/apply/bind 可以改变指向
javascript
// 默认绑定
function foo() {
  console.log(this);
}
foo(); // window (非严格模式)

// 隐式绑定
const obj = {
  name: "obj",
  foo: function() {
    console.log(this.name);
  }
};
obj.foo(); // "obj"

// 显式绑定
foo.call({name: "call"}); // {name: "call"}

箭头函数:

  • 箭头函数没有自己的 this,会继承外层作用域的 this
  • 无法通过 call/apply/bind 改变 this 指向
javascript
const obj = {
  name: "obj",
  foo: function() {
    // 普通函数
    console.log(this.name); // "obj"
    
    const arrow = () => {
      // 箭头函数继承外层 this
      console.log(this.name); // "obj"
    };
    arrow();
  }
};

2. call、apply、bind 的区别?

相同点: 都可以改变函数的 this 指向

不同点:

  • call:立即执行,参数逐个传递
  • apply:立即执行,参数以数组形式传递
  • bind:返回新函数,不立即执行
javascript
function greet(greeting, punctuation) {
  console.log(greeting + this.name + punctuation);
}

const obj = { name: "Alice" };

// call
greet.call(obj, "Hello, ", "!");        // "Hello, Alice!"

// apply
greet.apply(obj, ["Hi, ", "."]);        // "Hi, Alice."

// bind
const bound = greet.bind(obj, "Hey, ");
bound("!!");                             // "Hey, Alice!!"

四、原型和继承

1. 原型链是什么?

每个对象都有一个指向其原型对象的内部链接,通过这个链接可以访问原型对象的属性和方法。多个对象通过原型链接形成原型链。

javascript
function Person(name) {
  this.name = name;
}

Person.prototype.sayHello = function() {
  console.log("Hello, " + this.name);
};

const person = new Person("Alice");
person.sayHello(); // "Hello, Alice"

// 原型链查找:person -> Person.prototype -> Object.prototype -> null

2. 实现继承的方式有哪些?

方式一:原型链继承

javascript
function Parent() {
  this.name = "Parent";
}

Parent.prototype.sayHello = function() {
  console.log("Hello from " + this.name);
};

function Child() {}

Child.prototype = new Parent();
const child = new Child();

方式二:构造函数继承

javascript
function Parent(name) {
  this.name = name;
}

function Child(name, age) {
  Parent.call(this, name);
  this.age = age;
}

方式三:组合继承

javascript
function Parent(name) {
  this.name = name;
}

Parent.prototype.sayHello = function() {
  console.log("Hello from " + this.name);
};

function Child(name, age) {
  Parent.call(this, name);  // 继承属性
  this.age = age;
}

Child.prototype = new Parent();  // 继承方法
Child.prototype.constructor = Child;

方式四:ES6 Class 继承

javascript
class Parent {
  constructor(name) {
    this.name = name;
  }
  
  sayHello() {
    console.log("Hello from " + this.name);
  }
}

class Child extends Parent {
  constructor(name, age) {
    super(name);
    this.age = age;
  }
}

五、异步编程

1. Promise 的原理和使用

Promise 的三种状态:

  • pending(等待中)
  • fulfilled(已成功)
  • rejected(已失败)
javascript
const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("成功");
    // reject("失败");
  }, 1000);
});

promise
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error(error);
  });

Promise 的常用方法:

  • Promise.all():所有 Promise 都成功时返回结果数组
  • Promise.race():返回最先完成的 Promise
  • Promise.allSettled():等待所有 Promise 完成(无论成功或失败)

2. async/await 的使用

async/await 是 Promise 的语法糖,让异步代码看起来像同步代码。

javascript
async function fetchData() {
  try {
    const response = await fetch('/api/data');
    const data = await response.json();
    return data;
  } catch (error) {
    console.error(error);
  }
}

3. 事件循环(Event Loop)

JavaScript 是单线程的,通过事件循环机制处理异步任务。

执行顺序:

  1. 同步代码
  2. 微任务(Promise.then、queueMicrotask)
  3. 宏任务(setTimeout、setInterval、I/O)
javascript
console.log(1);

setTimeout(() => {
  console.log(2);
}, 0);

Promise.resolve().then(() => {
  console.log(3);
});

console.log(4);

// 输出:1, 4, 3, 2

六、数组方法

1. 常用的数组方法

遍历方法:

  • forEach():遍历数组,不返回新数组
  • map():遍历数组,返回新数组
  • filter():过滤数组,返回满足条件的元素
  • reduce():累积计算,返回一个值
javascript
const arr = [1, 2, 3, 4, 5];

// map
const doubled = arr.map(x => x * 2);  // [2, 4, 6, 8, 10]

// filter
const evens = arr.filter(x => x % 2 === 0);  // [2, 4]

// reduce
const sum = arr.reduce((acc, x) => acc + x, 0);  // 15

查找方法:

  • find():查找第一个满足条件的元素
  • findIndex():查找第一个满足条件的元素索引
  • some():判断是否有元素满足条件
  • every():判断是否所有元素都满足条件
javascript
const users = [
  { id: 1, name: "Alice" },
  { id: 2, name: "Bob" }
];

const user = users.find(u => u.id === 2);  // { id: 2, name: "Bob" }
const hasBob = users.some(u => u.name === "Bob");  // true

七、ES6+ 新特性

1. 解构赋值

javascript
// 数组解构
const [a, b, c] = [1, 2, 3];

// 对象解构
const { name, age } = { name: "Alice", age: 20 };

// 函数参数解构
function greet({ name, age }) {
  console.log(`${name} is ${age} years old`);
}

2. 展开运算符

javascript
// 数组展开
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5];  // [1, 2, 3, 4, 5]

// 对象展开
const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3 };  // { a: 1, b: 2, c: 3 }

// 函数参数展开
function sum(...args) {
  return args.reduce((a, b) => a + b, 0);
}

3. 模板字符串

javascript
const name = "Alice";
const age = 20;

const message = `Hello, my name is ${name} and I'm ${age} years old.`;

八、常见面试题

1. 深拷贝和浅拷贝的区别?

浅拷贝:只复制对象的第一层属性

javascript
const obj = { a: 1, b: { c: 2 } };
const shallow = Object.assign({}, obj);
shallow.b.c = 3;
console.log(obj.b.c);  // 3 (原对象也被修改)

深拷贝:完全复制对象的所有层级

javascript
// 使用 JSON(有局限性,不能处理函数、undefined 等)
const deep1 = JSON.parse(JSON.stringify(obj));

// 递归实现
function deepClone(obj) {
  if (obj === null || typeof obj !== 'object') return obj;
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof Array) return obj.map(item => deepClone(item));
  if (typeof obj === 'object') {
    const cloned = {};
    for (let key in obj) {
      if (obj.hasOwnProperty(key)) {
        cloned[key] = deepClone(obj[key]);
      }
    }
    return cloned;
  }
}

2. 防抖和节流的区别?

防抖(Debounce):在事件被触发 n 秒后再执行回调,如果在这 n 秒内又被触发,则重新计时

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

节流(Throttle):规定在一个单位时间内,只能触发一次函数

javascript
function throttle(func, delay) {
  let lastTime = 0;
  return function(...args) {
    const now = Date.now();
    if (now - lastTime >= delay) {
      func.apply(this, args);
      lastTime = now;
    }
  };
}

3. 数组去重的方法?

javascript
const arr = [1, 2, 2, 3, 3, 4];

// 方法1:Set
const unique1 = [...new Set(arr)];

// 方法2:filter + indexOf
const unique2 = arr.filter((item, index) => arr.indexOf(item) === index);

// 方法3:reduce
const unique3 = arr.reduce((acc, item) => {
  if (!acc.includes(item)) acc.push(item);
  return acc;
}, []);

基于 VitePress 构建 | Copyright © 2026-present