JavaScript原型链终极解析:彻底搞懂prototype和__proto__的区别

93 阅读5分钟

JavaScript原型链终极解析:彻底搞懂prototype和__proto__的区别 你被JavaScript的prototype和__proto__搞得头晕脑胀吗?这两个概念确实让很多开发者困惑不已。今天我们用最直白的方式,彻底搞清楚它们的区别和关系,让你再也不会在面试中栽跟头。

先记住一个核心区别 在深入之前,先记住这个最重要的区别:

prototype - 只有函数才有,是人为设定的属性 proto - 所有对象都有,用来实现继承关系 这个区别是理解整个原型链的关键。很多人混淆这两个概念,就是因为没有搞清楚这个基本点。

JavaScript的"创世神话" 为了更好理解原型链,我们可以把JavaScript的对象体系想象成一个"神话世界"。

第一神:Object.prototype 在JavaScript的世界里,有一个万物起源,就是Object.prototype。它是所有对象的最终祖先。

// Object.prototype是万物的尽头 console.log(Object.prototype.proto); // null AI写代码 javascript 运行 1 2 这个null就代表虚无,再往上就没有了。Object.prototype就是继承链的终点。

第二神:Function.prototype 接下来诞生了第二个重要角色:Function.prototype。它继承自Object.prototype:

console.log(Function.prototype.proto === Object.prototype); // true AI写代码 javascript 运行 1 有个容易混淆的点:Function.prototype本身也是个函数,但它是个特殊的函数。它不管你传什么参数,都返回undefined,而且不能用new调用。

函数是"一等公民"的真正含义 经常听到"函数在JavaScript中是一等公民",这到底什么意思?

其实就是说,所有的函数(包括Function构造函数本身)都继承自Function.prototype:

// 所有函数的__proto__都指向Function.prototype console.log(Object.proto === Function.prototype); // true console.log(Function.proto === Function.prototype); // true console.log(String.proto === Function.prototype); // true console.log(Number.proto === Function.prototype); // true AI写代码 javascript 运行 1 2 3 4 5 连Function自己都继承自Function.prototype,这看起来有点奇怪,但确实是这样设计的。

用实例来验证理解 让我们通过几个具体例子来验证我们的理解:

例子1:Object instanceof Object 这个表达式为什么是true?

console.log(Object instanceof Object); // true AI写代码 javascript 运行 1 分析过程:

Object是个函数,所以Object.proto === Function.prototype Function.prototype.proto === Object.prototype instanceof会沿着__proto__链查找,最终找到了Object.prototype 所以结果是true 例子2:Function instanceof Function console.log(Function instanceof Function); // true AI写代码 javascript 运行 1 分析过程:

Function是个函数,所以Function.proto === Function.prototype instanceof在第一步就找到了Function.prototype 所以结果是true 例子3:自定义函数 function MyFunction() {} console.log(MyFunction instanceof Function); // true console.log(MyFunction instanceof Object); // true AI写代码 javascript 运行 1 2 3 分析过程:

MyFunction是函数,MyFunction.proto === Function.prototype Function.prototype.proto === Object.prototype 所以MyFunction既是Function的实例,也是Object的实例 构造函数的prototype属性 当我们创建函数时,JavaScript会自动给它添加一个prototype属性:

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

// Person.prototype是人为设定的 Person.prototype.sayHello = function() { console.log('Hello, I am ' + this.name); };

const person1 = new Person('张三');

// person1的__proto__指向Person.prototype console.log(person1.proto === Person.prototype); // true AI写代码 javascript 运行

1 2 3 4 5 6 7 8 9 10 11 12 13 这里的关键理解:

Person.prototype是我们人为设定的,用来给Person的实例添加共享方法 person1.__proto__是自动设置的,指向构造函数的prototype 原型链查找机制 当我们访问对象的属性时,JavaScript会按照这个顺序查找:

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

Person.prototype.species = 'human'; Object.prototype.planet = 'earth';

const person = new Person('李四');

// 查找顺序演示 console.log(person.name); // 直接在person对象上找到 console.log(person.species); // 在Person.prototype上找到
console.log(person.planet); // 在Object.prototype上找到 console.log(person.nothing); // 都找不到,返回undefined AI写代码 javascript 运行

1 2 3 4 5 6 7 8 9 10 11 12 13 14 查找路径:

person自身属性 person.proto (即Person.prototype) person.proto.proto (即Object.prototype) person.proto.proto.proto (即null) 常见误区和陷阱 误区1:混淆prototype和__proto__ function Person() {} const person = new Person();

// 错误理解 console.log(person.prototype); // undefined,实例没有prototype属性

// 正确理解
console.log(person.proto === Person.prototype); // true AI写代码 javascript 运行 1 2 3 4 5 6 7 8 误区2:直接修改__proto__ // 不推荐的做法 const obj = {}; obj.proto = Person.prototype;

// 推荐的做法 const obj = Object.create(Person.prototype); // 或者 Object.setPrototypeOf(obj, Person.prototype); AI写代码 javascript 运行 1 2 3 4 5 6 7 8 误区3:认为所有对象都有prototype属性 const obj = {}; console.log(obj.prototype); // undefined,普通对象没有prototype

function func() {} console.log(func.prototype); // {},只有函数才有prototype AI写代码 javascript 运行 1 2 3 4 5 实际应用场景 继承的实现 function Animal(name) { this.name = name; }

