核心概念
- 原型链: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); // true1.2 原型链查找机制
属性查找规则
- 先查找对象本身的属性
- 如果没有,通过
__proto__向上查找- 依次向上,直到找到或到达
null- 到达
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 指向全局对象(严格模式为 undefined) | fn() |
| 隐式绑定 | 作为对象方法调用,this 指向该对象 | obj.fn() |
| 显式绑定 | 使用 call/apply/bind 改变 this | fn.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 操作符做了什么
- 创建新对象
{}- 新对象的
__proto__指向构造函数的prototype- 将构造函数的
this指向新对象- 执行构造函数代码
- 如果构造函数返回对象,则返回该对象;否则返回新创建的对象
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(); // 253.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();
// "旺财 发出声音"
// "汪汪"五、最佳实践
建议
- 使用 Class 语法而非原型链继承
- 使用箭头函数保持
this指向- 理解原型链,避免属性查找性能问题
- 使用私有属性(
#)而非命名约定(_)- 合理使用继承,优先组合而非继承