本文深入解析JavaScript面向对象编程中的继承机制,详细阐述构造函数、原型与实例的关系。重点介绍原型链、构造函数、组合继承等实现方式,分析各自优缺点及适用场景,帮助开发者掌握JavaScript继承的核心原理与最佳实践。
本文详细解析了JavaScript中面向对象继承的核心原理与具体实现方式,旨在为读者提供清晰的参考。
构造函数、原型和实例的关系
1、构造函数、原型和实例的关系
构造函数拥有一个名为prototype的原型属性,该属性指向一个原型对象。
原型对象内部包含一个指向构造函数的指针constructor 。
实例对象内部包含一个指向原型对象的指针[[prototype]] 。
通过原型链实现继承
2、通过原型链实现继承
核心思想是利用原型机制,使一个引用类型能够继承另一个引用类型的属性和方法,从而让子类型访问超类型的所有属性和方法。构建原型链的本质是将一个类型的实例赋值给另一个构造函数的原型,这实际上是对原型对象的重写,用新类型的实例替换原原型。
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
alert("Hello, " + this.name);
}
var person = new Person("Alice");
person.sayHello(); // Hello, Alice
function Student() {
}
Student.prototype = new Person("Bruce");
Student.prototype.id = 16;
Student.prototype.showId = function() {
alert(this.id);
}
var student = new Student();
student.sayHello(); // Hello, Bruce
student.showId(); // 16
需要注意的是,不能使用对象字面量语法来创建原型方法,因为这会重写原型链,导致之前的继承关系失效。
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
alert("Hello, " + this.name);
}
var person = new Person("Alice");
person.sayHello(); // Hello, Alice
function Student() {
}
Student.prototype = new Person("Bruce");
Student.prototype.id = 16;
Student.prototype = {
showId: function() {
alert(this.id);
}
};
var student = new Student();
student.sayHello(); // 报错:student.sayHello is not a function
student.showId(); // 16
在原型链结构中,student变量指向Student的原型,而Student的原型又指向Person的原型。
student.sayHello()原型链的搜索机制如下:
1)首先搜索student实例中是否存在sayHello()
2)若未找到,则搜索Student.prototype中是否存在sayHello()
3)若仍未找到,则继续搜索Person.prototype中是否存在sayHello()
子类型有时需要覆盖超类型中的某个方法,或者需要添加超类型中不存在的新方法。
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
alert("Hello, " + this.name);
}
var person = new Person("Alice");
person.sayHello(); // Hello, Alice
function Student() {
}
Student.prototype = new Person("Bruce");
Student.prototype.id = 16;
Student.prototype.showId = function() {
alert(this.id);
}
Student.prototype.sayHello = function() {
alert("Hi, " + this.name);
}
var student = new Student();
student.sayHello(); //Hi, Bruce
student.showId(); // 16
关键点是:用于覆盖或添加方法的代码必须放置在替换原型的语句之后执行。
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
alert("Hello, " + this.name);
}
var person = new Person("Alice");
person.sayHello(); // Hello, Alice
function Student() {
}
Student.prototype.sayHello = function() {
alert("Hi, " + this.name);
}
Student.prototype = new Person("Bruce");
Student.prototype.id = 16;
Student.prototype.showId = function() {
alert(this.id);
}
var student = new Student();
student.sayHello(); // Hello, Bruce
student.showId(); // 16
确定实例与原型之间关系的三种方法:
(1)instanceof运算符
alert(student instanceof Object); // true
alert(student instanceof Student); // true
alert(student instanceof Person); // true
(2)isProtptypeOf方法
alert(Object.prototype.isPrototypeOf(student)); // true
alert(Student.prototype.isPrototypeOf(student)); // true
alert(Person.prototype.isPrototypeOf(student)); // true
(3)getPrototypeOf方法
Object.getPrototypeOf(student1) == Student.prototype
使用原型链实现继承存在以下问题:
(1)引用类型的属性会被所有实例共享。因为在原型继承中,原型变成了另一个类型的实例,该实例的属性随之成为新原型的属性,从而导致共享现象。
function Person(name, age) {
this.friends = ["Cindy","David"];
}
function Student() {
}
Student.prototype = new Person();
var student1 = new Student();
student1.friends.push("Bruce");
alert(student1.friends); // "Cindy","David","Bruce"
var student2 = new Student();
alert(student1.friends); // "Cindy","David","Bruce"
(2)在创建子类型实例时,无法向超类型的构造函数传递参数。这是因为在不影响所有对象实例的前提下,很难实现向超类型构造函数传参。
因此,实际开发中很少单独使用原型链来实现继承。
通过构造函数实现继承
3、通过构造函数实现继承
基本思路是在子类型构造函数的内部调用超类型的构造函数。此外,还可以利用apply()和call()方法在新创建的对象上执行构造函数。
function Person(name, age) {
this.name = name;
this.age = age;
}
function Student() {
Person.call(this,"Alice",22); // 继承了构造函数Person,同时还传递了参数
this.id = 16; // 实例属性
}
var student = new Student();
alert(student.name); // "Alice"
alert(student.age); // 22
alert(student.id); // 16
使用构造函数实现继承存在以下问题:
(1)在超类型原型中定义的方法,对子类型而言是不可见的,这导致所有类型都只能依赖构造函数模式。
(2)若要让子类访问超类定义的方法,这些方法必须在构造函数中定义,但这会导致函数复用无法实现。
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.showName = function() {
alert(this.name);
};
function Student() {
Person.call(this,"Alice",22);
this.id = 16;
}
var student = new Student();
alert(student.showName()); // 报错:student.showName is not a function
因此,实际开发中很少单独使用构造函数来实现继承。
组合使用原型链和构造函数实现继承
4、组合使用原型链和构造函数实现继承
思路是:利用原型链继承共享的属性和方法,同时使用构造函数来继承实例属性。
效果:既通过在原型上定义方法实现了函数复用,又保证了每个实例拥有自己的属性。
function Person(name, age) {
this.name = name;
this.age = age;
this.friends = ["Cindy","David"];
}
Person.prototype.sayHello = function() {
alert("Hello, " + this.name);
}
function Student(name, age, id) {
Person.call(this, name, age);
this.id = id;
}
Student.prototype = new Person();
Student.prototype.showId = function() {
alert(this.id);
}
var student1 = new Student("Alice", 22, 16);
student1.friends.push("Emy");
alert(student1.friends); // "Cindy","David","Emy"
student1.sayHello(); // Hello, Alice
student1.showId(); // 16
var student2 = new Student("Bruce", 23, 17);
alert(student2.friends); // "Cindy","David"
student2.sayHello(); // Hello, Bruce
student2.showId(); // 17
更多关于JavaScript相关内容感兴趣的读者可查看本站专题:《javascript面向对象入门教程》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript数学运算用法总结》
希望本文所述对大家JavaScript程序设计有所帮助。