Animal.prototype.speak = function() { console.log(this.name + ' makes a sound'); };

function Dog(name) { Animal.call(this, name); }

// 实现继承 Dog.prototype = Object.create(Animal.prototype); Dog.prototype.constructor = Dog;

Dog.prototype.bark = function() { console.log(this.name + ' barks'); };

const dog = new Dog('旺财'); dog.speak(); // 旺财 makes a sound dog.bark(); // 旺财 barks AI写代码 javascript 运行

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 判断对象类型 function isArray(obj) { return Object.prototype.toString.call(obj) === '[object Array]'; }

function isFunction(obj) { return typeof obj === 'function'; }

// 更现代的方法 console.log(Array.isArray([])); // true console.log(Array.isArray({})); // false AI写代码 javascript 运行

1 2 3 4 5 6 7 8 9 10 11 扩展内置对象(谨慎使用) // 给所有数组添加自定义方法 Array.prototype.last = function() { return this[this.length - 1]; };

const arr = [1, 2, 3, 4]; console.log(arr.last()); // 4

// 注意:修改内置对象原型在生产环境中要谨慎 AI写代码 javascript 运行 1 2 3 4 5 6 7 8 9 现代JavaScript的替代方案 使用Class语法(ES6+) class Animal { constructor(name) { this.name = name; }

speak() {
    console.log(`${this.name} makes a sound`);
}

}

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

const dog = new Dog('旺财'); dog.speak(); // 旺财 makes a sound dog.bark(); // 旺财 barks AI写代码 javascript 运行

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 虽然class语法看起来更清爽,但底层实现还是基于原型链。

使用Object.create() const animalMethods = { speak() { console.log(${this.name} makes a sound); } };

function createAnimal(name) { const animal = Object.create(animalMethods); animal.name = name; return animal; }

const animal = createAnimal('小猫'); animal.speak(); // 小猫 makes a sound AI写代码 javascript 运行

1 2 3 4 5 6 7 8 9 10 11 12 13 14 面试常考问题 问题1:解释原型链 答案要点:

每个对象都有__proto__属性,指向其构造函数的prototype 原型链是通过__proto__连接起来的链条 属性查找会沿着原型链向上查找,直到找到或到达null 问题2:new操作符做了什么 function myNew(Constructor, ...args) { // 1. 创建新对象 const obj = {};

// 2. 设置原型链
Object.setPrototypeOf(obj, Constructor.prototype);

// 3. 绑定this并执行构造函数
const result = Constructor.apply(obj, args);

// 4. 返回对象
return (typeof result === 'object' && result !== null) ? result : obj;

} AI写代码 javascript 运行

1 2 3 4 5 6 7 8 9 10 11 12 13 问题3:instanceof的实现原理 function myInstanceof(left, right) { let leftProto = Object.getPrototypeOf(left); const rightPrototype = right.prototype;

while (leftProto !== null) {
    if (leftProto === rightPrototype) {
        return true;
    }
    leftProto = Object.getPrototypeOf(leftProto);
}

return false;

} AI写代码 javascript 运行

1 2 3 4 5 6 7 8 9 10 11 12 13 性能和最佳实践 性能考虑 避免深层原型链 - 查找属性时会影响性能 缓存属性访问 - 频繁访问的属性可以缓存到局部变量 使用hasOwnProperty - 避免查找原型链上的属性 const obj = { name: '张三' };

// 好的做法 if (obj.hasOwnProperty('name')) { console.log(obj.name); }

// 更安全的做法 if (Object.prototype.hasOwnProperty.call(obj, 'name')) { console.log(obj.name); } AI写代码 javascript 运行

1 2 3 4 5 6 7 8 9 10 11 最佳实践 不要修改内置对象的原型 - 可能与其他代码冲突 使用Object.create(null)创建纯净对象 - 没有原型链的对象 优先使用组合而非继承 - 现代JavaScript推荐的模式 // 纯净对象,没有原型链 const pureObj = Object.create(null); pureObj.name = '张三'; console.log(pureObj.toString); // undefined

// 组合模式示例 function createLogger(config) { return { log(message) { console.log([${config.level}] ${message}); } }; } AI写代码 javascript 运行

1 2 3 4 5 6 7 8 9 10 11 12 13 调试技巧 当你需要调试原型链相关问题时,这些方法很有用:

function debugPrototypeChain(obj) { let current = obj; let depth = 0;

while (current !== null) {
    console.log(`Level ${depth}:`, current.constructor.name);
    current = Object.getPrototypeOf(current);
    depth++;
    
    if (depth > 10) { // 防止无限循环
        console.log('Chain too deep, stopping...');
        break;
    }
}

}

function Person() {} const person = new Person(); debugPrototypeChain(person); // Level 0: Person // Level 1: Function
// Level 2: Object AI写代码 javascript 运行

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 理解prototype和__proto__的关系是掌握JavaScript面向对象编程的关键。虽然现在有了class语法和其他现代特性,但原型链仍然是JavaScript的核心机制。掌握了这些概念,你就能更好地理解JavaScript的工作原理,写出更优雅的代码。

你在学习原型链的过程中遇到过什么困惑?或者有什么好的记忆方法?欢迎在评论区分享。

觉得这篇文章对你有帮助的话,记得点赞收藏,我会继续分享更多JavaScript深度技术内容 ————————————————

                        版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                    

原文链接:blog.csdn.net/2301_809349…