2.0 面向对象编程
面向对象编程(Object-oriented programming,OOP)是一种程序设计范型。它将对象作为程序的基本单元,将程序和数据封装其中,以提高程序的重用性、灵活性和扩展性。
- 在
JavaScript中函数也是一种对象。 JavaScript是一种基于原型prototype的语言。
1. 封装
- 面向对象编程就是将你的需求抽象成一个对象。然后针对这个对象分析其他特征(属性)与动作(方法)。这个对象我们称为类。
- 面向对象编程的一个特点就是封装。
- 封装就是把你需要的功能放在一个对象里。
(面向对象每个人的理解是不一样的,在其他编程语言中,一般是先创建类,在实例化类为对象,类也很好理解===水果是一个类,那具体一个苹果就是一个对象实例。但在 js 中原型链是这个语言非常重要的特性,所以可能有所不同。)
2. 创建一个类
- 在 js 中创建一个类很容易,首先声明一个函数保存在一个变量里。
- 代表类的变量名一般第一个字母大写。
- 然后在这个函数(类)的内容通过
this变量添加属性或 者方法来实现对类添加属性或者方法。 this指(函数内部自带的一个变量,用于指向当前这个对象)
创建一个类
const Person = function (id, name, age) {
this.id = id
this.name = name
this.age = age
}
使用类, 实例化类成具体某一个对象
let person = new Person(1, '文山js学习笔记', 66)
console.log(person.id) // 1
console.log(person.name) // 文山js学习笔记
console.log(person.age) // 66
3. 函数级作用域
- 函数级作用域:声明在函数内部的变量以及方法在外界是访问不到的,这样就可以创创建类的私有变量和私有方法。
- 通过函数内部 this 创建的属性与方法,在类创建对象时,每个新创建的对象都会带一份并且外部可以访问。
- 通过
this创建的属性,可以看做是对象公有属性与对象公有方法。 - 通过
this创建的方法,不但可以访问这些对象的公有属性与公有方法,还能访问到类(创建时)或对象自身的私有属性和私有方法,由于这种方法权利过大,我们称之为特权方法。
let Book = function (id, name, price) {
// 私有属性
var num = 1
// 私有方法
function checkId() {}
// 特权方法
this.getName = function () {
return name
}
this.getPrice = function () {
return price
}
this.setName = function (newName) {
name = newName
}
this.setPrice = function (newPrice) {
price = newPrice
}
// 对象公有属性
this.id = id
// 对象公有方法
this.copy = function () {
console.log('copy')
}
// 构造器
this.setName(name)
this.setPrice(price)
}
// 类静态公有属性(对象不能访问)
Book.isChinese = true
// 类静态公有方法(对象不能访问)
Book.resetTime = function () {
console.log('new Time')
}
Book.prototype = {
// 公有属性
isJSBook: false,
// 公有方法
display: function () {},
}
const b = new Book(125, 'js设计模式笔记', 120)
console.log(b)
console.log(b.num) // undefined
console.log(b.isJSBook) // false
console.log(b.id) // 11
console.log(b.isChinese) // undefined
// 类自身的静态公有属性方法可以通过自身访问
console.log(Book.isChinese) // true
console.log(Book.resetTime()) // new Time
要点
- 类的定义:函数即是类。类变量名称首字母大写。
- 私有属性与私有方法: 函数类中直接声明的变量和方法为私有的。
- 对象公有的:函数类中通过 this 声明的变量和方法,是未来新建对象时公有的。
- 特权方法:在类中通过 this 创建的方法,可以访问私有属性和私有方法有可能访问公有属性和方法,有权利太大我们称之为特权方法。
- 构造器:运行特权方法进行初始化对象数据为类的构造器。
- 类静态公有属性与方法:就是类有而对象没有,通过点直接创建的属性和方法是只有类有的,通过该类创建的对象是没有的。如:
Book.isChinese - 公有属性和公有方法:就是类与对象都有的。是通过设置类原型 prototype 中的对象内容实现的。如:
Book.prototype = {isJSBook: false,xxx:true}
4. 闭包实现
- 有时我们经常将类的静态变量通过闭包来实现。
- 闭包是有权访问另外一个函数作用域中变量的函数,即在一个函数内部创建另外一个函数。
一个闭包类
// 利用闭包实现
var Book = (function () {
// 静态私有变量
var bookNum = 0
// 静态私有方法
function checkBook(name) {}
// 创建类
function _book(newId, newName, newPrice) {
// 私有变量
var name, price
// 私有方法
function checkID(id) {}
// 特权方法
this.getName = function () {}
this.getPrice = function () {}
this.setName = function () {}
this.setPrice = function () {}
// 公有属性
this.id = newId
// 公有方法
this.copy = function () {}
bookNum++
if (bookNum > 100) {
throw new Error('我们仅出版100本书。')
}
// 构造器
this.setName(newName)
this.setPrice(newPrice)
}
// 在闭包中设置原型对象,这样看起来更新一个整体
_book.prototype = {
// 静态公有属性
isJSBook: false,
// 静态公有方法
display: function () {},
}
// 返回类
return _book
})()
console.log(new Book())
console.log(window.title)
console.log(window.time)
console.log(window.type)
5. **忘记 new 处理 **
var Book = function (title, time, type) {
// 判断执行过程中this是否是当前这个对象(如果是说明用new创建的)
if (this instanceof Book) {
this.title = title
this.time = time
this.type = type
// 否则重新创建这个对象
} else {
return new Book(title, time, type)
}
}
var book = Book('javascript', '2024', 'js')
console.log(book.title) // javascript
console.log(book.time) // 2024
console.log(book.type) // js
console.log(window.title) // undefined
6. ** 继承 **
类的三个部分
- 第一部分是构造函数部分,这是供实例化对象复制用的。
- 第二部分是构造函数外的,直接通过点语法添加的,这是供类使用的,实例化对象时访问不到。
- 第三部分是类的原型中。实例化对象可以通 过原型链间接访问到。
6.1 类似继承 (非完美)
声明两个类,将第一个类的实例赋值给第二个类的原型。
// 类似继承
// 声明父类
function SuperClass() {
this.superValue = true
}
// 为父类添加公有方法
SuperClass.prototype.getSuperValue = function () {
return this.superValue
}
// 声明子类
function SubClass() {
this.subValue = false
}
// 继承父类
SubClass.prototype = new SuperClass()
// 为子类添加共有方法
SubClass.prototype.getSubValue = function () {
return this.subValue
}
// 使用子类
let instance = new SubClass()
console.log(instance.getSuperValue()) // true
console.log(instance.getSubValue()) // false
console.log(instance instanceof SuperClass) // true
类似继承缺点
- 修改父类的引用类型内容,会影响所有子类的所有实例化对象内容。
- 无法向父类传递参数。
6.2 构造函数继承(非完美)
// 构造函数继承
// 声明父类
function SuperClass(id) {
// 引用类型共有属性
this.books = ['javascript', 'html', 'css']
// 值类型共有属性
this.id = id
}
// 父类声明原型方法
SuperClass.prototype.showBooks = function () {
console.log(this.books)
}
// 声明子类
function SubClass(id) {
// 继承父类
SuperClass.call(this, id)
}
// 创建第一个子类的实例
var instancel = new SubClass(10)
// 创建第二个子类的实例
var instance2 = new SubClass(11)
instancel.books.push('设计模型')
console.log(instancel.books) // ['javascript', 'html', 'css',"设计模型"]
console.log(instancel.id) // 10
console.log(instance2.books) // ['javascript', 'html', 'css']
console.log(instance2.id) // 11
instancel.showBooks() // 出现错误,这个方法无法再子类实例中找到
6.3 组合继承(非完美)
组合继承==类似继承+构造函数继承
6.4 原型式继承(非完美)
Object.create() 静态方法以一个现有对象作为原型,创建一个新对象。
const person = {
isHuman: false,
printIntroduction: function () {
console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`)
},
}
const me = Object.create(person)
me.name = 'Matthew' // "name" is a property set on "me", but not on "person"
me.isHuman = true // Inherited properties can be overwritten
me.printIntroduction()
// output: "My name is Matthew. Am I human? true"
缺点跟类似继承差不多
// 原型式继承
function inheritObject(o) {
// 声明一个过渡函数对象
function F() {}
// 过渡对象的原型继承父对象
F.prototype = o
// 返回过渡对象的一个实例,该实例的原型继承了父对象
return new F()
}
6.5 寄生式继承
// 原型式继承
function inheritObject(o) {
// 声明一个过渡函数对象
function F() {}
// 过渡对象的原型继承父对象
F.prototype = o
// 返回过渡对象的一个实例,该实例的原型继承了父对象
return new F()
}
// 寄生式继承
// 声明基对象
var book = {
name: 'js book',
alikeBook: ['css book', 'html book'],
}
function createBook(obj) {
// 通过原型继承方式创建新对象
var o = new inheritObject(obj)
// 拓展
}
6.6 寄生组合式继承
6.7 多继承
放在Object的原型中,这样所有对象都可以使用
// 多继承 属性复制
Object.prototype.mix = function () {
console.log('arguments', arguments)
var i = 0,
len = arguments.length,
arg
// 遍历被继承的对象
for (; i < len; i++) {
// 缓存当前对象
arg = arguments[i]
// 将被继承对象中的属性复制到目标对象中
for (var property in arg) {
// 将被继承对象中的属性复制到目标对象中
this[property] = arg[property]
}
}
// 返回目标对象
//return target
}
var book1 = {
name: 'JavaScript 设计模式1',
alike: ['css', 'html', 'JavaScript'],
}
var book2 = {
name: 'JavaScript 设计模式2',
alike: ['css', 'html', 'DIV'],
pws: [1, 2, 3],
}
var anotherBook = {
color: 'blue',
}5
anotherBook.mix(book1, book2)
console.log(anotherBook)
执行结果
{
"color": "blue",
"name": "JavaScript 设计模式2",
"alike": [
"css",
"html",
"DIV"
],
"pws": [
1,
2,
3
]
}
6.8 多态
多态:就是同一个方法多种调用方式。 就是对传入的参数进行判断,根据不同的参数返回不同的内容。
封装在类中的多态
function Add2() {
// 无参数算法
function zero() {
return 10
}
// 一个参数算法
function one(num) {
return 10 + num
}
// 两个参数算法
function two(num1, num2) {
return num1 + num2
}
// 相加共有方法
this.add = function () {
var arg = arguments,
len = arg.length
switch (len) {
case 0:
return zero()
case 1:
return one(arg[0])
case 2:
return two(arg[0], arg[1])
}
}
}
// 实例化类
var A = new Add2()
// 测试
console.log(A.add()) // 10
console.log(A.add(5)) // 15
console.log(A.add(6, 7)) // 13
总结
封装与继承是面向对象编程的两个主要特性。
- 不论对类如何实例化,只创建一次,那么这种类的属性或者方法称为静态的。
- 静态的只被类所拥有,则称为静态类方法与静态类属性。
可被继承的方法与属性有两种:
- 一类在构造函数中,这类属性与方法在对象实例化时被复制一遍。
- 另一类在类的原型对象中,这类属性与方法在对象实例化时被所有实例化对象所共有。