从零开始捡起知识点(二)原型

原型与原型链

一、用法比较

名词 归属 作用 代码示例
构造函数 函数 创建对象的模板,通过 new 调用 function Person(name) { this.name = name }
prototype(显式原型) 仅函数拥有 存放所有实例共享的属性/方法 Person.prototype.sayHi = function() {}
proto(隐式原型) 所有对象拥有 指向创建该对象的构造函数的 prototype,构成原型链 const p = new Person(); p.__proto__ === Person.prototype

实例的 __proto__ 严格等于其构造函数的 prototype

JavaScript 复制代码
// 代码验证核心规则
function Person(name) {
  this.name = name; // 实例独有属性
}
const p = new Person("Jack");

console.log(p.__proto__ === Person.prototype); // true(核心等式)
console.log(Object.getPrototypeOf(p) === Person.prototype); // ES6推荐写法,结果一致

二、逻辑图

三、为什么要原型?

1. 共享方法,节省内存

反例 不用原型,内存浪费
JavaScript 复制代码
function Person(name) {
  this.name = name;
  // 每个实例都会创建独立的sayHi函数,实例越多内存占用越大
  this.sayHi = function() {
    console.log(`Hi, ${this.name}`);
  };
}

const p1 = new Person("Jack");
const p2 = new Person("Lucy");
console.log(p1.sayHi === p2.sayHi); // false(两个不同的函数实例)
比较(用原型,方法共享)
JavaScript 复制代码
function Person(name) {
  this.name = name;
}
// 所有实例共享同一个sayHi方法
Person.prototype.sayHi = function() {
  console.log(`Hi, ${this.name}`);
};

const p1 = new Person("Jack");
const p2 = new Person("Lucy");
console.log(p1.sayHi === p2.sayHi); // true(共享同一个函数)
p1.sayHi(); // Hi, Jack
p2.sayHi(); // Hi, Lucy

2. 目的:实现继承

ES6的class只是语法糖,底层依然依赖原型链实现继承。

四、原型链

定义

当访问对象的属性/方法时,JS引擎会按「自身 → 原型 → 原型的原型 → ... → Object.prototype → null」的顺序查找,这条查找链路就是原型链

JavaScript 复制代码
function Person(name) {
  this.name = name; // 实例自身属性
}
// 原型对象上的方法
Person.prototype.sayHi = function() {
  console.log(`Hi, ${this.name}`);
};

const p = new Person("Jack");

// 1. 查找自身属性:name → 存在,直接返回
console.log(p.name); // Jack

// 2. 查找方法:sayHi → 自身没有,去__proto__(Person.prototype)找 → 存在
p.sayHi(); // Hi, Jack

// 3. 查找toString方法:自身和Person.prototype都没有 → 去Object.prototype找 → 存在
console.log(p.toString()); // [object Object]

// 4. 查找不存在的属性:abc → 查找到null仍未找到 → 返回undefined
console.log(p.abc); // undefined

五、constructor

constructor是原型对象(prototype)上的默认属性,默认指向创建该原型对象的构造函数。

JavaScript 复制代码
function Person() {}
console.log(Person.prototype.constructor === Person); // true
const p = new Person();
// p自身无constructor,通过原型链查找Person.prototype.constructor
console.log(p.constructor === Person); // true

关键场景:继承时修正constructor指向

JavaScript 复制代码
// 父构造函数
function Parent() {
  this.type = "parent";
}
// 子构造函数
function Child() {}

// 实现原型继承:子类原型指向父类实例
Child.prototype = new Parent();
// 此时constructor指向被篡改,需要修正
console.log(Child.prototype.constructor === Parent); // true(错误)
Child.prototype.constructor = Child; // 手动修正
console.log(Child.prototype.constructor === Child); // true(正确)

const child = new Child();
console.log(child.constructor === Child); // true

六、应用场景

1.实现方法共享

JavaScript 复制代码
// 构造函数
function User(username, age) {
  this.username = username;
  this.age = age;
}

// 原型上定义共享方法
User.prototype.login = function() {
  console.log(`${this.username} 登录成功`);
};
User.prototype.getInfo = function() {
  return { username: this.username, age: this.age };
};

// 实例化
const user1 = new User("zhangsan", 20);
const user2 = new User("lisi", 22);

user1.login(); // zhangsan 登录成功
user2.login(); // lisi 登录成功
console.log(user1.getInfo()); // { username: 'zhangsan', age: 20 }
console.log(user1.login === user2.login); // true(方法共享)

2. 实现原型继承

JavaScript 复制代码
// 父类:动物
function Animal(name) {
  this.name = name;
  this.eat = function() { // 实例独有方法(非共享)
    console.log(`${this.name} 吃东西`);
  };
}
// 父类原型方法(共享)
Animal.prototype.run = 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; // 修正constructor

// 子类原型方法
Dog.prototype.bark = function() {
  console.log(`${this.name} 汪汪叫`);
};

// 测试
const erha = new Dog("二哈", "哈士奇");
erha.eat(); // 二哈 吃东西(继承父类实例方法)
erha.run(); // 二哈 跑起来(继承父类原型方法)
erha.bark(); // 二哈 汪汪叫(子类原型方法)
console.log(erha.breed); // 哈士奇(子类独有属性)

3.扩展内置对象方法

JavaScript 复制代码
// 给Array扩展去重方法
if (!Array.prototype.unique) { // 避免重复定义
  Array.prototype.unique = function() {
    return [...new Set(this)];
  };
}

