核心概念

  • 原型链:JavaScript 的继承机制
  • this 指向:函数调用上下文的决定
  • OOP 三大特性:封装、继承、多态

一、原型与原型链

1.1 基本概念

三个关键属性

  • __proto__:每个对象都有(隐式原型)
  • prototype:每个函数都有(显式原型)
  • constructor:指向构造函数
// 函数拥有 prototype
function Person() {}
console.log(Person.prototype);  // {}
 
// 对象拥有 __proto__
const person = new Person();
console.log(person.__proto__);  // {}
console.log(person.__proto__ === Person.prototype);  // true

1.2 原型链查找机制

属性查找规则

  1. 先查找对象本身的属性
  2. 如果没有,通过 __proto__ 向上查找
  3. 依次向上,直到找到或到达 null
  4. 到达 null 还没找到,返回 undefined
graph TD
    A[实例对象] -->|__proto__| B[构造函数.prototype]
    B -->|__proto__| C[Object.prototype]
    C -->|__proto__| D[null]
function Person() {
  this.name = "张三";
}
 
Person.prototype.age = 18;
 
const person = new Person();
 
console.log(person.name);  // "张三"(来自实例本身)
console.log(person.age);   // 18(来自原型)
console.log(person.toString());  // 来自 Object.prototype
 
// 原型链的终点
console.log(Object.prototype.__proto__);  // null

原型链 vs 作用域链

  • 原型链:查找对象属性
  • 作用域链:查找变量

1.3 原型链继承

// 父构造函数
function Animal(name) {
  this.name = name;
}
 
Animal.prototype.eat = function() {
  console.log(this.name + " 在吃东西");
};
 
// 子构造函数
function Dog(name, breed) {
  Animal.call(this, name);  // 继承属性
  this.breed = breed;
}
 
// 继承方法
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
 
Dog.prototype.bark = function() {
  console.log(this.name + " 在叫");
};
 
const dog = new Dog("旺财", "金毛");
dog.eat();  // "旺财 在吃东西"
dog.bark(); // "旺财 在叫"

二、this 指向详解

2.1 this 的四种绑定规则

