跳到主要内容

JavaScript 原型

什么是原型?

在JavaScript中,原型是一个核心概念,它是JavaScript实现对象继承的基础机制。与传统的基于类的继承不同,JavaScript使用原型链的方式来实现对象之间的继承关系。

每个JavaScript对象都有一个与之关联的原型对象。当我们试图访问一个对象的属性或方法时,如果该对象本身没有这个属性或方法,JavaScript引擎会自动去对象的原型上查找。这种机制称为"原型继承"。

原型链基础

每个JavaScript对象都有一个特殊的内部属性[[Prototype]](在一些浏览器中可以通过__proto__访问),它指向该对象的原型。原型本身也是一个对象,因此也有自己的原型,这样就形成了一个原型链。

原型链的终点通常是Object.prototype,而Object.prototype的原型是null

访问和设置原型

使用Object.getPrototypeOf()Object.setPrototypeOf()

javascript
const person = {
name: '张三',
sayHello() {
return `你好,我是${this.name}`;
}
};

const student = {
grade: '一年级'
};

// 设置student的原型为person
Object.setPrototypeOf(student, person);

// 获取student的原型
console.log(Object.getPrototypeOf(student) === person); // 输出:true

// 通过原型链调用方法
console.log(student.sayHello()); // 输出:"你好,我是张三"

使用__proto__(不推荐在生产环境使用)

javascript
const person = {
name: '张三',
greet() {
return `你好,我是${this.name}`;
}
};

const student = {
grade: '一年级',
__proto__: person
};

console.log(student.greet()); // 输出:"你好,我是张三"
警告

__proto__是一个已废弃的属性,不应在生产代码中使用。请使用Object.getPrototypeOf()Object.setPrototypeOf()或构造函数来代替。

构造函数和原型

在JavaScript中,构造函数是创建对象的模板。每个构造函数都有一个prototype属性,指向一个对象,这个对象就是通过该构造函数创建的实例的原型。

javascript
// 定义一个构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}

// 在原型上添加方法
Person.prototype.sayHello = function() {
return `你好,我是${this.name},我今年${this.age}`;
};

// 创建实例
const person1 = new Person('李四', 25);
const person2 = new Person('王五', 30);

console.log(person1.sayHello()); // 输出:"你好,我是李四,我今年25岁"
console.log(person2.sayHello()); // 输出:"你好,我是王五,我今年30岁"

// 验证原型关系
console.log(person1.__proto__ === Person.prototype); // 输出:true
console.log(person2.__proto__ === Person.prototype); // 输出:true

构造函数、原型和实例的关系

原型继承

JavaScript中实现继承的一种方式是通过原型链。

使用Object.create()

javascript
// 父对象
const animal = {
type: '动物',
eat() {
return `${this.name}正在吃东西`;
}
};

// 使用Object.create()创建一个继承自animal的对象
const dog = Object.create(animal);
dog.name = '旺财';
dog.bark = function() {
return '汪汪!';
};

console.log(dog.eat()); // 输出:"旺财正在吃东西"
console.log(dog.bark()); // 输出:"汪汪!"

构造函数继承

javascript
// 父构造函数
function 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);
// 修复constructor指向
Dog.prototype.constructor = Dog;

// 添加子类特有方法
Dog.prototype.bark = function() {
return `${this.breed}${this.name}:汪汪!`;
};

const myDog = new Dog('小黑', '拉布拉多');
console.log(myDog.eat()); // 输出:"小黑正在吃东西"
console.log(myDog.bark()); // 输出:"拉布拉多小黑:汪汪!"

属性查找机制

