原生 JavaScript 知识体系

JavaScript 基础概念、原型、闭包、异步等常见考点

问题

谈谈你对原生 JavaScript 的了解程度。

解答

数据类型

JavaScript 有 8 种数据类型:

// 原始类型(7种)
const str = 'hello';           // string
const num = 42;                // number
const bool = true;             // boolean
const n = null;                // null
const u = undefined;           // undefined
const sym = Symbol('id');      // symbol
const big = 9007199254740991n; // bigint

// 引用类型(1种)
const obj = { name: 'Tom' };   // object(包括数组、函数、日期等)

// 类型判断
typeof str;        // 'string'
typeof null;       // 'object'(历史遗留 bug)
Array.isArray([]); // true
obj instanceof Object; // true

// 准确判断类型
Object.prototype.toString.call([]); // '[object Array]'
Object.prototype.toString.call(null); // '[object Null]'

作用域与闭包

// 作用域链:内部函数可以访问外部变量
function outer() {
  const x = 10;
  
  function inner() {
    const y = 20;
    console.log(x + y); // 可以访问外部的 x
  }
  
  return inner;
}

// 闭包:函数记住并访问其词法作用域
function createCounter() {
  let count = 0; // 私有变量
  
  return {
    increment() { return ++count; },
    decrement() { return --count; },
    getCount() { return count; }
  };
}

const counter = createCounter();
counter.increment(); // 1
counter.increment(); // 2
counter.getCount();  // 2

原型与继承

// 原型链
function Person(name) {
  this.name = name;
}

Person.prototype.sayHello = function() {
  console.log(`Hello, I'm ${this.name}`);
};

const tom = new Person('Tom');
tom.sayHello(); // Hello, I'm Tom

// 原型链查找
tom.hasOwnProperty('name');     // true(自身属性)
tom.hasOwnProperty('sayHello'); // false(原型上的属性)

// ES6 class 语法
class Animal {
  constructor(name) {
    this.name = name;
  }
  
  speak() {
    console.log(`${this.name} makes a sound`);
  }
}

class Dog extends Animal {
  speak() {
    console.log(`${this.name} barks`);
  }
}

const dog = new Dog('Rex');
dog.speak(); // Rex barks

this 指向

const obj = {
  name: 'Tom',
  
  // 普通函数:this 取决于调用方式
  greet() {
    console.log(this.name);
  },
  
  // 箭头函数:this 继承外层作用域
  greetArrow: () => {
    console.log(this.name); // undefined(指向全局)
  },
  
  // 延迟调用中的 this
  delayGreet() {
    // 错误:this 丢失
    setTimeout(function() {
      console.log(this.name); // undefined
    }, 100);
    
    // 正确:箭头函数保持 this
    setTimeout(() => {
      console.log(this.name); // Tom
    }, 100);
  }
};

// 显式绑定 this
const greet = obj.greet;
greet.call(obj);   // Tom
greet.apply(obj);  // Tom
greet.bind(obj)(); // Tom

异步编程

// 1. 回调函数
function fetchData(callback) {
  setTimeout(() => {
    callback('data');
  }, 1000);
}

// 2. Promise
function fetchDataPromise() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('data');
    }, 1000);
  });
}

fetchDataPromise()
  .then(data => console.log(data))
  .catch(err => console.error(err));

// 3. async/await
async function getData() {
  try {
    const data = await fetchDataPromise();
    console.log(data);
  } catch (err) {
    console.error(err);
  }
}

// 并行执行
async function parallel() {
  const [a, b] = await Promise.all([
    fetchDataPromise(),
    fetchDataPromise()
  ]);
  return [a, b];
}

事件循环

console.log('1'); // 同步

setTimeout(() => {
  console.log('2'); // 宏任务
}, 0);

Promise.resolve().then(() => {
  console.log('3'); // 微任务
});

console.log('4'); // 同步

// 输出顺序:1 -> 4 -> 3 -> 2
// 执行顺序:同步代码 -> 微任务 -> 宏任务

ES6+ 常用特性

// 解构赋值
const { name, age = 18 } = { name: 'Tom' };
const [first, ...rest] = [1, 2, 3];

// 展开运算符
const merged = { ...obj1, ...obj2 };
const combined = [...arr1, ...arr2];

// 可选链和空值合并
const city = user?.address?.city ?? 'Unknown';

// Map 和 Set
const map = new Map([['key', 'value']]);
const set = new Set([1, 2, 2, 3]); // {1, 2, 3}

// Proxy
const proxy = new Proxy(target, {
  get(obj, prop) {
    return prop in obj ? obj[prop] : 'default';
  },
  set(obj, prop, value) {
    obj[prop] = value;
    return true;
  }
});

关键点

  • 类型判断typeofnull 返回 object,用 Object.prototype.toString.call() 更准确
  • 闭包:函数可以记住创建时的词法作用域,常用于数据私有化
  • 原型链:对象通过 __proto__ 链接到原型,属性查找沿链向上
  • this 绑定:普通函数看调用方式,箭头函数看定义位置
  • 事件循环:同步代码 → 微任务(Promise)→ 宏任务(setTimeout)