面向过程编程
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个依次调用就可以了
面向过程:就是按照我么分析好的步骤,按照步骤解决问题
蛋炒饭
- 准备米饭
- 炒鸡蛋(倒入鸡蛋翻炒)
- 炒胡萝卜、香肠
- 把米饭、鸡蛋、胡萝卜一起翻炒
优点:
性能高,适合跟硬件联系比较紧密的东西,比如单片机,采用面向过程编程
缺点:
不灵活,复用性差
面向对象编程
把事务分解成为一个个对象,对象之间分工合作
优点:
易维护,易复用,易拓展,由于面向对象有封装,继承,多态的特性,设计的系统更加灵活,易于维护
缺点:
性能低
面向对象的特性
- 封装性
- 继承性
- 多态性
构造函数
封装时面向对象思想中比较重要的一部分,js面向对象可以通过构造函数来实现封装
把公共的属性和方法抽取封装到构造函数里面来实现数据的共享,这样创建的实例对象就可以使用这些属性和方法
// 构造函数实现封装
function Person(name, age) {
this.name = name
this.age = age
this.sayHi = function () {
console.log("HI~")
}
}
const zs = new Person('张三', 18)
const ls = new Person('李四', 19)
console.log(zs)
console.log(ls)
console.log(zs === ls) //false
/*
总结:
构造函数体现了面向对象的封装特性
构造函数实例创建的对象彼此独立,互不影响
*/
// 构造函数实现封装有个问题 存在浪费内存的问题 (构造函数中函数会多次创建,占用内存)
console.log(zs.sayHi === ls.sayHi) //false 两个函数不一样
原型对象
js规定,每一个构造函数都有一个prototype属性,指向另一个对象,我们称为原型对象
使用场景
可以解决:构造函数封装时函数(方法)会多次创建,占用内存的问题
原型对象可以挂载函数,对象实例化不会多次创建原型对象里的函数,节约内存
实例对象可以直接访问原型对象中的函数
执行过程:先找实例对象的属性或函数,找不到会再找原型对象中属性或函数
// 构造函数实现封装
function Person(name, age) {
this.name = name
this.age = age
// this.sayHi=function(){
// console.log("HI~")
// }
}
// console.log(Person.prototype)
Person.prototype.sayHi = function () {
console.log("HI~")
}
console.log(Person.prototype)
const zs = new Person('张三', 18)
const ls = new Person('李四', 19)
zs.sayHi()
ls.sayHi()
console.log(zs.name)
console.log(zs.sayHi === ls.sayHi) //true
// 实例对象可以直接访问原型对象中的函数
// 执行过程:先找实例对象的属性或函数,找不到会再找原型对象中属性或函数
const arr = new Array()
console.log(Array.prototype)//原型对象
// arr.filter()
原型对象-this指向
构造函数和原型对象中this都指向 实例化的对象
// 构造函数this指向 实例对象
function Person(name, age) {
this.name = name
}
// 原型对象this指向
Person.prototype.sayHi = function () {
console.log('~HI')
console.log(this) //指向实例对象张三
}
const zs = new Person("zs", 18)
zs.sayHi()
/*
注意:
箭头函数不能做构造函数,因为箭头函数里没有this
原型对象里面的函数如果需要用到this,也不要用箭头函数
*/
constructor属性
每个原型对象里面都有个constructor属性(constructor构造函数)
作用:该属性指向该原型对象的构造函数
使用场景:
如果有多个对象的方法,我们可以给原型对象采取对象形式赋值
但是这样会覆盖构造函数原型对象原来的内容,这样修改后的原型对象 constructor 就不再指向当前构造函数了
此时我们可以再修改后的原型对象中,添加一个constructor指向原来的构造函数
function Person(name) {
this.name = name
}
// constructor属性在原型对象里面
console.log(Person.prototype)
// constructor属性指向原型对象的构造函数
console.log(Person.prototype.constructor === Person) //true
// 使用场景
// Person.prototype.sing= function(){
// console.log("我会唱歌")
// }
// Person.prototype.dance= function(){
// console.log("我会跳舞")
// }
Person.prototype = {
// 手动设置constructor 指向Person
constructor: Person,
sing() {
console.log("我会唱歌")
},
dance() {
console.log("我会跳舞")
}
}
console.log(Person.prototype)
__proto__属性
对象都会有一个属性__proto__
指向构造函数的prototype原型对象
之所以我们对象可以使用构造函数prototype原型对象的方法,就是因为对象有__proto__
的存在
function Person(name) {
this.name = name
}
// 实例对象里有__proto__属性
const zs = new Person('zs')
console.log(zs)
// __proto__ 指向原型对象
console.log(zs.__proto__ === Person.prototype)//true
// 注意
// prototype 原型对象 __proto__ 原型
// __proto__ 非标准属性 尽量不要去修改它 否则影响性能
原型、原型对象和实例对象之间的关系
案例
// const arr = new Array()
// arr.filter
// 给数组扩展求最大值方法 和求和方法
const arr = [1, 2, 3];
arr.reverse()
// console.log(arr) //[3, 2, 1]
// 需要扩展
// arr.max() //3
// arr.sum() //6
// 数组扩展求最大值/最小值
Array.prototype.max = function () {
// this 原型对象里的this指向实例对象
return Math.max(...this)
}
console.log(arr.max())
console.log([3, 6, 7].max())
console.log([22, 55, 44].max())
// 数组扩展求和方法
const arr2 = [1, 2, 3]
Array.prototype.sum = function () {
// 求和
return this.reduce((prev, current) => prev + current, 0)
}
console.log(arr2.sum())
console.log([3, 5, 6].sum())
原型链
proto属性链状结构称为原型链
原型链为对象成员的查找机制提供了一个方向,或者是一条路线
查找机制
- 当访问一个对象成员(属性/方法)时,首先查找这个对象自身有没有该成员(属性/方法)
- 如果没有就查找它的原型对象(也就是proto指向的prototype原型对象)
- 如果还没有就查找原型对象的原型对象(Object的原型对象)
- 依次类推一直找到Object为止(null)
// __proto__属性链状结构称为原型链
function Person(name) {
this.name = name
}
// 实例对象
const zs = new Person('张三');
// zs.sayHi= function(){
// console.log("实例对象的方法")
// }
// zs.sayHi()
// Person原型对象
// Person.prototype.sayHi=function(){
// console.log("Person原型对象的方法")
// }
// zs.sayHi()
// Object原型对象
// Object.prototype.sayHi=function(){
// console.log("Object原型对象的方法")
// }
// zs.sayHi()
// null
console.log(zs.sayHi) //undefined
// zs.sayHi() //zs.sayHi is not a function
instanceof运算符
作用:用来检测构造函数.prototype是否存在于实例对象的原型链上
实例对象.instanceof 构造函数
原型继承
继承是面向对象编程的另一个特性,龙生龙,凤生凤 老鼠的儿子会打洞描述的正是继承的含义
有些公共的属性和方法可以写到父级身上,子级通过继承也可以使用这些属性和方法
js中大多是借助原型对象实现继承的特性
const person ={
eyes: 2,
eat:function(){
console.log("我会吃饭")
}
}
function Man(){
}
function Woman(){
}
// 封装 抽取公共部分
// 把男人和女人公共的部分抽取出来放到person对象里面
// 继承 通过原型对象实现继承
// 把男人 女人原型对象赋值为person对象
Man.prototype =person
const zs = new Man()
console.log(zs)
Woman.prototype = person
const xl = new Woman()
console.log(xl)
// 3.使用字面量对象实现继承会有问题
// 女人会生孩子
// 男人和女人同时使用了同一个对象,根据引用类型的特点,他们指向同一个对象,修改一个就会都影响
Woman.prototype.baby =function(){
console.log("我会生孩子")
}
console.log(xl)
console.log(zs)
解决
构造函数实例化对象继承,new每次都会创建一个新的对象
// 解决 男人和女人不要使用同一个对象,但是不同对象里面包含相同的属性和方法
// 使用 构造函数实例化对象继承,new每次都会创建一个新的对象
function Person (){
this.eyes=2
}
Person.prototype.eat = function(){
console.log("我会吃饭")
}
console.log(new Person()===new Person()) //false
function Man(){
}
Man.prototype = new Person()
Man.prototype.constructor = Man
function Woman(){
}
Woman.prototype = new Person()
Woman.prototype.constructor =Woman
const zs = new Man()
const xl = new Woman()
Woman.prototype.baby =function(){
console.log("我会生孩子")
}
console.log(xl)
console.log(zs)
模态框封装案例
<!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>Document</title>
<style>
.modal {
width: 400px;
height: 100px;
border: 1px solid #333;
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
}
.modal .header {
padding: 10px;
display: flex;
justify-content: space-between;
}
.modal .body {
text-align: center;
}
</style>
</head>
<body>
<button class="del">删除</button>
<button class="login">登录</button>
<!-- <div class="modal">
<div class="header"><span>温馨提示</span><i>x</i></div>
<div class="body">你没有删除权限操作</div>
</div> -->
<script>
// 面向对象写插件 模态框
// 构造函数 把模态框封装一个构造函数Model 每次new都会产出一个模态框 所以点击不同的按钮 就是在做new模态框实例化
// 模态框有什么功能 显示 关闭 构造函数Model的原型对象上
// 1.Modal构造函数
// 公共属性 标题title 提示信息内容message 可以设置默认参数
function Modal(title = "", message = "") {
// 创建modal模态框盒子
// 创建div标签 modal
// modal盒子内部填充2个div标签并且修改文字内容
this.modalBox = document.createElement('div')
this.modalBox.className = 'modal'
this.modalBox.innerHTML = `
<div class="header"><span>${title}</span><i>x</i></div>
<div class="body">${message}</div>
`
console.log(this.modalBox)
}
// new Modal("温馨提示","你没有删除权限操作")
// new Modal("友情提示","你还没有登录")
// open
// 写到构造函数的原型对象上
// 把刚才创建的modalBox添加到页面body标签中
// open打开的本质就是 把创建的标签添加到页面中
// 点击按钮 实例化对象,传入对应的参数 执行open方法
Modal.prototype.open = function () {
// 注意不能用箭头函数
// 把刚才创建的modalBox添加到页面body标签中
// bug: 判断页面中有没有modal 有就移除 没有就添加
const box = document.querySelector('.modal')
box && box.remove()
document.body.append(this.modalBox)
// 要等到盒子显示出来 就可以绑定点击事件
this.modalBox.querySelector('i').addEventListener('click', () => {
console.log(1)
// 这个地方要用到箭头函数 this指向实例对象
this.close()
})
}
document.querySelector('.del').addEventListener('click', function () {
const del = new Modal("温馨提示", "你没有删除权限操作")
del.open()
})
document.querySelector('.login').addEventListener('click', function () {
const login = new Modal("友情提示", "你还没有登录")
login.open()
})
// close
// 写到构造函数的原型对象上
// 把刚才创建的modalBox 从页面body标签中删除
// 需要注意x关闭按钮在模态框里面
// 页面显示这个模态框就要绑定事件
// 页面显示模态框在open里面 绑定关闭事件也要写到open方法里面
Modal.prototype.close = function () {
this.modalBox.remove()
}
</script>
</body>
</html>
函数递归
递归:递归就是一种函数调用自身的操作(函数内部自己调用自己–递归–这个函数就是递归函数)
递归函数的作用和循环效果类似
递归一定要有退出条件return,没有就会出现 ‘栈溢出’
function fn(){
fn()
}
案例
// 函数内部自己调用自己 ---递归
// 使用函数递归打印3句话
let i = 1;
function fn() {
console.log(`我是第${i}句话`)
if (i >= 3) return
i++
fn() //递归
}
// 10递归.html:16 Uncaught RangeError: Maximum call stack size exceeded
// fn()
// 练习:利用递归函数实现setTimeout 模拟setInterval效果
// 页面每隔一秒输出当前的时间
// 输出当前时间可以使用new Date().toLocalString()
function timer() {
const time = new Date().toLocaleString()
console.log(time)
setTimeout(timer, 1000)
}
timer()
// 1+2+3+4+5+....+n
function fn1(n) {
if (n === 1) {
return 1
}
return n + fn1(n - 1)
}
fn1(6)