JavaScript的面向对象编程
面向对象编程 —— Object Oriented Programming,简称 OOP ,是一种编程开发思想。 它将真实世界各种复杂的关系,抽象为一个个对象,然后由对象之间的分工与合作,完成对真实世界的模拟。面向对象的特性:
封装性
继承性
多态性抽象
JavaScript的面向对象编程和大多数其他语言如Java、C#的面向对象编程都不太一样。如果你熟悉Java或C#,很好,你一定明白面向对象的两个基本概念:
类(class):类是对象的类型模板,例如,定义Student类来表示学生,类本身是一种类型,Student表示学生类型,但不表示任何具体的某个学生;
实例(Instance):实例是根据类创建的对象,例如,根据Student类可以创建出xiaoming、xiaohong、xiaojun等多个实例,每个实例表示一个具体的学生,他们全都属于Student类型。
类和实例是大多数面向对象编程语言的基本概念。
JavaScript面向对象术语
【参见 JavaScript面向对象简介 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Introduction_to_Object-Oriented_JavaScript 术语 部分】
Namespace 命名空间
允许开发人员在一个独特,应用相关的名字的名称下捆绑所有功能的容器。
Class 类
定义对象的特征。它是对象的属性和方法的模板定义。
Object 对象
类的一个实例。
Property 属性
对象的特征,比如颜色。
【又见 Property (JavaScript) Property (JavaScript) - MDN Web Docs Glossary: Definitions of Web-related terms | MDN
JavaScript property是对象的特征(characteristic),通常描述与数据结构相关联的attributes。
有两种属性(properties):
实例属性(Instance properties)保存特定于给定对象实例的数据。
静态属性(Static properties)保存在所有对象实例之间共享的数据。】
Method 方法
对象的能力,比如行走。
Constructor 构造函数
对象初始化的瞬间,被调用的方法。通常它的名字与包含它的类一致。
Inheritance 继承
一个类可以继承另一个类的特征。
Encapsulation 封装
一种把数据和相关的方法绑定在一起使用的方法。
Abstraction 抽象
结合复杂的继承,方法,属性的对象能够模拟现实的模型。
Polymorphism 多态
多意为「许多」,态意为「形态」。不同类可以定义相同的方法或属性。
【关于 JavaScript面向对象,可参见:适合初学者的JavaScript面向对象 JavaScript 中的类 - 学习 Web 开发 | MDN 】
早期的ECMAScript-262 (也称为ECMAscript)标准把对象定义为:无序属性的集合,其属性可以包含基本值、对象或者函数。 严格来讲,这就相当于说对象是一组没有特定顺序的值。对象的每个属性或方法都有一个名字,而每个名字都映射到一个值。
每个对象都是基于一个引用类型创建的,这些类型可以是系统内置的原生类型,也可以是开发人员自定义的类型。
简单地说,早期的ECMAScript-262 (ECMAscript)标准不区分类和实例的概念,而是通过原型(prototype)和构造函数Constructor)来实现面向对象编程。
【一文读懂 JavaScript 和 ECMAScript 的区别
一文读懂 JavaScript 和 ECMAScript 的区别 - OSCHINA - 中文开源技术交流社区 】
ECMAScript 6,也称为ECMAScript 2015或ECMA-262 edition 6,简称ES6,它是 ECMA-262 标准的第六个版本,其特点是对 ECMAScript 规范有着显著的变化和改进,如引进了类(class)。ES6之前,javascript本质上不能算是一门面向对象的编程语言,因为它对于封装、继承、多态这些面向对象语言的特点并没有在语言层面上提供原生的支持。它引进了原型(prototype)的概念,可以让我们以另一种方式模仿类,并通过原型链的方式实现了父类子类之间共享属性的继承以及身份确认机制。正是由于javascript本身对面向对象编程没有一个语言上的支持标准,所以才有了五花八门、令人眼花缭乱的“类继承”的代码。所幸,es6增加了class、extends、static等关键字用以在语言层面支持面向对象。我们先列举出es6之前常见的几种继承方案,然后再来一探es6的类继承机制,最后再讨论下javascript多态。
JavaScript 面向对象的设计思想是:
☆早期技术——ES6之前的方式
JavaScript 面向对象的设计思想是:
抽象出构造函数Constructor)
根据构造函数Constructor) 创建 Instance(实例)
指挥 Instance(实例) 得结果
☆新技术——引进了类(class)后的方式
抽象出 Class(类)
根据 Class(类)或创建 Instance(实例)
指挥 Instance(实例) 得结果
ES6之前
ES6之前,JavaScript中没有类的概念,创建一个对象只要定义一个该对象的构造函数并通过它创建对象即可 。
创建一个Card(名片)对象,每个对象又有这些属性:name(名字)、address(地址)、phone(电话)。创建名片对象的构造函数 代码如下:
function Card( _name, _address, _phone ) // 定义构造函数
{
this.name=_name; // 初始化“名字”属性
this.address=_address; // 初始化“地址”属性
this.phone=_phone; // 初始化“电话”属性
}
用于输出卡片上的信息 的方法如下:
function printCard() //打印信息
{
line1="Name:"+this.name+"<br>\n"; // 读取name
line2="Address:"+this.address+"<br>\n"; // 读取address
line3="Phone:"+this.phone+"<br>\n" // 读取phone
document.writeln(line1,line2,line3);
}
实例化对象
Tom=new Card("张三","某市区广达路 123好","电话:0633-6668888"); // 创建名片Tom.printCard(); // 输出名片信息
完整代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>名片</title>
</head>
<body>
<script>
function Card(name,address,phone)
{
this.name=name; //初始化名片信息
this.address=address;
this.phone=phone;
this.printCard=function() // 创建printCard函数的定义
{
line1="Name:"+this.name+"<br>\n"; // 输出名片信息
line2="Address:"+this.address+"<br>\n";
line3="Phone:"+this.phone+"<br>\n"
document.writeln(line1,line2,line3);
}
}
Tom=new Card("张三","某市区广达路 123好","电话:0633-6668888"); // 创建名片
Tom.printCard() // 输出名片信息
</script>
</body>
</html>
保存文件名为:名片.html
运行之,按F12 打开控制台,显示如下图:
对象的废除
把对象的引用设置为null,对象就会被清除。如:
obj = null; //将obj对象清除
ES6之前,JavaScript通过模仿类实现继承,方式很多,在此进简单介绍原型继承、借用构造函数的方式继承。
原型继承的例子
// 父类
function Persion(name,age){
this.name = name;
this.age = age;
}
// 父类的原型对象属性
Persion.prototype.id = 10;
// 子类
function Boy(sex){
this.sex = sex;
}
// 继承实现
Boy.prototype = new Persion('c5',27);
var b = new Boy();
console.log(b.name)// c5
console.log(b.id)//10
运行之,参见下图:
借用构造函数的方式继承的例子
// 父类
function Persion(name,age){
this.name = name;
this.age = age;
}
// 父类的原型对象属性
Persion.prototype.id = 10;
// 子类
function Boy(name,age,sex){
//call apply 实现继承
Persion.call(this,name,age);
this.sex = sex;
}
var b = new Boy('c5',27,'男');
console.log(b.name)// c5
console.log(b.id)//undinfind 父类的原型对象并没有继承
运行之,参见下图:
引进了类(class)后
新的关键字class从ES6开始正式被引入到JavaScript中。class的目的就是让定义类更简单。
JavaScript类 参考 类 - JavaScript | MDN
我们先回顾前面用函数实现Student修改版的方法:
function Student(name) {
this.name = name;
}
Student.prototype.hello = function () {
alert('Hello, ' + this.name + '!');
}
如果用新的class关键字来编写Student,可以这样写:
class Student {
constructor(name) {
this.name = name;
}
hello() {
alert('Hello, ' + this.name + '!');
}
}
创建一个Student对象代码和前面完全一样:
var xiaoming = new Student('小明');
xiaoming.name; // "小明"
xiaoming.hello(); // Hello, 小明!
对于class的继承,直接通过extends来实现。我们从Student派生一个PrimaryStudent:
class PrimaryStudent extends Student {
constructor(name, grade) {
super(name); // 记得用super调用父类的构造方法!
this.grade = grade;
}
myGrade() {
alert('I am at grade ' + this.grade);
}
}
注意PrimaryStudent的定义也是class关键字实现的,而extends则表示原型链对象来自Student。
关于JavaScript继承与原型链可参见 继承与原型链 - JavaScript | MDN
关于ES6中的类可参见ES6中的类(Class)-CSDN博客
就此打住,不深入介绍了。下面给出比较完整的例子
例1、早期的封装、继承与多态的例子,代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>js早期的封装、继承与多态</title>
</head>
<body>
<script>
window.onload = function() {
// 封装
var Book = function(id, name, price) {
// 私有变量(在函数内部定义,函数外部访问不到,实例化之后实例化的对象访问不到)
var num = 1
var id = id
function privateFunction() {
console.log('this is private')
}
// protected(可以访问到函数内部的私有属性和私有方法,在实例化之后就可以对实例化的类进行初始化拿到函数的私有属性)
this.getNum = function() {
console.log(num)
}
this.getFunction = function() {
privateFunction()
}
//public(实例化的之后,实例化的对象就可以访问到了~)
this.name = name
this.copy = function() {
console.log('this is public')
}
}
//在Book的原型上添加的方法实例化之后可以被实例化对象继承
Book.prototype.proFunction = function() {
console.log('this is proFunction')
}
//在函数外部通过.语法创建的属性和方法,只能通过该类访问,实例化对象访问不到
Book.setTime = function() {
console.log('this is new time')
}
var book1 = new Book('B11', '悲惨世界', '$99')
// 通过this创建的公共属性和方法,实例化的时候会复制一遍,所以可以访问到
console.log(book1.name)
book1.copy()
// 通过protected的getNum来访问Book的私有变量
book1.getNum()
book1.getFunction()
// 直接通过实例来访问私有变量是无法访问的
// console.log(book1.num)
// book1.privateFunction()
// 通过prototype创建的方法可以在实例中访问
book1.proFunction()
// 直接在构造函数中.的方法实例是无法访问的
// book1.setTime()
// 只能通过构造函数来访问
Book.setTime()
// privateFunction是无法访问的
// Book.privateFunction()
// 继承
var SuperClass = function() {
var id = 1
this.name = ['javascript']
this.superValue = function() {
console.log('superValue is true')
console.log(id)
}
}
SuperClass.prototype.getSuperValue = function() {
return this.superValue()
}
var SubClass = function() {
this.subValue = function() {
console.log('this is subValue ')
}
}
//继承父类
SubClass.prototype = new SuperClass()
SubClass.prototype.getSubValue = function() {
return this.subValue()
}
var sub = new SubClass()
var sub2 = new SubClass()
console.log(sub)
// 多态
function Add() {
function zero() {
return 0
}
function one(id) {
return 0 + id
}
function two(id, name) {
return 0 + id + name
}
this.print = function() {
var arg = arguments
var len = arg.length
switch(len) {
case 0: {
return zero()
}
case 1: {
return one(arg[0])
}
case 2: {
return two(arg[0], arg[1])
}
}
}
}
var add = new Add()
console.log(add.print())
console.log(add.print(1))
console.log(add.print(1, 2))
}
</script>
</body>
</html>
保存文件名为:早期的封装、继承与多态.html
运行之,按F12 打开控制台,显示如下图:
例2、ES6的封装、继承与多态的例子,代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>js之ES6的封装、继承与多态</title>
</head>
<body>
<script>
window.onload = function() {
// 封装
class School {
// 构造器:创建对象完成初始化操作
constructor(id, name) {
this.id = id
this.name = name
}
// 实例方法
schoolName() {
console.log(this.name)
}
// 类方法
static schoolOnly() {
console.log('我是类方法只可通过函数本身调用')
}
}
// 继承
class Student extends School {
constructor(sId, sName, id, name) {
super(id, name)
this.sId = sId
this.sName = sName
}
studentName() {
console.log(this.sName)
}
say() {
console.log('I am a student')
}
}
// 多态
class Teacher extends School {
constructor(tId, tName, id, name) {
super(id, name)
this.tId = tId
this.tName = tName
}
TeacherName() {
console.log(this.tName)
}
say() {
console.log('I am a teacher')
}
}
// 测试
let school = new School(1, '第一小学')
let student = new Student(10, 'Daming', 1, '第一小学')
let teacher = new Teacher(100, 'MrLi', 1, '第一小学')
console.log(student)
console.log(teacher)
student.studentName()
student.schoolName()
student.say()
teacher.say()
School.schoolOnly()
}
</script>
</body>
</html>
保存文件名为:ES6的封装、继承与多态.html
运行之,按F12 打开控制台,显示如下图:
若想进一步学习 JavaScript 面向对象编程,可参见:
理解JS面向对象编程思想 https://juejin.cn/post/6844904082210045965
JavaScrip面向对象编程
https://www.liaoxuefeng.com/wiki/1022910821149312/1023022126220448
Javascript的继承与多态