JavaScript 继承 封装 多态实现及原理详解

yizhihongxing

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"

以上代码中,我们定义了 PersonStudent 两个构造函数,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"

以上代码中,我们定义了 PersonStudent 两个构造函数,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"

以上代码中,我们定义了 PersonStudent 两个构造函数,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 的构造函数中,然后通过构造函数的公共方法 getAgesetAge 来实现对 _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"

以上代码中,我们定义了 VehicleCar 两个类,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); // 输

以上代码中,我们定义了 VehicleCarTruck 三个类,其中 CarTruck 都继承自 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

以上代码中,我们定义了 AnimalMammalBirdBat 四个类,并在其之间建立了复杂的继承关系。其中,MammalBird 继承自 AnimalBat 继承自 MammalBird。我们通过在 Bat 的构造函数中分别调用 MammalBird 的构造函数实现多重继承,从而实现了 Bat 类对 MammalBird 的属性和方法的继承。

示例二:使用 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技术站

(0)
上一篇 2023年6月25日
下一篇 2023年6月25日

相关文章

  • 聊聊Java 成员变量赋值和构造方法谁先执行的问题

    聊聊Java 成员变量赋值和构造方法谁先执行的问题 在Java中,成员变量赋值和构造方法的执行顺序是有一定规律的。了解这个问题对于理解对象的创建和初始化过程非常重要。下面是详细的攻略: 1. 成员变量赋值 在Java中,成员变量可以在声明时进行初始化,也可以在构造方法中进行赋值。当成员变量在声明时进行初始化时,它们会在构造方法执行之前被赋值。如果成员变量没有…

    other 2023年8月6日
    00
  • 教你认清六种网络特殊用途IP地址

    教你认清六种网络特殊用途IP地址 在网络中,有一些特殊用途的IP地址被保留用于特定的目的。这些IP地址不用于一般的主机通信,而是用于特殊的网络功能。下面是六种常见的网络特殊用途IP地址及其用途的详细说明: 1. 0.0.0.0 这个IP地址被称为“未指定地址”或“通配地址”。它用于表示当前主机的任何IP地址,或者用于表示目标地址未知的情况。在网络编程中,0.…

    other 2023年7月29日
    00
  • 有备而来 让系统工作区连接顺风顺水

    有备而来 – 让系统工作区连接顺风顺水 如果你正在使用Linux或Mac OS X,那么你很幸运,因为使用SSH连接到其他电脑或服务器上的工作区非常简单。然而,如果你正在使用Windows操作系统,那么使用SSH连接可能会有些困难。在这篇文章中,我们将提供使用SSH连接工作区的完整攻略,以便你的工作区连接顺风顺水。 准备工作 在你开始使用SSH连接工作区之前…

    other 2023年6月27日
    00
  • ansys17.0详细安装图文教程

    以下是关于如何安装ANSYS 17.0的详细攻略: 步骤一:下载ANSYS 17.0安装文件 从ANSYS官网下载ANSYS 17.0安装文件。您需要登录到ANSYS官网并购买许可证才能下载安装文件。 步骤二:解压缩安装文件 将下载的安装文件解压缩到您选择的目录中。您可以使用WinRAR或7-Zip等解压缩工具来解压缩文件。 步骤三:运行安装程序 在解压后的…

    other 2023年5月7日
    00
  • Java 10的10个新特性总结

    Java 10的10个新特性总结 Java 10是Java技术的一个重要更新版本,它增加了许多新特性和改进,以下是Java 10的10个新特性: 局部变量的类型推导 Java 10中引入了var关键字,可以在局部变量声明时自动推导出其类型,使得代码更加简洁、可读性更高。例如: var number = 10; var str = "hello wo…

    other 2023年6月26日
    00
  • Python面向对象中的封装详情

    当我们使用Python面向对象编程时,封装就是隐藏了类的内部细节,不让外部代码随意修改类的属性和方法,让对象的使用更加安全和方便。接下来,我将详细讲解Python面向对象中的封装。 封装的基本原则 在Python面向对象中,封装主要体现在以下几个方面: 属性和方法的访问权限控制 使用属性访问器来访问对象的属性 将对象的复杂实现细节隐藏起来 封装的基本原则是:…

    other 2023年6月25日
    00
  • MySQL数据库grant授权命令

    下面是 MySQL 数据库 grant 授权命令的完整攻略,包括授权命令的语法、使用方法和两个示例说明。 授权命令的语法 MySQL 数据库 grant 授权命令的语法如下: GRANT privileges ON database.table TO ‘user’@’host’ IDENTIFIED BY ‘password’; 其中,privileges …

    other 2023年5月5日
    00
  • ZeroMQ接口函数之 :zmq_disconnect – 断开一个socket的连接

    ZeroMQ接口函数之 :zmq_disconnect – 断开一个socket的连接 zmq_disconnect(void *socket, const char *endpoint)函数用于断开一个已建立连接的socket。这个函数的调用方式如下: int zmq_disconnect (void *socket, const char *endpoi…

    其他 2023年3月28日
    00
合作推广
合作推广
分享本页
返回顶部