规则说明示例
默认绑定独立函数调用,this 指向全局对象(严格模式为 undefinedfn()
隐式绑定作为对象方法调用,this 指向该对象obj.fn()
显式绑定使用 call/apply/bind 改变 thisfn.call(obj)
new 绑定使用 new 调用构造函数,this 指向新实例new Fn()

2.2 默认绑定

function fn() {
  console.log(this);  // window(非严格模式)
}
 
fn();
 
// 严格模式
function fnStrict() {
  "use strict";
  console.log(this);  // undefined
}
 
fnStrict();

2.3 隐式绑定

const person = {
  name: "Alice",
  greet() {
    console.log(`Hello, ${this.name}!`);
  }
};
 
person.greet();  // "Hello, Alice!"(this 指向 person)
 
// 隐式丢失
const fn = person.greet;
fn();  // "Hello, undefined!"(this 指向 window)

隐式丢失 将函数赋值给变量或作为参数传递时,会丢失 this 绑定

2.4 显式绑定

call() - 立即执行

function greet(message) {
  console.log(`${message}, ${this.name}!`);
}
 
const person = { name: "Bob" };
 
greet.call(person, "Hi");  // "Hi, Bob!"

apply() - 数组参数

function greet(greeting, punctuation) {
  console.log(`${greeting}, ${this.name}${punctuation}`);
}
 
const person = { name: "Bob" };
 
greet.apply(person, ["Hello", "!"]);  // "Hello, Bob!"

bind() - 返回新函数

function greet(message) {
  console.log(`${message}, ${this.name}!`);
}
 
const person = { name: "Bob" };
const greetBob = greet.bind(person);
 
greetBob("Hi");  // "Hi, Bob!"

call/apply/bind 对比

方法是否立即执行参数传递
call✅ 是参数列表
apply✅ 是参数数组
bind❌ 否参数列表

2.5 new 绑定

function Person(name) {
  this.name = name;
}
 
const alice = new Person("Alice");
console.log(alice.name);  // "Alice"

new 操作符做了什么

  1. 创建新对象 {}
  2. 新对象的 __proto__ 指向构造函数的 prototype
  3. 将构造函数的 this 指向新对象
  4. 执行构造函数代码
  5. 如果构造函数返回对象,则返回该对象;否则返回新创建的对象

2.6 箭头函数的 this

箭头函数没有 this 箭头函数的 this 继承自外层作用域,无法通过 call/apply/bind 改变

const obj = {
  name: "Alice",
  greet: () => {
    console.log(`Hello, ${this.name}!`);
  }
};
 
obj.greet();  // "Hello, undefined!"(this 继承自外层,可能是 window)

2.7 this 绑定优先级

new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定
// new vs call
function Foo() {
  console.log(this);
}
const obj = {};
Foo.call(obj);  // obj
// new Foo.call(obj);  // TypeError(不能同时使用)
 
// call vs 隐式绑定
const obj1 = { name: "obj1" };
const obj2 = { name: "obj2" };
 
function fn() {
  console.log(this.name);
}
 
obj1.fn = fn;
obj1.fn.call(obj2);  // "obj2"(显式绑定 > 隐式绑定)

三、OOP 三大特性

3.1 封装(Encapsulation)

将数据和操作数据的方法封装在一起

class Person {
  // 私有属性(ES2022)
  #age = 0;
 
  constructor(name, age) {
    this.name = name;
    this.setAge(age);
  }
 
  // 公共方法
  getAge() {
    return this.#age;
  }
 
  setAge(age) {
    if (age > 0 && age < 150) {
      this.#age = age;
    } else {
      console.log("年龄不合法");
    }
  }
}
 
const person = new Person("张三", 25);
person.age;      // undefined(私有属性无法访问)
person.getAge(); // 25

3.2 继承(Inheritance)

子类继承父类的属性和方法

class Animal {
  constructor(name) {
    this.name = name;
  }
 
  eat() {
    console.log(this.name + " 在吃东西");
  }
}
 
class Dog extends Animal {
  constructor(name, breed) {
    super(name);  // 调用父类构造函数
    this.breed = breed;
  }
 
  bark() {
    console.log(this.name + " 在叫");
  }
}
 
const dog = new Dog("旺财", "金毛");
dog.eat();  // 继承自 Animal
dog.bark(); // 自己的方法

3.3 多态(Polymorphism)

相同接口,不同实现

class Animal {
  speak() {
    console.log("动物发出声音");
  }
}
 
class Dog extends Animal {
  speak() {
    console.log("汪汪");
  }
}
 
class Cat extends Animal {
  speak() {
    console.log("喵喵");
  }
}
 
const animals = [new Dog(), new Cat()];
animals.forEach((animal) => animal.speak());
// "汪汪"
// "喵喵"

四、Class 类语法

4.1 类的定义

class Person {
  // 实例属性(ES2022)
  name = "默认值";
 
  // 静态属性
  static species = "人类";
 
  // 构造函数
  constructor(name) {
    this.name = name;
  }
 
  // 实例方法
  sayHello() {
    console.log(`你好,我是${this.name}`);
  }
 
  // 静态方法
  static create(name) {
    return new Person(name);
  }
 
  // Getter
  get info() {
    return `姓名:${this.name}`;
  }
 
  // Setter
  set newName(name) {
    this.name = name;
  }
}
 
const person = new Person("张三");
person.sayHello();  // "你好,我是张三"
Person.create("李四");  // 使用静态方法

4.2 继承

class Animal {
  constructor(name) {
    this.name = name;
  }
 
  speak() {
    console.log(this.name + " 发出声音");
  }
}
 
class Dog extends Animal {
  constructor(name, breed) {
    super(name);  // 必须先调用 super
    this.breed = breed;
  }
 
  speak() {
    super.speak();  // 调用父类方法
    console.log("汪汪");
  }
}
 
const dog = new Dog("旺财", "金毛");
dog.speak();
// "旺财 发出声音"
// "汪汪"

五、最佳实践

建议

  1. 使用 Class 语法而非原型链继承
  2. 使用箭头函数保持 this 指向
  3. 理解原型链,避免属性查找性能问题
  4. 使用私有属性#)而非命名约定(_
  5. 合理使用继承,优先组合而非继承

相关链接