// 给String扩展去除空格方法
if (!String.prototype.trimAll) {
  String.prototype.trimAll = function() {
    return this.replace(/\s+/g, "");
  };
}

// 测试
const arr = [1, 2, 2, 3, 3, 3];
console.log(arr.unique()); // [1, 2, 3]

const str = "  he llo  world  ";
console.log(str.trimAll()); // helloworld

4. 手写new关键字

JavaScript 复制代码
/**
 * 手写new关键字
 * @param {Function} fn 构造函数
 * @param  {...any} args 构造函数参数
 * @returns {Object} 实例对象
 */
function myNew(fn, ...args) {
  // 1. 创建空对象
  const obj = {};
  // 2. 绑定原型:obj.__proto__ 指向构造函数的prototype
  obj.__proto__ = fn.prototype;
  // 3. 执行构造函数,绑定this到obj
  const result = fn.apply(obj, args);
  // 4. 如果构造函数返回对象,返回该对象;否则返回obj
  return result instanceof Object ? result : obj;
}

// 测试
function Person(name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.say = function() {
  console.log(`我是${this.name},${this.age}岁`);
};

const p = myNew(Person, "Tom", 18);
console.log(p.name); // Tom
console.log(p.age); // 18
p.say(); // 我是Tom,18岁
console.log(p.__proto__ === Person.prototype); // true

5. 判断数据类型

JavaScript 复制代码
/**
 * 精准判断数据类型
 * @param {any} data 要判断的数据
 * @returns {string} 类型字符串(如"Array"、"Object"、"Number")
 */
function getType(data) {
  return Object.prototype.toString.call(data).slice(8, -1);
}

// 测试
console.log(getType([])); // Array
console.log(getType({})); // Object
console.log(getType(123)); // Number
console.log(getType("abc")); // String
console.log(getType(null)); // Null
console.log(getType(undefined)); // Undefined
console.log(getType(() => {})); // Function

七、经典面试题合集(附答案+解析)

1. prototype 和 proto 的区别?

  • prototype:仅函数拥有,是「显式原型」,用于存放所有实例共享的属性/方法;

  • proto:所有对象(包括函数)都拥有,是「隐式原型」,指向创建该对象的构造函数的prototype,是原型链查找的核心链路。

2. 原型链的终点是什么?为什么?

  • 终点是 null

  • 原因:Object.prototype.__proto__ === nullObject.prototype 是所有对象的顶级原型,其隐式原型指向null,代表原型链查找终止。

3. 如何判断一个属性是对象自身的,还是原型上的?

使用 hasOwnProperty() 方法(来自Object.prototype):

JavaScript 复制代码
function Person() {
  this.name = "Jack";
}
Person.prototype.age = 18;

const p = new Person();
console.log(p.hasOwnProperty("name")); // true(自身属性)
console.log(p.hasOwnProperty("age")); // false(原型属性)

4. ES6的class和原型的关系?

ES6的class是原型链的「语法糖」,底层依然基于原型实现:

JavaScript 复制代码
// ES6 class写法
class Person {
  constructor(name) {
    this.name = name;
  }
  sayHi() {
    console.log(`Hi, ${this.name}`);
  }
}

// 等价于ES5原型写法
function Person(name) {
  this.name = name;
}
Person.prototype.sayHi = function() {
  console.log(`Hi, ${this.name}`);
};

5. 执行 Object.prototype.toString.call([]) 为什么返回 "[object Array]"?

  • toString 是Object.prototype上的方法,默认返回对象的类型信息;

  • 数组自身重写了toString方法(返回数组元素拼接字符串),通过call改变this指向,调用的是Object.prototype上的原始toString方法,因此能精准返回类型。

八、总结

  1. 核心等式 :实例.proto === 构造函数.prototype(原型的核心关联);

  2. 原型链规则:属性/方法查找遵循「自身 → 原型 → 原型的原型 → ... → Object.prototype → null」;

  3. 核心价值:原型实现属性/方法共享(节省内存),原型链实现JS继承(面向对象的底层);

  4. 关键方法hasOwnProperty()(判断自身属性)、Object.getPrototypeOf()(ES6获取隐式原型)、Object.create()(实现原型继承)。

相关推荐
阿星AI工作室1 小时前
给openclaw龙虾造了间像素办公室!实时看它写代码、摸鱼、修bug、写日报,太可爱了吧!
前端·人工智能·设计模式
Kayshen1 小时前
我用纯前端逆向了 Figma 的二进制文件格式,实现了 .fig 文件的完整解析和导入
前端·agent·ai编程
wuhen_n1 小时前
模板编译三阶段:parse-transform-generate
前端·javascript·vue.js
椰子皮啊1 小时前
音视频会议 ASR 实战:概率性识别不准问题定位与解决
前端
小码哥_常1 小时前
Kotlin扩展:为代码注入新活力
前端
小码哥_常1 小时前
Kotlin函数进阶:解锁可变参数与局部函数的奇妙用法
前端
Wect1 小时前
浏览器缓存机制
前端·面试·浏览器
滕青山1 小时前
正则表达式测试 在线工具核心JS实现
前端·javascript·vue.js
不可能的是1 小时前
前端图片懒加载方案全解析
前端·javascript
不可能的是1 小时前
前端 SSE 流式请求三种实现方案全解析
前端·http