当我们访问一个对象的属性时,JavaScript引擎会按照以下步骤进行查找:

  1. 首先在对象自身查找该属性
  2. 如果没有找到,则在对象的原型上查找
  3. 如果还没有找到,则继续在原型的原型上查找
  4. 重复以上步骤,直到找到该属性或到达原型链的末尾(null
javascript
const grandparent = {
lastName: '王',
hobby: '下棋'
};

const parent = Object.create(grandparent);
parent.job = '教师';
parent.hobby = '读书'; // 覆盖祖父的爱好

const child = Object.create(parent);
child.name = '小明';

console.log(child.name); // 输出:"小明"(自身属性)
console.log(child.job); // 输出:"教师"(来自父对象)
console.log(child.lastName); // 输出:"王"(来自祖父对象)
console.log(child.hobby); // 输出:"读书"(来自父对象,覆盖了祖父对象的属性)
console.log(child.age); // 输出:undefined(整个原型链上都没有这个属性)

实际应用案例

案例1:创建可复用的UI组件

javascript
// 基础组件原型
const UIComponent = {
render() {
return `<div class="${this.className}">${this.content}</div>`;
},
setContent(content) {
this.content = content;
return this;
}
};

// 创建按钮组件
const Button = Object.create(UIComponent);
Button.className = 'btn';
Button.content = '点击我';
Button.click = function(callback) {
console.log('按钮被点击');
if (typeof callback === 'function') {
callback();
}
};

// 使用组件
console.log(Button.render()); // 输出:<div class="btn">点击我</div>
Button.setContent('提交').click(() => console.log('表单已提交'));
console.log(Button.render()); // 输出:<div class="btn">提交</div>

案例2:创建一个简单的插件系统

javascript
// 基础插件系统
const PluginSystem = {
plugins: {},
register(name, plugin) {
this.plugins[name] = plugin;
return this;
},
use(name, ...args) {
if (this.plugins[name]) {
return this.plugins[name].apply(this, args);
}
return null;
}
};

// 创建应用实例
const app = Object.create(PluginSystem);

// 注册一些插件
app.register('logger', function(message) {
console.log(`[LOG]: ${message}`);
return true;
});

app.register('formatter', function(text) {
return text.toUpperCase();
});

// 使用插件
app.use('logger', 'Hello World'); // 输出:[LOG]: Hello World
const formatted = app.use('formatter', 'hello javascript');
console.log(formatted); // 输出:HELLO JAVASCRIPT

原型方法

JavaScript提供了一些原型相关的方法,帮助我们更好地操作原型:

Object.create()

创建一个新对象,使用现有的对象作为新创建对象的原型。

javascript
const person = {
isHuman: true,
introduce() {
return `我的名字是 ${this.name}. 我是一个${this.isHuman ? '人类' : '机器人'}`;
}
};

const me = Object.create(person);
me.name = 'Jack';

console.log(me.introduce()); // 输出:"我的名字是 Jack. 我是一个人类"

hasOwnProperty()

用于检查对象是否具有指定的自有属性(不是继承来的)。

javascript
const parent = { lastName: '张' };
const child = Object.create(parent);
child.firstName = '小明';

console.log(child.hasOwnProperty('firstName')); // 输出:true
console.log(child.hasOwnProperty('lastName')); // 输出:false

isPrototypeOf()

用于检查一个对象是否存在于另一个对象的原型链上。

javascript
const animal = { eats: true };
const rabbit = Object.create(animal);
const longEar = Object.create(rabbit);

console.log(animal.isPrototypeOf(rabbit)); // 输出:true
console.log(animal.isPrototypeOf(longEar)); // 输出:true
console.log(rabbit.isPrototypeOf(animal)); // 输出:false

ES6 中的类与原型

ES6引入了class语法,使JavaScript的面向对象编程更接近传统的基于类的语言。但在底层,JavaScript的class仍然是基于原型的。

javascript
class Animal {
constructor(name) {
this.name = name;
}

speak() {
return `${this.name}发出声音`;
}
}

class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}

speak() {
return `${this.name}汪汪叫`;
}

fetch() {
return `${this.name}在捡东西`;
}
}

const dog = new Dog('小花', '柯基');
console.log(dog.speak()); // 输出:"小花汪汪叫"
console.log(dog.fetch()); // 输出:"小花在捡东西"

实际上,上面的代码在底层仍然使用原型链实现继承。

总结

在JavaScript中,原型是一种非常强大的机制,它使对象能够从其他对象继承属性和方法。理解原型对于掌握JavaScript面向对象编程至关重要。通过原型,我们可以:

  • 实现对象之间的继承关系
  • 共享方法,减少内存占用
  • 动态扩展对象功能
  • 创建可复用的对象模板

尽管ES6引入了更易于理解的类语法,但JavaScript的对象系统仍然基于原型。深入理解原型机制可以帮助我们更有效地使用JavaScript,编写更高效、更优雅的代码。

学习建议

学习原型时,建议先理解基本概念,再通过实际编码加深理解。可以尝试改造本文中的示例,或者自己创建一些继承关系来巩固所学知识。

练习

  1. 创建一个Vehicle原型对象,包含typemove()方法,然后创建CarBicycle对象继承自Vehicle,并添加各自特有的方法。

  2. 实现一个简单的继承函数extend(Child, Parent),使Child构造函数能够继承Parent构造函数的原型方法。

  3. 使用原型模式设计一个简单的表单验证系统,包括一个基础验证器和几个特定类型的验证器(如邮箱验证器、密码验证器等)。

参考资源

继续深入学习JavaScript原型将帮助你更好地理解JavaScript这门语言的核心机制,为掌握更复杂的JavaScript概念和设计模式打下坚实基础。