JavaScript 原型与原型链
原型是 JavaScript 最核心的概念之一,理解原型是掌握 JavaScript 面向对象编程的关键。
原型基础
prototype 属性
每个函数(除箭头函数)都有一个 prototype 属性,它是一个对象:
javascriptfunction Person(name, age) { this.name = name; this.age = age; } console.log(Person.prototype); // { constructor: Person }
constructor 属性
prototype 对象上有一个 constructor 属性,指向构造函数本身:
javascriptfunction Person(name, age) { this.name = name; this.age = age; } console.log(Person.prototype.constructor === Person); // true
proto 与原型对象
对象的隐式原型
每个对象都有一个 __proto__ 属性(部分环境为 [[Prototype]]),指向其构造函数的 prototype:
javascriptfunction Person(name) { this.name = name; } const person = new Person('张三'); console.log(person.__proto__ === Person.prototype); // true
原型链查找
访问对象的属性时,会沿着原型链向上查找:
javascriptfunction Person(name) { this.name = name; } Person.prototype.greet = function() { return `你好,我是${this.name}`; }; const person = new Person('张三'); console.log(person.name); // '张三'(自有属性) console.log(person.greet()); // '你好,我是张三'(原型链查找) console.log(person.toString()); // '[object Object]'(Object.prototype)
原型链
原型链的结构
person 实例
│
├─ name: '张三'
│
└─ __proto__ ──────────► Person.prototype
│
├─ constructor: Person
│
├─ greet: function
│
└─ __proto__ ──────► Object.prototype
│
├─ constructor: Object
│
├─ toString: function
│
├─ hasOwnProperty: function
│
└─ __proto__ ──────► null(原型链终点)
原型链的尽头
Object.prototype 的 __proto__ 为 null,这就是原型链的尽头:
javascriptconsole.log(Object.prototype.__proto__); // null
属性遮蔽
原型链上的属性会被自有属性遮蔽:
javascriptfunction Person() {} Person.prototype.name = '原型名称'; const person = new Person(); console.log(person.name); // '原型名称' person.name = '自有名称'; console.log(person.name); // '自有名称'(遮蔽了原型属性) console.log(person.__proto__.name); // '原型名称'(原型属性未被修改)
基于原型的继承
原型链继承
javascriptfunction Animal(name) { this.name = name; } Animal.prototype.eat = function() { return `${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() { return `${this.name} 正在叫`; }; const dog = new Dog('旺财', '金毛'); console.log(dog.eat()); // '旺财 正在吃东西' console.log(dog.bark()); // '旺财 正在叫'
ES6 class 与原型
ES6 的 class 语法只是原型链继承的语法糖:
javascriptclass Animal { constructor(name) { this.name = name; } eat() { return `${this.name} 正在吃东西`; } } class Dog extends Animal { constructor(name, breed) { super(name); // 调用父类构造函数 this.breed = breed; } bark() { return `${this.name} 正在叫`; } } const dog = new Dog('旺财', '金毛'); console.log(dog instanceof Animal); // true console.log(dog instanceof Dog); // true
原型链与属性操作
hasOwnProperty
判断属性是否为自有属性:
javascriptfunction Person(name) { this.name = name; } Person.prototype.age = 25; const person = new Person('张三'); console.log(person.hasOwnProperty('name')); // true console.log(person.hasOwnProperty('age')); // false(原型属性) console.log(person.__proto__.hasOwnProperty('age')); // true
in 操作符
in 操作符会检查原型链:
javascriptfunction Person(name) { this.name = name; } Person.prototype.age = 25; const person = new Person('张三'); console.log('name' in person); // true(自有) console.log('age' in person); // true(原型链) console.log('toString' in person); // true(Object.prototype)
for...in 遍历
for...in 遍历包括原型链上的可枚举属性:
javascriptfunction Person(name) { this.name = name; } Person.prototype.age = 25; const person = new Person('张三'); for (const key in person) { if (person.hasOwnProperty(key)) { console.log(key, person[key]); // name - 张三 } }
常见面试问题
Q1: 原型链继承和构造函数继承的区别?
javascript// 原型链继承:共享方法,节省内存 // 构造函数继承:每个实例有独立的方法 function Parent() { this.colors = ['红', '绿']; } function Child() { Parent.call(this); // 构造函数继承 } // 如果不加这行,原型链继承 Child.prototype = new Parent(); // 问题:共享引用类型的原型属性
Q2: 为什么函数有 prototype 而对象没有?
- 函数有
prototype属性:用于创建实例的原型对象 - 对象有
__proto__属性:指向其构造函数的 prototype
javascriptconst obj = {}; console.log(obj.prototype); // undefined(对象没有 prototype) console.log(obj.__proto__); // Object.prototype(对象有 __proto__)
Q3: instanceof 的原理?
javascriptfunction Person(name) { this.name = name; } const person = new Person('张三'); // instanceof 检查 Person.prototype 是否在 person 的原型链中 console.log(person instanceof Person); // true console.log(person instanceof Object); // true console.log(person instanceof Array); // false
Q4: 如何创建一个没有原型的对象?
javascriptconst obj = Object.create(null); console.log(obj.__proto__); // undefined console.log(obj.toString); // undefined // 纯字典对象,常用于 Map 或性能敏感场景
最佳实践
- 优先使用 ES6 class:语法更清晰,行为更可预测
- 使用 Object.create() 创建原型继承:更灵活的原型继承方式
- 避免修改 Object.prototype:会影响所有对象
- 使用 hasOwnProperty 检查自有属性:避免原型链干扰
- 原型链不要太长:超过 3-4 层会影响性能