一、基本概念
1. prototype是什么?
-
每个函数 都有一个
prototype属性(只有函数有) -
它是一个对象,称为原型对象
-
用于实现基于原型的继承和共享属性
javascript
function Person(name) {
this.name = name;
}
// Person.prototype 是原型对象
console.log(typeof Person.prototype); // "object"
console.log(Person.prototype); // Person {}
2. constructor是什么?
-
每个原型对象 都有一个
constructor属性 -
它指向创建该原型的构造函数
-
是一个循环引用
javascript
function Person(name) {
this.name = name;
}
console.log(Person.prototype.constructor === Person); // true
console.log(Person.prototype.constructor.name); // "Person"
二、原型链的基本结构
javascript
function Person(name) {
this.name = name;
}
// 添加方法到原型
Person.prototype.sayHello = function() {
console.log(`Hello, I'm ${this.name}`);
};
const alice = new Person('Alice');
// 原型链关系
console.log(alice.__proto__ === Person.prototype); // true
console.log(Person.prototype.constructor === Person); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.constructor === Object); // true
console.log(Object.prototype.__proto__ === null); // true
// 完整的原型链
// alice --> Person.prototype --> Object.prototype --> null
三、构造函数、实例、原型的关系图
javascript
构造函数 Person
│
│ new Person()
▼
实例 alice Person.prototype (原型对象)
│ │
│.__proto__--------┘
▼ constructor --> Person
alice.__proto__ │
│ │.__proto__
▼ ▼
Person.prototype Object.prototype
│ │
│.constructor │.constructor
└──────────────────┘
四、prototype的详细作用
1. 共享方法和属性
javascript
function Person(name) {
this.name = name;
this.id = Math.random(); // 每个实例都有自己的id
}
// 共享方法 - 所有实例共享同一个函数
Person.prototype.sayHello = function() {
console.log(`Hello from ${this.name}`);
};
// 共享属性 - 所有实例共享同一个引用
Person.prototype.species = 'Homo sapiens';
const p1 = new Person('Alice');
const p2 = new Person('Bob');
console.log(p1.sayHello === p2.sayHello); // true - 共享同一个函数
console.log(p1.id === p2.id); // false - 每个实例有自己的id
console.log(p1.species === p2.species); // true - 共享属性
// 修改共享属性会影响所有实例
Person.prototype.species = 'Human';
console.log(p1.species); // "Human"
console.log(p2.species); // "Human"
2. 原型链查找机制
javascript
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log(`Hello, I'm ${this.name}`);
};
const person = new Person('Alice');
// 访问属性和方法时的查找顺序
console.log(person.hasOwnProperty('name')); // true - 自身属性
console.log(person.hasOwnProperty('sayHello')); // false - 来自原型
// 原型链查找过程
person.sayHello(); // JavaScript会:
// 1. 检查person对象自身是否有sayHello属性 - 没有
// 2. 检查person.__proto__(Person.prototype)是否有 - 有,执行
// 3. 如果还没有,继续向上查找person.__proto__.__proto__(Object.prototype)
// 4. 直到找到或到达null
五、prototype.constructor的详细作用
1. 识别对象类型
javascript
function Person(name) {
this.name = name;
}
function Animal(name) {
this.name = name;
}
const p = new Person('Alice');
const a = new Animal('Dog');
console.log(p.constructor === Person); // true
console.log(a.constructor === Animal); // true
console.log(p.constructor === a.constructor); // false
// 判断对象类型
function getType(obj) {
return obj.constructor.name;
}
console.log(getType(p)); // "Person"
console.log(getType(a)); // "Animal"
console.log(getType([])); // "Array"
console.log(getType({})); // "Object"
2. 创建新实例
javascript
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log(`Hello, I'm ${this.name}`);
};
const p1 = new Person('Alice');
const p2 = new p1.constructor('Bob'); // 使用constructor创建新实例
p2.sayHello(); // Hello, I'm Bob
// 动态创建实例(不知道构造函数时)
function createInstance(obj, ...args) {
return new obj.constructor(...args);
}
const p3 = createInstance(p1, 'Charlie');
p3.sayHello(); // Hello, I'm Charlie
3. 修复constructor(重要!)
javascript
// 问题:重写prototype会丢失constructor
function Person(name) {
this.name = name;
}
// 完全重写prototype
Person.prototype = {
sayHello: function() {
console.log(`Hello, I'm ${this.name}`);
}
// 注意:这里没有constructor属性!
};
const p = new Person('Alice');
console.log(p.constructor === Person); // false!
console.log(p.constructor === Object); // true! (来自Object.prototype)
// 解决方案1:手动添加constructor
Person.prototype = {
constructor: Person, // 显式设置constructor
sayHello: function() {
console.log(`Hello, I'm ${this.name}`);
}
};
// 解决方案2:使用Object.defineProperty
Person.prototype = {
sayHello: function() {
console.log(`Hello, I'm ${this.name}`);
}
};
Object.defineProperty(Person.prototype, 'constructor', {
enumerable: false, // 不可枚举
writable: true,
value: Person
});
// 解决方案3:不重写,只扩展(推荐)
Person.prototype.sayHello = function() {
console.log(`Hello, I'm ${this.name}`);
};
Person.prototype.sayGoodbye = function() {
console.log(`Goodbye from ${this.name}`);
};
六、继承中的prototype和constructor
1. 经典继承模式
javascript
// 父类
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
console.log(`${this.name} makes a sound`);
};
// 子类
function Dog(name, breed) {
Animal.call(this, name); // 1. 调用父类构造函数
this.breed = breed;
}
// 2. 设置原型链
Dog.prototype = Object.create(Animal.prototype);
// 3. 修复constructor
Dog.prototype.constructor = Dog;
// 4. 添加子类方法
Dog.prototype.bark = function() {
console.log(`${this.name} barks!`);
};
const dog = new Dog('Rex', 'German Shepherd');
dog.speak(); // Rex makes a sound
dog.bark(); // Rex barks!
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
console.log(dog.constructor === Dog); // true
2. constructor在继承中的重要性
javascript
function Animal(name) {
this.name = name;
}
function Dog(name, breed) {
Animal.call(this, name);
this.breed = breed;
}
// 错误的继承方式
Dog.prototype = Animal.prototype; // 共享原型,污染父类
Dog.prototype.constructor = Dog; // 这会修改Animal.prototype.constructor!
console.log(Animal.prototype.constructor === Dog); // true!Animal的constructor被修改了
// 正确的继承方式
Dog.prototype = Object.create(Animal.prototype); // 创建新对象,不共享
Dog.prototype.constructor = Dog; // 只修改Dog.prototype.constructor
console.log(Animal.prototype.constructor === Animal); // true,保持正确
七、内置构造函数的prototype和constructor
javascript
// 数组
const arr = [1, 2, 3];
console.log(arr.constructor === Array); // true
console.log(Array.prototype.constructor === Array); // true
// 对象
const obj = {};
console.log(obj.constructor === Object); // true
console.log(Object.prototype.constructor === Object); // true
// 函数
const func = function() {};
console.log(func.constructor === Function); // true
console.log(Function.prototype.constructor === Function); // true
// 原型链的顶端
console.log(Function.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.constructor === Object); // true
console.log(Object.prototype.__proto__ === null); // true
八、实际应用场景
1. 检查对象类型
javascript
function isArray(obj) {
return obj.constructor === Array;
// 或者更好的方式:Array.isArray(obj)
}
function isDate(obj) {
return obj.constructor === Date;
}
function isRegExp(obj) {
return obj.constructor === RegExp;
}
2. 深拷贝
javascript
function deepClone(obj) {
// 根据constructor创建新实例
if (obj === null || typeof obj !== 'object') {
return obj;
}
// 处理数组
if (obj.constructor === Array) {
return obj.map(item => deepClone(item));
}
// 处理日期
if (obj.constructor === Date) {
return new Date(obj.getTime());
}
// 处理正则表达式
if (obj.constructor === RegExp) {
return new RegExp(obj);
}
// 处理普通对象
const cloned = new obj.constructor();
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = deepClone(obj[key]);
}
}
return cloned;
}
3. 动态创建组件
javascript
// UI框架中常见模式
function Component(config) {
this.id = config.id;
this.type = config.type;
}
function Button(config) {
Component.call(this, config);
this.text = config.text;
}
Button.prototype = Object.create(Component.prototype);
Button.prototype.constructor = Button;
function Input(config) {
Component.call(this, config);
this.value = config.value;
}
Input.prototype = Object.create(Component.prototype);
Input.prototype.constructor = Input;
// 工厂函数,根据类型创建组件
function createComponent(config) {
const componentTypes = {
button: Button,
input: Input,
// 可以动态注册新类型
};
const ComponentClass = componentTypes[config.type];
if (!ComponentClass) {
throw new Error(`Unknown component type: ${config.type}`);
}
return new ComponentClass(config);
}
九、常见陷阱和注意事项
1. constructor的可写性
javascript
function Person(name) {
this.name = name;
}
const p = new Person('Alice');
// constructor属性可以被修改
p.constructor = Array;
console.log(p.constructor === Array); // true(但p不是数组)
// 更安全地获取constructor
console.log(Object.getPrototypeOf(p).constructor === Person); // true
2. 性能考虑
javascript
// 方法定义在原型上 vs 定义在构造函数中
// 方式1:定义在原型上(推荐)
function Person1(name) {
this.name = name;
}
Person1.prototype.sayHello = function() {
console.log(`Hello, ${this.name}`);
};
// 优点:所有实例共享同一个函数,节省内存
// 方式2:定义在构造函数中
function Person2(name) {
this.name = name;
this.sayHello = function() {
console.log(`Hello, ${this.name}`);
};
}
// 缺点:每个实例都有独立的函数副本,浪费内存
const people1 = [];
const people2 = [];
// 创建1000个实例测试
for (let i = 0; i < 1000; i++) {
people1.push(new Person1(`Person1-${i}`));
people2.push(new Person2(`Person2-${i}`));
}
console.log(people1[0].sayHello === people1[1].sayHello); // true
console.log(people2[0].sayHello === people2[1].sayHello); // false
十、面试常见问题
问题1:解释new操作符做了什么?
javascript
function newOperator(constructor, ...args) {
// 1. 创建一个空对象,继承构造函数的原型
const obj = Object.create(constructor.prototype);
// 2. 将构造函数的this绑定到这个新对象
const result = constructor.apply(obj, args);
// 3. 如果构造函数返回一个对象,则返回该对象
// 否则返回新创建的对象
return result instanceof Object ? result : obj;
}
// 使用自定义的new
function Person(name) {
this.name = name;
}
const p = newOperator(Person, 'Alice');
console.log(p.name); // Alice
console.log(p instanceof Person); // true
问题2:如何判断一个属性是自身的还是继承的?
javascript
function Person(name) {
this.name = name;
}
Person.prototype.species = 'Human';
const p = new Person('Alice');
// 方法1:hasOwnProperty
console.log(p.hasOwnProperty('name')); // true
console.log(p.hasOwnProperty('species')); // false
// 方法2:in操作符 vs hasOwnProperty
console.log('name' in p); // true
console.log('species' in p); // true
console.log('toString' in p); // true(继承自Object.prototype)
// 方法3:Object.getOwnPropertyNames
console.log(Object.getOwnPropertyNames(p)); // ['name']
总结要点
-
prototype是函数的属性,用于实现基于原型的继承
-
constructor是原型对象的属性,指向创建该原型的构造函数
-
实例通过__proto__访问原型,原型通过constructor访问构造函数
-
方法应该定义在原型上,属性应该定义在构造函数中
-
继承时需要修复constructor,防止污染父类
-
new操作符会创建新对象,设置原型链,绑定this
-
constructor可用于类型识别和动态创建实例
-
ES5的原型系统是理解JavaScript继承的基础,虽然ES6引入了class语法,但底层仍然是原型机制
理解prototype和constructor是掌握JavaScript面向对象编程的关键,也是理解现代框架(如React、Vue)内部实现的基础。