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

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日

相关文章

  • win10蓝屏笑脸提示重启怎么办 蓝屏哭脸和笑脸提示重启的解决方法步骤

    针对“win10蓝屏笑脸提示重启怎么办 蓝屏哭脸和笑脸提示重启的解决方法步骤”的问题,我为您提供以下攻略。 前置知识 在查看本攻略之前,您需要了解以下基础知识: 蓝屏:指在Windows系统中出现的蓝色屏幕死机现象。 笑脸:Windows系统蓝屏错误提示的一种图案,表示在出现错误时系统已自动重启恢复正常。 哭脸:Windows系统蓝屏错误提示的一种图案,表示…

    other 2023年6月27日
    00
  • bootstrap table表格插件之服务器端分页实例代码

    下面是关于“bootstrap table表格插件之服务器端分页实例代码”的攻略。 什么是bootstrap table Bootstrap Table是一个基于jQuery和Bootstrap的jQuery插件,可以在网页中添加现代和简单的表格视图,功能强大、灵活易用。 什么是服务器端分页 服务器端分页就是当表格中数据较多时,不将所有数据一次性加载,而是通…

    other 2023年6月27日
    00
  • Linux常用的磁盘管理及文件目录管理命令总结

    Linux常用的磁盘管理命令总结 磁盘分区和格式化 fdisk 命令:用于对磁盘进行分区操作。 示例: fdisk /dev/sda mkfs 命令:用于对分区进行格式化操作。 示例: mkfs -t ext4 /dev/sda1 磁盘挂载和卸载 mount 命令:用于挂载文件系统。 示例: mount /dev/sda1 /mnt umount 命令:用于…

    other 2023年6月27日
    00
  • win7中格式化C盘的命令行是什么

    下面是在Windows 7中格式化C盘的完整攻略,步骤如下: 1.打开命令提示符窗口。 在Windows 7中,可以通过以下方法打开命令提示符窗口: 点击“开始”菜单,在搜索栏中输入“cmd”,然后按Enter键。 使用快捷键Win+R,输入“cmd”,然后按Enter键。 2.以管理员身份运行命令提示符。 在开始菜单中找到“命令提示符”,右键点击并选择“以…

    other 2023年6月26日
    00
  • C语言动态内存管理的原理及实现方法

    C语言动态内存管理的原理及实现方法 动态内存管理是C语言中非常重要的概念,它允许程序在运行时动态地分配和释放内存。本文将详细讲解C语言动态内存管理的原理及实现方法,并提供两个示例说明。 原理 C语言中的动态内存管理是通过以下几个函数来实现的: malloc(size_t size):用于分配指定大小的内存块,并返回指向该内存块的指针。 calloc(size…

    other 2023年7月31日
    00
  • Linux查找处理文件名后包含空格的文件(两种方法)

    Linux查找处理文件名后包含空格的文件(两种方法) 在Linux系统中,如果文件名中含有空格,那么会不方便我们的操作。因此需要查找和处理这些文件名包含空格的文件。本节介绍两种方法。 方法一(使用find命令) find命令是Linux中非常常用的命令之一,可以用于查找文件和目录。find命令可以使用-name选项来查找匹配指定模式的文件名,可以使用-exe…

    other 2023年6月26日
    00
  • java中array/list/map/object与json互相转换详解(转载)

    Java中Array/List/Map/Object与JSON互相转换详解(转载) 在Java中,我们常常需要进行各种类型之间的相互转换,最常见的就是把Java中的数据结构与JSON格式进行相互转换。为此,我们需要借助一些工具类库来完成,这篇文章就将详细讲解如何使用这些工具类库进行相应的转换操作。 使用Jackson库进行转换 在Java中,最常用的处理JS…

    其他 2023年3月28日
    00
  • 朋友网手机客户端下载页面的设计分享(图文)

    来一步一步讲解一下“朋友网手机客户端下载页面的设计分享(图文)”的完整攻略。 1. 确定页面设计的方向和主题 在设计“朋友网手机客户端下载页面”时,需要确定手机客户端的主题和方向,包括页面风格、配色方案、字体、布局等。具体的方向可以通过市场调研和竞品研究来确定,设计时要考虑用户群的需求和关注点,以及如何通过设计来突出产品的优势和特点。 2. 设计页面结构和布…

    other 2023年6月25日
    00
合作推广
合作推广
分享本页
返回顶部