目录
一、深入面向对象
分为面向过程和面向对象两种,面向过程是以步骤来划分问题,在这儿不做过多描述。
(一)面向对象介绍
把事务分解成一个个对象,由对象之间分工合作,面向对象是以对象功能来划分问题,
(二)面向对象编程 (oop)
1.面向对象编程介绍
就是事情没有先后顺序,是并行进行的,每个对象都是功能中心,必须有明确分工。
2.面向对象编程优点
面向对象编程 灵活 容易维护和开发 适合多人合作大型软件项目
3.面向对象的特征
封装性
继承性
多态性
4.和面向过程编程对比
二、构造函数
(一)介绍
封装在面向对象编程中很重要,js 面向对象可以通过构造函数实现封装
构造函数体现了面向对象的封装特性
使用构造函数创建的对象彼此独立,互不影响
下面的 this 就是指向下面创建的对象 zhangsan
再创建一个别的指向就不同了 所以相互独立
<body>
<script>
function Person(name, age){
this.name = name
this.age = age
}
const zhangsan = new Person('张三', 18)
console.log(zhangsan)
</script>
</body>
(二)内存浪费
构造函数 存在内存浪费的问题,张三和李四的吃饭方法都是相同的但是它们分别属于不同的对象,所以不相等,所以会浪费内存
<body>
<script>
function Person(name, age){
this.name = name
this.age = age
this.eat = function(){
console.log('我是人')
}
}
const zhangsan = new Person('张三', 18)
const lisi = new Person('李四', 20)
console.log(zhangsan.eat === lisi.eat)
</script>
</body>
三、原型
(一)原型介绍
用于解决构造函数内存浪费的问题,利用原型实现方法共享,原型分配的函数是所有对象共享的
每个构造函数都有一个 prototype 属性 原型在构造函数中,是一个对象,叫原型对象
这个对象可以挂载函数,对象实例化不会多次创建原型上函数节约内存
就是把公共的方法单独拿出来 使用 构造函数名.prototype.方法名 = ... 就不会多次创建浪费内存了
公共属性放构造函数里面就行
<body>
<script>
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.eat = function () {
console.log('吃吃吃')
}
const zhangsan = new Person('张三', 18)
const lisi = new Person('李四', 20)
console.log(zhangsan.eat === lisi.eat)
</script>
</body>
原型对象中的 this 也指向 实例化后的对象 zhangsan,和构造函数中的 this 指向相同
<body>
<script>
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.eat = function () {
that = this
console.log('吃吃吃')
}
const zhangsan = new Person('张三', '18')
zhangsan.eat()
console.log(zhangsan === that)
</script>
</body>
(二)constructor 属性
每个原型对象都有 constructor 属性,这个属性指向原型对象 (prototype)的构造函数
如果有多个对象方法,可以给构造函数的原型对象赋值,来节省内存
原本 prototype 是一个对象 并通过 constructor 指向它的构造函数父亲 Person 但是经过给原型对象赋值后 覆盖了之前的 原型对象,之前能指向父亲的 constructor 属性消失了 所以就失去父亲了 这个原型对象就不知道是谁的儿子了
所以我们需要在赋值的大括号里面重新加上 constructor 属性 就能指向原来的父亲了就能正常调用里面的方法了
<body>
<script>
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype = {
constructor: Person,
sing: function () {
console.log("唱歌")
},
dance: function () {
console.log('跳舞')
}
}
console.log(Person.prototype.constructor)
</script>
</body>
(三)对象原型
1.介绍
构造函数中有原型对象,构造函数也能创建实例对象,正常来说,原型对象和实例对象是同等地位的,那么怎么实例对象可以调用原型对象中的方法呢?
当然是因为对象原型,对象都会有一个属性 __proto__ 指向构造函数的原型对象 prototype ,所以实例对象能调用原型对象中的方法(一边是两个下划线)
__proto__ 不是 js 标准写法 [[prototype]] 是一个意思表明 当前实例对象指向 某个原型对象
下面两个布尔值是相等的
<body>
<script>
function Person(){}
const zhangsan = new Person()
console.log(zhangsan.__proto__ === Person.prototype)
</script>
</body>
2.三者关系图
构造函数里本来就有 prototype 原型对象
构造函数能实例化实例对象
原型对象中有 constructor 属性指向 他的父亲构造函数
实例对象中 __proto__ 对象原型中的 constructor 属性也指向 父亲构造函数
实例对象的 对象原型 __proto__ 指向兄弟 原型对象 所以能调用其中的方法
(四)原型继承
1.简单使用对象继承(有问题)
如果有多个构造函数内部有很多相同的属性和方法,就可以再写一个对象把共同的部分单独提出来再用构造函数的原型对象继承
如下 男人女人 都具有人类的属性就把 人类属性单独提炼出来变成一个对象,然后前面学过如果是构造函数公用的部分,就得把相同的部分封装在原型对象中
所以把 Woman 和 Man 中的 prototype 用 Person 覆盖 这样就继承了Person的属性和方法,这样就能使用了,但是他俩都使用了Person 如果想要 添加各自单独的方法,就会污染 Person 对象
结果就是 在Woman 中创建的方法 在 Man 中 也能使用,因为他俩都继承了 Person ,都指向Person
<body>
<script>
const Person = {
eyes: 2,
head: 1
}
function Woman() {
}
function Man() {
}
Woman.prototype = Person
Woman.prototype.contructor = Woman
Woman.prototype.baby = function () {
console.log('宝贝')
}
Man.prototype = Person
Man.prototype.contructor = Man
const zhangsan = new Woman()
const lisi = new Man()
console.log(lisi.baby())
</script>
</body>
2.使用父构造函数继承
为了解决上面的问题,我们只要让每个构造函数的原型对象 被赋值的 Person 不一样就行了
就是
Man.prototype = Person1
Woman.prototype = Person2
就避免了上面的情况,因为现在 他俩指向的 Person 不同了,这样就不会互相影响了
思路就是把 数组 Person 也变成一个构造函数 然后每次 new Person() 都不一样就可以出现不一样的Person 对象了
改进代码如下:这样 lisi 就不具有 baby() 方法了
<body>
<script>
function Person() {
eyes = 2
head = 1
}
function Woman() {
}
function Man() {
}
Woman.prototype = new Person()
Woman.prototype.contructor = Woman
Woman.prototype.baby = function () {
console.log('宝贝')
}
Man.prototype = new Person()
Man.prototype.contructor = Man
const zhangsan = new Woman()
const lisi = new Man()
console.log(zhangsan.baby())
</script>
</body>
(五)原型链
1.原型链介绍
基于原型对象的继承使得不同构造函数的原型对象关联在一起,这种关系是一种链状的结构,将这种链状结构就成为原型链
只要是对象就有 __proto__,__proto__ 只指向原型对象 prototype
只要有原型对象就有 constructor,指向创建我的 构造函数
2.模型图讲解
__proto__ 就构成了原型链
基本的三角关系不过多描述了,前面有
实例对象 中有对象原型 __proto__ 指向 兄弟:父亲构造函数的原型对象 prototype
但是兄弟原型对象也是对象 也拥有对象原型 __proto__ 它指向的是更大的爷爷
就是 构造函数 Object 的原型对象 Object.prototype
Object.prototype 也能指向 Object 通过 Object.prototype.constructor 指向
但是原型对象 Object.prototype 也是对象也拥有对象原型 __proto__ 但是没有更大的构造函数了,所以最后指向为 空 null 原型链就结束了
3.原型链面试理解
类似于作用域链
1.原型链就是一种查找规则,我们想要一些属性和方法,先看当前实例有没有当前方法
2.如果没有就查找它的原型 __proto__ 指向的 prototype 原型对象
3.如果还没有 就查找原型对象的原型
4.直到查到 null (Object)为止
__proto__ 原型对象为对象成员查找机制提供了一个方向/一条路路线
能用 instanceof 运算符检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上
下面三个输出结果都是 true
Object 是最大的对象 数组也是对象 本质也是通过 const str = new Array() 出来的
<body>
<script>
const str = [1,2,3]
function Person(){
}
const zhangsan = new Person()
console.log(str instanceof Array)
console.log(zhangsan instanceof Person)
console.log(zhangsan instanceof Object)
</script>
</body>
练习:给数组拓展求最大最小值和求和方法
结果展示:
代码部分:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
const arr = [1, 2, 3]
Array.prototype.max = function () {
return Math.max(...this)
}
Array.prototype.min = function () {
return Math.min(...this)
}
Array.prototype.sum = function () {
return this.reduce((prev, item) => prev + item, 0)
}
console.log(arr.max())
console.log(arr.min())
console.log(arr.sum())
</script>
</body>
</html>
综合案例:模态框构造函数
界面展示:
代码部分:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.modal {
width: 300px;
min-height: 100px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
border-radius: 4px;
position: fixed;
z-index: 999;
left: 50%;
top: 50%;
transform: translate3d(-50%, -50%, 0);
background-color: #fff;
}
.modal .header {
line-height: 40px;
padding: 0 10px;
position: relative;
font-size: 20px;
}
.modal .header i {
font-style: normal;
color: #999;
position: absolute;
right: 15px;
top: -2px;
cursor: pointer;
}
.modal .body {
text-align: center;
padding: 10px;
}
.modal .footer {
display: flex;
justify-content: flex-end;
padding: 10px;
}
.modal .footer a {
padding: 3px 8px;
background: #ccc;
text-decoration: none;
color: #fff;
border-radius: 2px;
margin-right: 10px;
font-size: 14px;
}
.modal .footer a.submit {
background-color: #369;
}
</style>
</head>
<body>
<button id="delete">删除</button>
<button id="login">登录</button>
<script>
function Modal(title = '', message = '') {
this.box = document.createElement('div')
this.box.className = 'modal'
this.box.innerHTML = `<div class="header">${title}<i>×</i></div>
<div class="body">${message}</div>`
}
Modal.prototype.open = function () {
const panduan = document.querySelector('.modal')
panduan && panduan.remove()
document.body.append(this.box)
this.box.querySelector('i').addEventListener('click', () => {
this.close()
})
}
Modal.prototype.close = function () {
this.box.remove()
}
document.querySelector('#delete').addEventListener('click', () => {
const del = new Modal('温馨提示', '没有权限删除权限')
del.open()
})
document.querySelector('#login').addEventListener('click', () => {
const login = new Modal('友情提示', '你没注册')
login.open()
})
</script>
</body>
</html>