***本章面试使用居多* 理论篇**
一、编程思想
1.1 面向过程 JS 前端居多
按照步骤
性能高 适合跟硬件关系很紧密
没有面向对象易维护易复用易扩展
1.2 面向对象 java典型
按照功能,把事务分别成一个个对象,对象之间分工合作
比较灵活 适合多人合作的大型软件项目
三大特性:
想象将拖拉机改装成了扫地机理解特性
封装性
继承性
多态性 多种功能
二者区别:打游戏刚开始是面向对象的,中期互相帮忙的时候变成了面向过程
二、构造函数
封装是面向对象的比较重要一部分,js面向对象可以通过构造函数实现封装
1.构造函数体现了面向对象的封装性
2.构造函数实现的对象互不影响
//构造函数 公共的属性和方法封装到构造函数
function Star (uname, age) {
this.uname = uname
this.age = age
this.sing = function () {
console.log('唱歌')
}
}
// 只要一new 就创建了一个实例对象
const ldh = new Star('刘德华', 60)
const zxy = new Star('张学友', 60)
// 各个对象之间互不影响
console.log(ldh === zxy)
// 理论上是一样的 false 说明不一样 各个对象之间的属性互不影响
console.log(ldh.sing === zxy.sing);
构造函数创建有问题:浪费内存,如果里面的属性是添加方法的话,因为函数属于复杂数据类型,所以需要先将内存的地址存到栈中,实际上的函数存到了堆中 如果对象有100个,这样就不浪费了内存 如果只是单纯的属性名属性值用完就销毁。
JS实现面向对象通过构造函数,但是构造函数会有浪费内存(复杂数据类型),创建一个对象开一个内存去存funciton()
所以所有的对象使用同一个函数,需要节省内存,我们就引入到了原型:
三、原型
构造函数里面浪费内存的问题,对象中的方法。
目标:能够利用原型对象实现方法(就是函数)共享。
3.1 原型
定义:
一些共享的属性或者方法不应该写在构造函数上 ,写在原型上 原型是一个对象,所以称为原型对象。
构造函数都有一个prototrpe属性,这就是原型 ——原型对象
<script>
//构造函数 公共的属性和方法封装到构造函数
// 公共的属性写在构造函数中 公共的方法写在原型对象身上
function Star (uname, age) {
this.uname = uname
this.age = age
// this.sing = function () {
// console.log('唱歌')
// }
}
// 构造函数的属性prototype对象 这个对象可以挂载函数 是对象 可以使用对象.属性的方式进行赋值 相当于给对象追加属性 只调用一个
Star.prototype.sing = function () {
console.log('唱歌')
}
// 只要一new 就创建了一个实例对象
const ldh = new Star('刘德华', 60)
const zxy = new Star('张学友', 60)
ldh.sing()
zxy.sing()
// 各个对象之间互不影响
console.log(ldh === zxy)
// 理论上是一样的 false 说明不一样 各个对象之间的属性互不影响 //true
console.log(ldh.sing === zxy.sing)
// 写法是一个属性 但打印出来是一个对象
console.dir(Star.prototype)
</script>
原型的作用:共享方法 把那些不变的方法直接定义在prototype对象上
构造函数和原型里面的this指向谁?实例化的对象
案例:数组里面没有直接的方法,我们自己写一个:
只要一执行 就能够求和 自定义求和 求最大值
任何对象都能使用 放在prototype对象上
求最值
//自己定义 数组扩展方法 和最大值
// 1.我们定义的这个方法 任何一个数组实例对象都可以使用
// 2.自定义的方法 写到数组.protot ype上
// 1.求最大值
const arr = [1, 23]
// 同const arr = new Array(1, 2)
Array.prototype.max = function () {
//展开运算符 死记硬背
return Math.max(...this)
// 原型函数里面的this 指向实例对象 Array
}
console.log(arr.max())
console.log([2, 6, 8].max());
求和
// 求和方法
Array.prototype.sum = function () {
return this.reduce((prev, item) => prev + item, 0)
}
console.log([1, 2, 3].sum());
打印的两种方式和区别
前者打印出的是外部结构 后者打印出的是让人更容易理解 例如:数组 前者打印出的是 [ 内容 ],后者是Array 所以当我们想要打印的是函数或者对象的时候,最好使用dir打印 看的更清楚
const arr = [1, 223, 543]
console.log(arr)
console.dir(arr);
3.2 constructor属性
constructor属性告诉prototype对象他的爸爸是谁)——是构造函数
使用场景:
利用原型封装属性时,属于赋值,这样修改后的原型对象就不在指向构造函数了
<script>
// constructor 构造函数
function Star () {
}
// Star.prototype.sing = function () {
// console.log('唱歌')
// }
// Star.prototype.dance = function () {
// console.log('跳舞')
// }
console.log(Star.prototype)
Star.prototype = {
//重新指回创造这个原型对象的 构造函数
constructor: Star,
sing: function () {
console.log('唱歌')
}, dance: function () {
console.log('跳舞')
}
}
console.log(Star.prototype)
// const ldh = new Star()
// // console.log(Star.prototype);
// console.log(Star.prototype.constructor === Star);
</script>
3.3 对象原型
为什么实例对象可以访问原型上的方法:对象原型
包含在实例对象中
__proto__
里面有一个constructor 所有的实例对象都可以访问原型对象
实例对象中的__proto__属性可以获取构造函数的prototype原型对象
原型对象里面装的是公共属性 原型对象是由构造函数得来的· 实例对象中说原型对象不适用prototype,使用__proto__ 所以既然有这个属性 那么打印实例对象的时候就会打印出原型对象 在浏览器中显示是prototype
结合上面图示搞懂之间的关系
function Person () {
this.name = name
}
const peppa = new Person('佩奇')
console.log(peppa)
console.log(Person.prototype.constructor === Person)
//实例对象 构造函数 原型对象之间的关系
console.log(peppa.__proto__ === Person.prototype)
console.log(peppa.__proto__.constructor === Person);
3.4 原型继承
构造了两个分别是Man、Woman的构造函数,里面有很多类似属性,也就是公共属性,需要封装到一个对象上,然后将这个对象赋值给原型对象,实例对象通过原型对象获得公共属性。即为原型继承
注意:因为是对象赋值,那么就会把原来原型对象可以指向构造函数的属性失去,还需要给它指回去。
<script>
// 继续抽取 公共的部分放到原型上
const Person = {
eyes: 2,
head: 1
}
//女人 构造函数 想要继承Person
function Woman () {
}
//woman 通过原型来继承Person 把公共属性给了原型对象
Woman.prototype = Person
// 指回原来的构造函数
Woman.prototype.constructor = Woman
const red = new Woman()
// console.log(red.eyes)
// console.log(Woman.prototype === red.__proto)
console.log(red)
console.log(Woman.prototype)
//男人
function Man () {
}
Man.prototype = Person
Man.prototype.constructor = Man
const pink = new Man()
// console.log(pink);
console.log(pink);
</script>
赋值时,使用同一个对象所以添加其中一个原型对象的属性 另外一个也要添加 所以不能是同一个Person
结构相同 但是不同对象里面包含相同属性和方法——采用构造函数 new每次都会创建一个新的对象
<script>
// 继续抽取 公共的部分放到原型上
// const Person1 = {
// eyes: 2,
// head: 1
// }
// const Person2 = {
// eyes: 2,
// head: 1
// }
// 结构相同 对象不同
function Person () {
this.eyes = 2
this.head = 1
}
console.log(new Person())
//女人 构造函数 想要继承Person
function Woman () {
}
//woman 通过原型来继承Person 把公共属性给了原型对象
//父构造函数(父类) 子构造函数
//子类的原型=new 父类()
Woman.prototype = new Person()
// 指回原来的构造函数
Woman.prototype.constructor = Woman
Woman.prototype.baby = function () {
console.log('宝贝')
}
const red = new Woman()
console.log(red)
//男人
function Man () {
}
Man.prototype = new Person()
Man.prototype.constructor = Man
const pink = new Man()
console.log(pink);
</script>
3.5 原型链
为什么Object对象是最大的对象?
只要是原型对象就有constructor,指向构造函数 只要是对象有__proto__ 指向原型对象
公共属性会被放在prototype
死记硬背:只要原型对象上有这个公共属性,只要在原型链上的对象都可以使用
原型链是什么:查找规则 查找属性或者方法的一条路 先从自身原型对象 如果没有 就往上一层查找 如果有就使用 如果没有 就去往上上一层查找 在上面一层能查最后的任何一个地方 在没有 再去最大的一层查找 因为有原型链的存在 实例对象可以实现最上一层的方法 往上查找到null就没有这个属性了。
instanceof运算符 是用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上
死记硬背:换句话就是在这条原型链上,因为原型链的存在,对象和实例对象 以及原型对象之间可以互相指向,所以直接简单理解为 小对象 instanceof 大对象 小对象属与大对象 结合原型链
console.log(ldh instanceof Person)
console.log(ldh instanceof Object)
console.log(ldh instanceof Array)
console.log([1, 2, 3] instanceof Array)
console.log(Array instanceof Object) //万事皆对象
四、 综合案例
首先模态框里面有open方法和close方法
模态框使用构造函数
open方法和close方法使用构造函数的原型对象上 这样,每一个模块狂都可以使用open和close方法
不理解的是:构造函数提到了模态框 两个方法中=》模态框
模态框业务 构造函数
使用构造函数创建模态框内容
使用this 才能使得创建不同的盒子都可以轻松利用构造函数 而不是const
打开oppen 显示
将创建的模态框显示在页面中
在这里:为什么要将属性放在构造函数中,将方法放在原型对象中?首先就要想到是由于原型对象的特性,它是用于共享属性和方法,所以如果对于每个实力来说,属性如果是唯一的,就放在构造函数中,不是唯一,就放在原型对象上。
关闭 模态框 挂载
思路:关闭模态框的时候 因为是要出现模态框才能关闭 所以关闭语句要在open方法中写
难点理解:我们使用箭头函数 这样便于this指向实例对象 而不是箭头函数的调用者i,这样才可以调用close方法
出现的bug,多次点击会出现多个模态框 解决:准备open的时候 先判断页面中有没有modal盒子 有就移除 没有就添加
为什么是在open的时候来判断 是因为点击open方法的时候才会有模态框
<button id="delete">删除</button>
<button id="login">登录</button>
<button id="add">新增</button>
<!-- <div class="modal">
<div class="header">温馨提示 <i>x</i></div>
<div class="body">您没有删除权限操作</div>
</div> -->
<script>
// 1.modal构造函数封装——模态框
function Modal (title = '', message = '') {
console.log(title, message)
//创建modal模态框盒子
// 1.1 创建div标签
// 因为是构造函数 所以使用this this经过new 谁new谁是实例对象 两个不同的盒子 this指向当前的盒子
this.modalBox = document.createElement('div')
// 1.2 给div标签添加类名为modal
this.modalBox.className = 'modal'
// 1.3 modal盒子内部填充 2个div标签并且修改文字内容
this.modalBox.innerHTML = `
<div class="header">${title} <i>x</i></div>
<div class="body">${message}</div>`
console.log(this.modalBox)
}
// new Modal('温馨提示', '您没有权限删除操作')
// new Modal('友情提示', '您还没登录呢')
// 当我们打印函数时,想要知道里面的属性使用dir
// console.dir(Modal)
// 2.给构造函数原型对象挂载open方法 this指向函数的调用者 函数的调用者是实例对象
Modal.prototype.open = function () {
console.log(this)
// 先判断页面中有没有modal盒子 有就移除 没有就添加
const box = document.querySelector('.modal')
// 逻辑与中断 如果有 就执行remove 没有就执行中断这条语句 继续执行下面的语句
box && box.remove()
//注意这个方法不要用箭头函数
//把刚才创建的modalBox显示到页面body中
document.body.append(this.modalBox)
// 要等着盒子显示出来 就可以绑定点击事件了
// 使用箭头函数的原因是因为箭头函数中无this 所以this才会指向函数的调用者
this.modalBox.querySelector('i').addEventListener('click', () => {
// 这个地方需要用到箭头函数
// 这个this指向 实例对象 调用close方法进行移除
this.close()
})
}
// 测试一下 点击删除按钮
document.querySelector('#delete').addEventListener('click', () => {
// 先调用modal构造函数
const del = new Modal('温馨的提示', '您没有权限删除操作')
// 实例对象调用open方法
//使用原型链的原理:Modal.prototype 上定义了一个 open 方法。这意味着 open 方法是 Modal 构造函数的原型方法,并且会被所有通过 Modal 构造函数创建的实例所共享。 当调用 del.open() 时,JS引擎首先会查看 del 实例自身是否有一个名为 open 的方法。由于 open 方法是定义在原型上的,而不是直接定义在实例上的,所以引擎会在 del 实例的原型链上查找这个方法
del.open()
})
// 测试一下 点击登录按钮
document.querySelector('#login').addEventListener('click', () => {
//先调用Modal构造函数
const login = new Modal('友情的提示', '您没有注册呢')
//实例对象调用open方法
login.open()
})
// 测试一下 点击新增按钮
document.querySelector('#add').addEventListener('click', () => {
//先调用Modal构造函数
const add = new Modal('提示', '您没有新增权限呢')
//实例对象调用open方法
add.open()
})
// 3.给构造函数原型对象挂载close方法
Modal.prototype.close = function () {
this.modalBox.remove()
}
</script>
完整代码设计
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>面向对象封装消息提示</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>
<button id="add">新增</button>
<!-- <div class="modal">
<div class="header">温馨提示 <i>x</i></div>
<div class="body">您没有删除权限操作</div>
</div> -->
<script>
// 1.modal构造函数封装——模态框
function Modal (title = '', message = '') {
console.log(title, message)
//创建modal模态框盒子
// 1.1 创建div标签
// 因为是构造函数 所以使用this this经过new 谁new谁是实例对象 两个不同的盒子 this指向当前的盒子
this.modalBox = document.createElement('div')
// 1.2 给div标签添加类名为modal
this.modalBox.className = 'modal'
// 1.3 modal盒子内部填充 2个div标签并且修改文字内容
this.modalBox.innerHTML = `
<div class="header">${title} <i>x</i></div>
<div class="body">${message}</div>`
console.log(this.modalBox)
}
// new Modal('温馨提示', '您没有权限删除操作')
// new Modal('友情提示', '您还没登录呢')
// 当我们打印函数时,想要知道里面的属性使用dir
// console.dir(Modal)
// 2.给构造函数原型对象挂载open方法 this指向函数的调用者 函数的调用者是实例对象
Modal.prototype.open = function () {
console.log(this)
// 先判断页面中有没有modal盒子 有就移除 没有就添加
const box = document.querySelector('.modal')
// 逻辑与中断 如果有 就执行remove 没有就执行中断这条语句 继续执行下面的语句
box && box.remove()
//注意这个方法不要用箭头函数
//把刚才创建的modalBox显示到页面body中
document.body.append(this.modalBox)
// 要等着盒子显示出来 就可以绑定点击事件了
// 使用箭头函数的原因是因为箭头函数中无this 所以this才会指向函数的调用者
this.modalBox.querySelector('i').addEventListener('click', () => {
// 这个地方需要用到箭头函数
// 这个this指向 实例对象 调用close方法进行移除
this.close()
})
}
// 测试一下 点击删除按钮
document.querySelector('#delete').addEventListener('click', () => {
// 先调用modal构造函数
const del = new Modal('温馨的提示', '您没有权限删除操作')
// 实例对象调用open方法
//使用原型链的原理:Modal.prototype 上定义了一个 open 方法。这意味着 open 方法是 Modal 构造函数的原型方法,并且会被所有通过 Modal 构造函数创建的实例所共享。 当调用 del.open() 时,JS引擎首先会查看 del 实例自身是否有一个名为 open 的方法。由于 open 方法是定义在原型上的,而不是直接定义在实例上的,所以引擎会在 del 实例的原型链上查找这个方法
del.open()
})
// 测试一下 点击登录按钮
document.querySelector('#login').addEventListener('click', () => {
//先调用Modal构造函数
const login = new Modal('友情的提示', '您没有注册呢')
//实例对象调用open方法
login.open()
})
// 测试一下 点击新增按钮
document.querySelector('#add').addEventListener('click', () => {
//先调用Modal构造函数
const add = new Modal('提示', '您没有新增权限呢')
//实例对象调用open方法
add.open()
})
// 3.给构造函数原型对象挂载close方法
Modal.prototype.close = function () {
this.modalBox.remove()
}
</script>
</body>
</html>