JavaScript 继承详解(三)_js面向对象
注:本章中的jClass的实现参考了Simple JavaScript Inheritance的做法。
首先让我们来回顾一下第一章中介绍的例子:
function Person(name) {<BR> this.name = name;<BR> }<BR> Person.prototype = {<BR> getName: function() {<BR> return this.name;<BR> }<BR> }<br><br> function Employee(name, employeeID) {<BR> this.name = name;<BR> this.employeeID = employeeID;<BR> }<BR> Employee.prototype = new Person();<BR> Employee.prototype.getEmployeeID = function() {<BR> return this.employeeID;<BR> };<BR> var zhang = new Employee("ZhangSan", "1234");<BR> console.log(zhang.getName()); // "ZhangSan" <BR>
修正constructor的指向错误
从上一篇文章中关于constructor的描述,我们知道Employee实例的constructor会有一个指向错误,如下所示:
var zhang = new Employee("ZhangSan", "1234");<BR> console.log(zhang.constructor === Employee); // false<BR> console.log(zhang.constructor === Object); // true <BR>
function Employee(name, employeeID) {<BR> this.name = name;<BR> this.employeeID = employeeID;<BR> }<BR> Employee.prototype = new Person();<BR> Employee.prototype.constructor = Employee;<BR> Employee.prototype.getEmployeeID = function() {<BR> return this.employeeID;<BR> };<BR> var zhang = new Employee("ZhangSan", "1234");<BR> console.log(zhang.constructor === Employee); // true<BR> console.log(zhang.constructor === Object); // false<BR>
创建Employee类时实例化Person是不合适的
但另一方面,我们又必须依赖于这种机制来实现继承。 解决办法是不在构造函数中初始化数据,而是提供一个原型方法(比如init)来初始化数据。
// 空的构造函数<BR> function Person() {<BR> }<BR> Person.prototype = {<BR> init: function(name) {<BR> this.name = name;<BR> },<BR> getName: function() {<BR> return this.name;<BR> }<BR> }<BR> // 空的构造函数<BR> function Employee() {<BR> }<BR> // 创建类的阶段不会初始化父类的数据,因为Person是一个空的构造函数<BR> Employee.prototype = new Person();<BR> Employee.prototype.constructor = Employee;<BR> Employee.prototype.init = function(name, employeeID) {<BR> this.name = name;<BR> this.employeeID = employeeID;<BR> };<BR> Employee.prototype.getEmployeeID = function() {<BR> return this.employeeID;<BR> };<BR>
var zhang = new Employee();<BR> zhang.init("ZhangSan", "1234");<BR> console.log(zhang.getName()); // "ZhangSan"<BR>
如何自动调用init函数?
必须达到两个效果,构造类时不要调用init函数和实例化对象时自动调用init函数。看来我们需要在调用空的构造函数时有一个状态标示。
// 创建一个全局的状态标示 - 当前是否处于类的构造阶段<BR> var initializing = false;<BR> function Person() {<BR> if (!initializing) {<BR> this.init.apply(this, arguments);<BR> }<BR> }<BR> Person.prototype = {<BR> init: function(name) {<BR> this.name = name;<BR> },<BR> getName: function() {<BR> return this.name;<BR> }<BR> }<BR> function Employee() {<BR> if (!initializing) {<BR> this.init.apply(this, arguments);<BR> }<BR> }<BR> // 标示当前进入类的创建阶段,不会调用init函数<BR> initializing = true;<BR> Employee.prototype = new Person();<BR> Employee.prototype.constructor = Employee;<BR> initializing = false;<BR> Employee.prototype.init = function(name, employeeID) {<BR> this.name = name;<BR> this.employeeID = employeeID;<BR> };<BR> Employee.prototype.getEmployeeID = function() {<BR> return this.employeeID;<BR> };<br><br> // 初始化类实例时,自动调用类的原型函数init,并向init中传递参数<BR> var zhang = new Employee("ZhangSan", "1234");<BR> console.log(zhang.getName()); // "ZhangSan"<BR>
如何避免引入全局变量initializing?
我们需要引入一个全局的函数来简化类的创建过程,同时封装内部细节避免引入全局变量。
// 当前是否处于创建类的阶段<BR> var initializing = false;<BR> function jClass(baseClass, prop) {<BR> // 只接受一个参数的情况 - jClass(prop)<BR> if (typeof (baseClass) === "object") {<BR> prop = baseClass;<BR> baseClass = null;<BR> }<BR> // 本次调用所创建的类(构造函数)<BR> function F() {<BR> // 如果当前处于实例化类的阶段,则调用init原型函数<BR> if (!initializing) {<BR> this.init.apply(this, arguments);<BR> }<BR> }<BR> // 如果此类需要从其它类扩展<BR> if (baseClass) {<BR> initializing = true;<BR> F.prototype = new baseClass();<BR> F.prototype.constructor = F;<BR> initializing = false;<BR> }<BR> // 覆盖父类的同名函数<BR> for (var name in prop) {<BR> if (prop.hasOwnProperty(name)) {<BR> F.prototype[name] = prop[name];<BR> }<BR> }<BR> return F;<BR> };<BR>
var Person = jClass({<BR> init: function(name) {<BR> this.name = name;<BR> },<BR> getName: function() {<BR> return this.name;<BR> }<BR> });<BR> var Employee = jClass(Person, {<BR> init: function(name, employeeID) {<BR> this.name = name;<BR> this.employeeID = employeeID;<BR> },<BR> getEmployeeID: function() {<BR> return this.employeeID;<BR> }<BR> });<br><br> var zhang = new Employee("ZhangSan", "1234");<BR> console.log(zhang.getName()); // "ZhangSan"<BR>
如何调用父类的同名方法?
我们可以通过为实例化对象提供一个base的属性,来指向父类(构造函数)的原型,如下:
// 当前是否处于创建类的阶段<BR> var initializing = false;<BR> function jClass(baseClass, prop) {<BR> // 只接受一个参数的情况 - jClass(prop)<BR> if (typeof (baseClass) === "object") {<BR> prop = baseClass;<BR> baseClass = null;<BR> }<BR> // 本次调用所创建的类(构造函数)<BR> function F() {<BR> // 如果当前处于实例化类的阶段,则调用init原型函数<BR> if (!initializing) {<BR> // 如果父类存在,则实例对象的base指向父类的原型<BR> // 这就提供了在实例对象中调用父类方法的途径<BR> if (baseClass) {<BR> this.base = baseClass.prototype;<BR> }<BR> this.init.apply(this, arguments);<BR> }<BR> }<BR> // 如果此类需要从其它类扩展<BR> if (baseClass) {<BR> initializing = true;<BR> F.prototype = new baseClass();<BR> F.prototype.constructor = F;<BR> initializing = false;<BR> }<BR> // 覆盖父类的同名函数<BR> for (var name in prop) {<BR> if (prop.hasOwnProperty(name)) {<BR> F.prototype[name] = prop[name];<BR> }<BR> }<BR> return F;<BR> };<BR>
var Person = jClass({<BR> init: function(name) {<BR> this.name = name;<BR> },<BR> getName: function() {<BR> return this.name;<BR> }<BR> });<BR> var Employee = jClass(Person, {<BR> init: function(name, employeeID) {<BR> // 调用父类的原型函数init,注意使用apply函数修改init的this指向<BR> this.base.init.apply(this, [name]);<BR> this.employeeID = employeeID;<BR> },<BR> getEmployeeID: function() {<BR> return this.employeeID;<BR> },<BR> getName: function() {<BR> // 调用父类的原型函数getName<BR> return "Employee name: " + this.base.getName.apply(this);<BR> }<BR> });<br><br> var zhang = new Employee("ZhangSan", "1234");<BR> console.log(zhang.getName()); // "Employee name: ZhangSan"<BR>
目前为止,我们已经修正了在第一章手工实现继承的种种弊端。 通过我们自定义的jClass函数来创建类和子类,通过原型方法init初始化数据, 通过实例属性base来调用父类的原型函数。
唯一的缺憾是调用父类的代码太长,并且不好理解, 如果能够按照如下的方式调用岂不是更妙:
var Employee = jClass(Person, {<BR> init: function(name, employeeID) {<BR> // 如果能够这样调用,就再好不过了<BR> this.base(name);<BR> this.employeeID = employeeID;<BR> }<BR> });<BR>
优化jClass函数
// 当前是否处于创建类的阶段<BR> var initializing = false;<BR> function jClass(baseClass, prop) {<BR> // 只接受一个参数的情况 - jClass(prop)<BR> if (typeof (baseClass) === "object") {<BR> prop = baseClass;<BR> baseClass = null;<BR> }<BR> // 本次调用所创建的类(构造函数)<BR> function F() {<BR> // 如果当前处于实例化类的阶段,则调用init原型函数<BR> if (!initializing) {<BR> // 如果父类存在,则实例对象的baseprototype指向父类的原型<BR> // 这就提供了在实例对象中调用父类方法的途径<BR> if (baseClass) {<BR> this.baseprototype = baseClass.prototype;<BR> }<BR> this.init.apply(this, arguments);<BR> }<BR> }<BR> // 如果此类需要从其它类扩展<BR> if (baseClass) {<BR> initializing = true;<BR> F.prototype = new baseClass();<BR> F.prototype.constructor = F;<BR> initializing = false;<BR> }<BR> // 覆盖父类的同名函数<BR> for (var name in prop) {<BR> if (prop.hasOwnProperty(name)) {<BR> // 如果此类继承自父类baseClass并且父类原型中存在同名函数name<BR> if (baseClass &&<BR> typeof (prop[name]) === "function" &&<BR> typeof (F.prototype[name]) === "function") {<br><br> // 重定义函数name - <BR> // 首先在函数上下文设置this.base指向父类原型中的同名函数<BR> // 然后调用函数prop[name],返回函数结果<br><br> // 注意:这里的自执行函数创建了一个上下文,这个上下文返回另一个函数,<BR> // 此函数中可以应用此上下文中的变量,这就是闭包(Closure)。<BR> // 这是JavaScript框架开发中常用的技巧。<BR> F.prototype[name] = (function(name, fn) {<BR> return function() {<BR> this.base = baseClass.prototype[name];<BR> return fn.apply(this, arguments);<BR> };<BR> })(name, prop[name]);<br><br> } else {<BR> F.prototype[name] = prop[name];<BR> }<BR> }<BR> }<BR> return F;<BR> };<BR>
var Person = jClass({<BR> init: function(name) {<BR> this.name = name;<BR> },<BR> getName: function() {<BR> return this.name;<BR> }<BR> });<BR> var Employee = jClass(Person, {<BR> init: function(name, employeeID) {<BR> this.base(name);<BR> this.employeeID = employeeID;<BR> },<BR> getEmployeeID: function() {<BR> return this.employeeID;<BR> },<BR> getName: function() {<BR> return "Employee name: " + this.base();<BR> }<BR> });<br><br> var zhang = new Employee("ZhangSan", "1234");<BR> console.log(zhang.getName()); // "Employee name: ZhangSan"<BR>
至此,我们已经创建了一个完善的函数jClass, 帮助我们在JavaScript中以比较优雅的方式实现类和继承。
在以后的章节中,我们会陆续分析网上一些比较流行的JavaScript类和继承的实现。 不过万变不离其宗,那些实现也无非把我们这章中提到的概念颠来簸去的“炒作”, 为的就是一种更优雅的调用方式。

Outils d'IA chauds

Undresser.AI Undress
Application basée sur l'IA pour créer des photos de nu réalistes

AI Clothes Remover
Outil d'IA en ligne pour supprimer les vêtements des photos.

Undress AI Tool
Images de déshabillage gratuites

Clothoff.io
Dissolvant de vêtements AI

AI Hentai Generator
Générez AI Hentai gratuitement.

Article chaud

Outils chauds

Bloc-notes++7.3.1
Éditeur de code facile à utiliser et gratuit

SublimeText3 version chinoise
Version chinoise, très simple à utiliser

Envoyer Studio 13.0.1
Puissant environnement de développement intégré PHP

Dreamweaver CS6
Outils de développement Web visuel

SublimeText3 version Mac
Logiciel d'édition de code au niveau de Dieu (SublimeText3)

Sujets chauds

Dans l'héritage de fonction, utilisez le « pointeur de classe de base » et le « pointeur de classe dérivée » pour comprendre le mécanisme d'héritage : lorsque le pointeur de classe de base pointe vers l'objet de classe dérivé, une transformation ascendante est effectuée et seuls les membres de la classe de base sont accessibles. Lorsqu’un pointeur de classe dérivée pointe vers un objet de classe de base, une conversion vers le bas est effectuée (dangereuse) et doit être utilisée avec prudence.

L'héritage et le polymorphisme affectent le couplage des classes : l'héritage augmente le couplage car la classe dérivée dépend de la classe de base. Le polymorphisme réduit le couplage car les objets peuvent répondre aux messages de manière cohérente via des fonctions virtuelles et des pointeurs de classe de base. Les meilleures pratiques incluent l'utilisation de l'héritage avec parcimonie, la définition d'interfaces publiques, l'évitement de l'ajout de données membres aux classes de base et le découplage des classes via l'injection de dépendances. Un exemple pratique montrant comment utiliser le polymorphisme et l'injection de dépendances pour réduire le couplage dans une application de compte bancaire.

Tutoriel JavaScript : Comment obtenir le code d'état HTTP, des exemples de code spécifiques sont requis Préface : Dans le développement Web, l'interaction des données avec le serveur est souvent impliquée. Lors de la communication avec le serveur, nous devons souvent obtenir le code d'état HTTP renvoyé pour déterminer si l'opération a réussi et effectuer le traitement correspondant en fonction de différents codes d'état. Cet article vous apprendra comment utiliser JavaScript pour obtenir des codes d'état HTTP et fournira quelques exemples de codes pratiques. Utilisation de XMLHttpRequest

Conseils de débogage des erreurs d’héritage : assurez-vous que les relations d’héritage sont correctes. Utilisez le débogueur pour parcourir le code et examiner les valeurs des variables. Assurez-vous d'utiliser correctement le modificateur virtuel. Examinez le problème des diamants d'héritage causé par l'héritage caché. Recherchez les fonctions virtuelles pures non implémentées dans les classes abstraites.

Introduction à la méthode d'obtention du code d'état HTTP en JavaScript : Dans le développement front-end, nous devons souvent gérer l'interaction avec l'interface back-end, et le code d'état HTTP en est une partie très importante. Comprendre et obtenir les codes d'état HTTP nous aide à mieux gérer les données renvoyées par l'interface. Cet article explique comment utiliser JavaScript pour obtenir des codes d'état HTTP et fournit des exemples de code spécifiques. 1. Qu'est-ce que le code d'état HTTP ? Le code d'état HTTP signifie que lorsque le navigateur lance une requête au serveur, le service

Explication détaillée de l'héritage de fonction C++ : Maîtriser la relation entre "is-a" et "has-a" Qu'est-ce que l'héritage de fonction ? L'héritage de fonction est une technique en C++ qui associe des méthodes définies dans une classe dérivée à des méthodes définies dans une classe de base. Il permet aux classes dérivées d'accéder et de remplacer les méthodes de la classe de base, étendant ainsi les fonctionnalités de la classe de base. Relations « est-un » et « a-un » Dans l'héritage de fonctions, la relation « est-un » signifie que la classe dérivée est un sous-type de la classe de base, c'est-à-dire que la classe dérivée « hérite » des caractéristiques et du comportement de la classe de base. La relation « possède un » signifie que la classe dérivée contient une référence ou un pointeur vers l'objet de classe de base, c'est-à-dire que la classe dérivée « possède » l'objet de classe de base. SyntaxeVoici la syntaxe permettant d'implémenter l'héritage de fonction : classDerivedClass:pu

L'héritage de fonctions C++ ne doit pas être utilisé dans les situations suivantes : Lorsqu'une classe dérivée nécessite une implémentation différente, une nouvelle fonction avec une implémentation différente doit être créée. Lorsqu'une classe dérivée ne nécessite pas de fonction, elle doit être déclarée comme une classe vide ou utiliser des fonctions membres de la classe de base privées et non implémentées pour désactiver l'héritage des fonctions. Lorsque les fonctions ne nécessitent pas d'héritage, d'autres mécanismes (tels que des modèles) doivent être utilisés pour réaliser la réutilisation du code.

Qu'est-ce que la programmation orientée objet ? La programmation orientée objet (POO) est un paradigme de programmation qui résume les entités du monde réel en classes et utilise des objets pour représenter ces entités. Les classes définissent les propriétés et le comportement des objets, et les objets instancient les classes. Le principal avantage de la POO est qu’elle rend le code plus facile à comprendre, à maintenir et à réutiliser. Concepts de base de la POO Les principaux concepts de la POO incluent les classes, les objets, les propriétés et les méthodes. Une classe est le modèle d'un objet, qui définit ses propriétés et son comportement. Un objet est une instance d’une classe et possède toutes les propriétés et comportements de la classe. Les propriétés sont les caractéristiques d'un objet pouvant stocker des données. Les méthodes sont des fonctions d'un objet qui peuvent opérer sur les données de l'objet. Avantages de la POO Les principaux avantages de la POO sont les suivants : Réutilisabilité : la POO peut rendre le code plus
