本文详细解析JavaScript中两种经典的面向对象继承实现:原型继承与函数继承。通过Pet与Cat的示例,对比了prototype链式调用与闭包私有化的差异。重点探讨了内存效率、属性保护及实例复用等核心问题,帮助开发者根据实际需求选择合适的设计模式。
JavaScript面向对象继承概述
JavaScript自诞生以来已近20年,业界对其评价始终存在争议。尽管有人质疑其面向对象特性,但JavaScript类型系统宽松且无编译环节,这既赋予了开发者极高的自由度,也引入了潜在的技术缺陷。
即便JavaScript不被严格定义为面向对象语言,开发者仍可通过模仿其他语言的模式,在JavaScript中实现面向对象的编程范式。
以下介绍JavaScript教程中极具代表性的两种继承实现方案。
原型继承实现方案
具体实现代码如下:
// 定义Pet对象,包含名称(name)和腿的数量(legs)。
var Pet = function (name, legs) {
this.name = name; // 保存名称和腿的数量值。
this.legs = legs;
};
// 定义一个方法,用于显示Pet的名称和腿的数量。
Pet.prototype.getDetails = function () {
return this.name + " has " + this.legs + " legs ";
}
// 定义Cat对象,继承自Pet。
var Cat = function (name) {
Pet.call(this, name, 4); // 调用父对象的构造函数
};
// 执行此行以从Pet继承。
Cat.prototype = new Pet();
// 为Cat增加一个动作方法。
Cat.prototype.action = function () {
return "Catch a bird";
};
// 创建petCat实例。
var petCat = new Cat("felix");
var details = petCat.getDetails();
console.log(details); // 输出: "felix has 4 legs".
var action = petCat.action();
console.log(action); // 输出: "Catch a bird".
petCat.name = "sylvester"; // 修改petCat的名字
petCat.legs = 7; // 修改petCat的腿数量
details = petCat.getDetails();
console.log(details); // 输出: "sylvester has 7 legs". 上述方法在功能上可行,但代码结构较为臃肿,缺乏优雅感。此外,外部可以直接修改属性,说明该方法未对继承属性提供有效保护。接下来介绍一种更简洁的方法,通过利用“函数继承”特性,省去new和prototype的使用。
函数继承实现方案
具体实现代码如下:
// 定义pet对象,包含名称和腿的数量。
var pet = function (name, legs) {
// 创建一个对象that,其中name可修改,但legs不可改,实现变量私有化。
var that = {
name: name,
getDetails: function () {
return that.name + " has " + legs + " legs ";
}
};
return that;
}
// 定义cat对象,继承自pet。
var cat = function (name) {
var that = pet(name, 4); // 从pet中继承属性
// 在cat中增加action方法。
that.action = function () {
return "Catch a bird";
}
return that;
}
// 创建petCat2实例。
var petCat2 = cat("Felix");
var details = petCat2.getDetails();
console.log(details); // 输出: "felix has 4 legs".
var action = petCat2.action();
console.log(action); // 输出: "Catch a bird".
petCat2.name = "sylvester"; // 可以修改名字。
petCat2.legs = 7; // 但无法直接修改腿的数量
details = petCat2.getDetails();
console.log(details); // 输出: "sylvester has 4 legs". 两种继承方式的对比与建议
提示:原型继承的优势在于内存效率高,无论被继承多少次,对象的原型属性和方法仅保存一份。而在函数继承中,每个新实例都会创建重复的属性和方法,若创建大量大对象,内存消耗将显著增加。解决此问题的方法是将较大的属性或方法保存在一个独立对象中,并作为参数传给构造函数,使所有实例共享该对象资源,而非各自创建副本。
上述两种方法均能轻松实现JavaScript的面向对象继承,并无绝对优劣之分,具体选择取决于个人偏好与场景需求。此外,这两种方法并非唯一解,欢迎读者评论补充其他实现方式。