JavaScript 继承 封装 多态实现及原理详解
继承
在面向对象编程中,类的继承指的是类与类之间的关系,该关系表明一个类(称为子类、派生类)继承另外一个类(称为父类、基类、超类)的特征和行为。类的继承包含以下几种方式:
原型链继承
原型链继承是 JavaScript 中最常用的一种继承方式。它的原理是通过将父对象的实例作为子对象的原型,使得子对象可以访问父对象中的属性和方法:
function Person() {
this.name = 'Tom';
}
Person.prototype.sayHello = function() {
console.log('Hello, ' + this.name);
};
function Student() {}
Student.prototype = new Person();
var stu = new Student();
stu.sayHello(); // 输出 "Hello, Tom"
以上代码中,我们定义了 Person
和 Student
两个构造函数,Student
通过 new
操作符创建实例时,实例的 __proto__
属性指向了 Person
的实例。这样,Student
实例就可以访问到 Person
的属性和方法。
原型链继承的缺点是会将父对象的属性和方法添加到子对象的原型链中,这样会导致子对象的属性和方法可能被其他实例共享,也就是会存在“原型污染”的问题。
构造函数继承
构造函数继承是通过在子类构造函数中调用父类构造函数,使得子类可以继承父类的属性:
function Person(name) {
this.name = name;
}
function Student(name, grade) {
Person.call(this, name);
this.grade = grade;
}
var stu = new Student('Tom', 3);
console.log(stu.name + ' is in grade ' + stu.grade); // 输出 "Tom is in grade 3"
以上代码中,我们定义了 Person
和 Student
两个构造函数,Student
在构造函数中通过 call
方法调用了 Person
的构造函数,并将 this
上下文设置为当前实例。这样,Student
实例就获得了 Person
的属性。
构造函数继承的缺点是无法继承父类原型中的属性和方法,也就是无法实现方法的复用。
组合继承
组合继承是一种结合了原型链继承和构造函数继承的方式,它的原理是通过在子类构造函数中调用父类构造函数并在子类原型链中指定一个父类实例:
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log('Hello, ' + this.name);
};
function Student(name, grade) {
Person.call(this, name);
this.grade = grade;
}
Student.prototype = new Person();
Student.prototype.constructor = Student;
var stu = new Student('Tom', 3);
stu.sayHello(); // 输出 "Hello, Tom"
以上代码中,我们定义了 Person
和 Student
两个构造函数,Student
在构造函数中通过 call
方法调用了 Person
的构造函数,并将 this
上下文设置为当前实例;同时,在 Student
的原型链中指定了一个 Person
的实例,这样,Student
实例就可以访问到 Person
的属性和方法,同时也继承了 Person
原型中的属性和方法。
组合继承是目前使用最广泛的一种继承方式,它既继承了父类的属性和方法,也可以实现方法的复用,但它也存在一些问题。因为子类在原型链中指定了一个父类实例,所以子类的原型链中就存在了不必要的父类实例,这样会影响到性能。
封装
在面向对象编程中,封装是指将对象的状态和行为进行“封装”,以达到保护对象内部状态不被外部直接访问和修改的效果。JavaScript 中实现封装的方式主要有两种:使用闭包、使用 Symbol。
使用闭包
使用闭包可以将某些属性和方法“封装”在构造函数内部,使得它们不被外部直接访问:
function Person(name) {
var _age;
this.name = name;
this.getAge = function() {
return _age;
};
this.setAge = function(value) {
if (value > 0) {
_age = value;
}
};
}
var tom = new Person('Tom');
tom.setAge(18);
console.log(tom.getAge()); // 输出 18
console.log(tom._age); // 输出 undefined
以上代码中,我们通过将年龄 _age
作为一个局部变量定义在 Person
的构造函数中,然后通过构造函数的公共方法 getAge
和 setAge
来实现对 _age
的访问和修改。由于 _age
是一个局部变量,所以它不被外部直接访问,实现了封装。
使用 Symbol
使用 Symbol 是一种较新的实现封装的方式,它能够生成唯一的属性名,使得该属性不容易被外部访问:
var Person = (function() {
var _age = Symbol('age');
function Person(name, age) {
this.name = name;
this[_age] = age;
}
Person.prototype.getAge = function() {
return this[_age];
};
Person.prototype.setAge = function(value) {
if (value > 0) {
this[_age] = value;
}
};
return Person;
})();
var tom = new Person('Tom', 18);
tom.setAge(20);
console.log(tom.getAge()); // 输出 20
console.log(tom._age); // 输出 undefined
以上代码中,我们使用了 ES6 中的 Symbol 来定义一个唯一的属性名 _age
,然后将其作为 Person
的实例属性。由于 _age
是一个 Symbol 类型的属性名,所以它不容易被外部访问,实现了封装。
多态
多态是一种在面向对象编程中非常重要的概念,它可以用来实现一些非常灵活的功能。在 JavaScript 中,我们可以通过重写父类方法或使用 instanceof
操作符来实现多态。
重写父类方法
重写父类方法是指在子类中重新定义父类同名的方法,这样可以使得子类对该方法的功能进行修改、扩展或重写:
function Vehicle() {}
Vehicle.prototype.run = function() {
console.log('run in Vehicle');
};
function Car() {}
Car.prototype = new Vehicle();
Car.prototype.constructor = Car;
Car.prototype.run = function() {
console.log('run in Car');
};
var v = new Vehicle();
var c = new Car();
v.run(); // 输出 "run in Vehicle"
c.run(); // 输出 "run in Car"
以上代码中,我们定义了 Vehicle
和 Car
两个类,Car
继承自 Vehicle
。通过在 Car
的原型对象上重写 run
方法,我们实现了对 run
方法的重写。
使用 instanceof
instanceof
操作符用来判断一个对象是否为某个类的实例,它可以用来实现多态:
function Vehicle() {}
Vehicle.prototype.run = function() {
console.log('run in Vehicle');
};
function Car() {}
Car.prototype = new Vehicle();
Car.prototype.constructor = Car;
Car.prototype.run = function() {
console.log('run in Car');
};
function Truck() {}
Truck.prototype = new Vehicle();
Truck.prototype.constructor = Truck;
Truck.prototype.run = function() {
console.log('run in Truck');
};
var v = new Vehicle();
var c = new Car();
var t = new Truck();
function runVehicle(vehicle) {
if (vehicle instanceof Vehicle) {
vehicle.run();
}
}
runVehicle(v); // 输出 "run in Vehicle"
runVehicle(c); // 输
以上代码中,我们定义了 Vehicle
、Car
和 Truck
三个类,其中 Car
和 Truck
都继承自 Vehicle
。我们通过编写一个函数 runVehicle
,并在其内部使用 instanceof
操作符来判断传入参数的类型,从而实现多态。
示例说明
示例一:复杂继承关系
在某个场景中,我们需要创建多个类并建立复杂的继承关系,如下所示:
function Animal(name) {
this.name = name;
}
Animal.prototype.sayName = function() {
console.log(this.name);
};
function Mammal(name) {
Animal.call(this, name);
this.hasHair = true;
this.hasLegs = true;
}
Mammal.prototype = new Animal();
Mammal.prototype.constructor = Mammal;
function Bird(name) {
Animal.call(this, name);
this.hasFeathers = true;
this.hasWings = true;
}
Bird.prototype = new Animal();
Bird.prototype.constructor = Bird;
function Bat(name) {
Mammal.call(this, name);
Bird.call(this, name);
this.canFly = true;
}
Bat.prototype = Object.create(Mammal.prototype);
Object.assign(Bat.prototype, Bird.prototype);
Bat.prototype.constructor = Bat;
var bat = new Bat('Tom');
bat.sayName(); // 输出 "Tom"
console.log(bat.hasHair); // 输出 true
console.log(bat.hasLegs); // 输出 true
console.log(bat.hasFeathers); // 输出 true
console.log(bat.hasWings); // 输出 true
console.log(bat.canFly); // 输出 true
以上代码中,我们定义了 Animal
、Mammal
、Bird
和 Bat
四个类,并在其之间建立了复杂的继承关系。其中,Mammal
和 Bird
继承自 Animal
,Bat
继承自 Mammal
和 Bird
。我们通过在 Bat
的构造函数中分别调用 Mammal
和 Bird
的构造函数实现多重继承,从而实现了 Bat
类对 Mammal
和 Bird
的属性和方法的继承。
示例二:使用 Symbol 实现封装
在某个场景中,我们定义了一个类,并且希望将其中的某些属性和方法“封装”,如下所示:
var Person = (function() {
var _age = Symbol('age');
function Person(name, age) {
this.name = name;
this[_age] = age;
}
Person.prototype.getAge = function() {
return this[_age];
};
Person.prototype.setAge = function(value) {
if (value > 0) {
this[_age] = value;
}
};
return Person;
})();
var tom = new Person('Tom', 18);
tom.setAge(20);
console.log(tom.getAge()); // 输出 20
console.log(tom._age); // 输出 undefined
以上代码中,我们使用了 ES6 中的 Symbol 定义一个属性名 _age
,并将其作为 Person
的实例属性。由于 _age
是一个 Symbol 类型的属性名,所以它不容易被外部访问,从而实现了封装。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:JavaScript 继承 封装 多态实现及原理详解 - Python技术站