JavaScript系列之面向对象
作者: __ mxin
来源:SegmentFault 思否社区
什么是面向对象?
概念起源
面向对象程序设计的雏形,早在1960年的 Simula 语言中即可发现,当时的程序设计领域正面临着一种危机:在软硬件环境逐渐复杂的情况下,软件如何得到良好的维护?面向对象程序设计在某种程度上通过强调可重复性解决了这一问题。--引自维基百科
面向对象(OOP)
面向对象编程是以抽象方式创建基于现实世界模型的一种编程模式;在此种模式下将对象作为拥有明确职责的基本单元,对象本身可以接收消息,处理数据以及发送数据给其他对象;OOP 有以下几点特性:
Inheritance 继承 子类可以继承父类的特征(属性和方法) Encapsulation 封装 是一种把数据和相关的方法绑定在一起使用的方法 Abstraction 抽象 结合复杂的继承,属性,方法的对象能够模拟现实的模型 Polymorphism 多态 不同类可以定义相同的属性和方法
基于原型和基于类
基于原型的编程提倡我们去关注一系列对象实例的行为,而后才去关心如何将这些对象,划分到最近的使用方式相似的原型对象,而不是将它们分成类
基于原型的面向对象系统通过复制的方式来创建新对象一些语言的实现中,还允许复制一个空对象,这实际上就是创建一个全新的对象
原型系统的“复制操作”有两种实现思路:
并不真的去复制一个原型对象,而是使得新对象持有一个原型的引用 切实地复制对象,从此两个对象再无关联
基于类的编程提倡使用一个关注分类和类之间关系开发模型
在这类语言中,总是先有类,再从类去实例化一个对象 类与类之间又可能会形成继承、组合等关系 类又往往与语言的类型系统整合,形成一定编译时的能力
基于原型和基于类的区别
类
面向对象语言中都有类的概念,如 Java、C++ 都是基于类的语言,而 JavaScript 是基于原型的语言,在旧的 ES 版本没有类的定义,所以我们要做一系列模拟类的操作去定义类
ES6中提供了 class ,使我们不用再去模拟类的操作,但是这个 class 并非真正的类,其实是基于原型继承方式的语法糖
function Person() { }
// 或
var Person = function(){ }
// 或
class Person{ }
构造函数
在实例化时构造函数被调用 (也就是对象实例被创建时)
构造函数是对象中的一个方法
在 JavaScript 中函数就可以作为构造器使用,因此不需要特别地定义一个构造器方法,每个声明的函数都可以在实例化后被调用执行(在ES6规范中 class 内的 constructor 起到同样的作用)
构造器常用于给对象的属性赋值或者为调用函数做准备
function Person(name) {
this.name = name
alert('Person instantiated')
}
// 或
class Person{
constructor(name){
this.name = name
alert(`Person instantiated`)
}
}
var person = new Person('mxin');
JavaScript对象
对象在计算机中是一个具有唯一标识的内存地址,参考 Grandy Booch 的《面向对象分析与设计》来看,对象具有下列几个特点:
唯一标识性:即使完全相同的两个对象,也并非同一个对象 状态:对象具有状态,同一对象可能处于不同状态之下 行为:即对象的状态,可能因为它的行为产生变迁
唯一标识性
function Person() { }
var person_1 = new Person();
var person_2 = new Person();
console.log(person_1 == person_2) // false
const obj_1 = { a: 1 };
const obj_2 = { a: 1 };
console.log(obj_1 == obj_2); // false
状态和行为
分别对应了属性和方法这两个概念,在 JavaScript 被抽象为属性,其属性可以为任何类型
const obj = {
name: 'mxin',
age: 99,
getInfo() {
return `name: ${this.name} , age: ${this.age}`
}
}
继承
JavaScript 通过将构造函数与原型对象相关联的方式来实现继承,下面将以 ES5 及 ES6 两种方式展示
ES5
// 定义Person构造器
function Person(firstName) {
this.firstName = firstName;
}
// 在Person.prototype中加入方法
Person.prototype.walk = function(){
console.log("I am walking!");
};
Person.prototype.sayHello = function(){
console.log("Hello, I'm " + this.firstName);
};
// 定义Student构造器
function Student(firstName, subject) {
// 调用父类构造器, 确保(使用Function#call)"this" 在调用过程中设置正确
Person.call(this, firstName);
// 初始化Student类特有属性
this.subject = subject;
};
// 建立一个由Person.prototype继承而来的Student.prototype对象.
// 注意: 常见的错误是使用 "new Person()"来建立Student.prototype.
// 这样做的错误之处有很多, 最重要的一点是我们在实例化时
// 不能赋予Person类任何的FirstName参数
// 调用Person的正确位置如下,我们从Student中来调用它
Student.prototype = Object.create(Person.prototype); // See note below
// 设置"constructor" 属性指向Student
Student.prototype.constructor = Student;
// 更换"sayHello" 方法
Student.prototype.sayHello = function(){
console.log("Hello, I'm " + this.firstName + ". I'm studying " + this.subject + ".");
};
// 加入"sayGoodBye" 方法
Student.prototype.sayGoodBye = function(){
console.log("Goodbye!");
};
// 测试实例:
var student = new Student("Janet", "Applied Physics");
student.sayHello(); // "Hello, I'm Janet. I'm studying Applied Physics."
student.walk(); // "I am walking!"
student.sayGoodBye(); // "Goodbye!"
// Check that instanceof works correctly
console.log(student instanceof Person); // true
console.log(student instanceof Student); // true
ES6
class Person {
constructor(firstName) {
this.firstName = firstName
}
walk() {
console.log(`I am walking!`)
}
sayHello() {
console.log(`Hello, I'm ${this.firstName}`)
}
}
class Student extends Person {
constructor(firstName, subject) {
super(firstName)
this.subject = subject
}
sayHello() {
console.log(`Hello, I'm ${this.firstName}. I'm studying ${this.subject}.`)
}
sayGoodBye() {
console.log(`Goodbye!`)
}
}
// 测试实例:
const student = new Student(`Janet`, `Applied Physics`)
student.sayHello() // "Hello, I'm Janet. I'm studying Applied Physics."
student.walk() // "I am walking!"
student.sayGoodBye() // "Goodbye!"
// Check that instanceof works correctly
console.log(student instanceof Person) // true
console.log(student instanceof Student) // true
封装
上面例子中Student类虽然不需要知道 Person 类的 walk() 方法是如何实现的,但是仍然可以使用这个方法,Student类不需要明确地定义这个方法,除非我们想改变它,这就叫做封装
抽象
抽象是允许模拟工作问题中通用部分的一种机制,这可以通过继承或组合( JavaScript 让类的实例是其他对象的属性值来实现组合)来实现:
JavaScript Function 类继承自 Object 类(继承)
Function.prototype 的属性是一个 Object 实例(组合)
var foo = function(){};
console.log(foo instanceof Function) // true
console.log(foo.prototype instanceof Object) // true
多态
就像所有定义在原型属性内部的方法和属性一样,不同的类可以定义具有相同名称的方法,方法是作用于所在的类中(并且这仅在两个类不是父子关系时成立)
总结
结合上面的知识,可以了解到JavaScript一种基于原型设计的面向对象语言,而且JavaScript的对象操作灵活,是具有高度动态性的属性集合
可以深入了解一下基于原型的设计思路以及JavaScript对象模型的细节
参考资料:
JavaScript面向对象简介:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Introduction_to_Object-Oriented_JavaScript 对象模型的细节:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Details_of_the_Object_Model 多继承:https://zhuanlan.zhihu.com/p/34693209