今天的课程主要是理解什么是面向对象的编程思想,还有写一个案例。
wpi考试错点
1-当css中写了!important,那么它的优先级最高,在如下js中也无法改变它,因为这个下面的js的本质是修改style即css。
2-捕获类型事件,只能通过addEventListener注册,传统注册方法只能是事件冒泡。
3-事件对象e.preventDefault()可以阻止默认事件,e.stopPropagation()可以阻止冒泡和捕获。
propagation传播; 扩展; 宣传; 培养;
问题:我阻止冒泡和捕获干啥?我阻止了还咋执行事件?
<body>
<div>
<a href="#">qqq</a>
</div>
<script>
let box = document.querySelector('a')
let div = document.querySelector('div')
div.addEventListener('click', function (e) {
alert('2')
})
box.addEventListener('click', function (e) {
alert('1')
})
</script>
</body>
如上,如果阻止了冒泡和捕获,本身的事件会执行,但是div的事件不会执行,因为阻止了冒泡。
4-document.querySelectorAll就算一个元素对象没取到,得到的是一个空数组而不是null;
document.querySelector如果元素不存在,会得到null
5-在事件委托绑定后,事件程序中e.target和this是不同的,e.target是指向鼠标点击的那个元素对象,this是谁调用指向谁。
编程思想
https://blog.csdn.net/youif/article/details/107322845
面向对象有封装性,继承性,多态性。
构造函数
js中面向对象需要构造函数来实现封装
<script>
function Awesome(name, gender) {
this.name = name
this.gender = gender
this.dream = () => { console.log('探索未知,体验未知,改变世界'); }
}
const zdq = new Awesome('zdq', 'man')
const tyl = new Awesome('tyl', 'man')
console.log(zdq.dream === tyl.dream) //false
</script>
浪费内存
在构造函数生成对象的时候,当不同对象有一个相同的方法,如上的dream方法,那么zdq.dream和tyl.dream的方法是存储中不同的地址的,每次new都又生成了方法。
所以说这样就极大的浪费了内存,这个方法其实共用就行了。怎么实现共用呢?
使用原型prototype
原型
prototype是构造函数的一个属性,指向一个对象,如你打印Awesome.prototype,输出的也是一个对象。
问题:函数就是函数,咋函数也有属性了?
函数是一种特殊类型的对象。您自己编写的代码并不是实际的函数。 该函数是具有属性的对象,此属性是可调用的。
console.dir(Awesome)
console.dir(Awesome.prototype)
也就是说只有是相同构造函数生成的不同对象,如果通过 构造函数名.prototype.方法名=function(){}的话
就可以共用这一个方法,这个方法被赋予给每一个此构造函数生成的对象,但是它们调用的话,就是调用同一个方法,避免了内存浪费。
<script>
function Awesome(name, gender) {
this.name = name
this.gender = gender
}
Awesome.prototype.dream =() => { console.log('探索未知,体验未知,改变世界'); }
const zdq = new Awesome('zdq', 'man')
const tyl = new Awesome('tyl', 'man')
console.log(zdq.dream === tyl.dream) //true
</script>
案例
分析:这里的需求就是在实例化数组的构造函数上加一个对象prototype的方法即可。
<script>
// const arr = [1,2,3]
const arr =new Array(1,2,8)
Array.prototype.max = function(){
// 原型函数里面的this 指向谁? 实例对象 arr
return Math.max(...this)
}
console.log(arr.max());
</script>
这里为什么写…this?this指向的就是调用者,调用者就是那个arr,三个点在数组里面就是展开运算符。
constructor属性
const恒定的,不变的;construct制造
constructor建造者,制造者,也就构造函数
console.log(Star.prototype.constructor === Star) //true
也就是说在原型对象prototype里面有一个constructor属性,这个属性就是整个构造函数的代码
console.log(Star)
//结果就是function Star(name){
//this.name =name
//}
那我搞个这东西干嘛呢?吃饭没事干?
应用:
我们都知道prototype对象可以为构造函数挂载公共方法,但是一个一个添加太麻烦了,所以我们经常直接为prototype对象赋值,那么就会覆盖掉原有的对象内容,原有自带的constructor就会消失,所以需要手动再添加一个constructor属性指向构造函数
问题:
我手动添加constructor的话,就代表已经知道构造函数是谁了,为什么还要用constructor来知道构造函数是谁???
https://blog.csdn.net/carayq/article/details/120453930
https://wangdoc.com/javascript/oop/object.html#objectprototype__proto__
我们并不需要写 构造函数.prototype.constructor来获取,只需要通过对象.–proto–.constructor,当生成了很多个对象,就这个通过这个constructor快速知道此对象是由哪个构造函数生成。
思考
因为生成的每个对象都有一个属性__proto__指向原型对象,所以可以共用挂载的函数方法。
这里注意
1-prototype是原型对象,是一个对象
2- __proto__是一个对象原型,它指向这个对象的原型对象,它是每个构造函数生成的对象的一个属性,所以
实例对象.__proto__===实例函数.prototype
3-有的浏览器控制台中会用prototype表示 __proto__
<script>
function Star() {
}
const ldh = new Star()
// 对象原型__proto__ 指向 改构造函数的原型对象
console.log(ldh.__proto__)
// console.log(ldh.__proto__ === Star.prototype)
// 对象原型里面有constructor 指向 构造函数 Star
console.log(ldh.__proto__.constructor === Star ===Star.prototpe.constructor) //true
</script>
__proto__.constructor和prototype.constructor的关系
原型继承
原型继承就是写一个对象,然后赋予给构造函数的prototype原型对象
<!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>
</head>
<body>
<script>
// 继续抽取 公共的部分放到原型上
// const Person1 = {
// eyes: 2,
// head: 1
// }
// const Person2 = {
// eyes: 2,
// head: 1
// }
// 构造函数 new 出来的对象 结构一样,但是对象不一样
function Person() {
this.eyes = 2
this.head = 1
}
// console.log(new Person)
// 女人 构造函数 继承 想要 继承 Person
function Woman() {
}
// Woman 通过原型来继承 Person
// 父构造函数(父类) 子构造函数(子类)
// 子类的原型 = new 父类
Woman.prototype = new Person() // {eyes: 2, head: 1}
// 指回原来的构造函数
Woman.prototype.constructor = Woman
// 给女人添加一个方法 生孩子
Woman.prototype.baby = function () {
console.log('宝贝')
}
const red = new Woman()
console.log(red)
// console.log(Woman.prototype)
// 男人 构造函数 继承 想要 继承 Person
function Man() {
}
// 通过 原型继承 Person
Man.prototype = new Person()
Man.prototype.constructor = Man
const pink = new Man()
console.log(pink)
</script>
</body>
</html>
为什么会这样呢?
因为男人女人两个构造函数引用赋予的prototype都是Person对象,这个对象赋予过去的是一个地址,指向同一片内存,一个通过操作改变如添加吸烟方法,另一个自然也改变。
怎么办?
很简单,我们让赋予给不同构造函数也就是男人女人两个构造函数的prototype的对象不同就行了,但是如果就男人女人两个构造函数还好说,如果有成千上万个要赋予相同方法和属性的构造函数怎么办呢?
再使用构造函数,可以构造出有相同的内容,不同地址的对象。
问题:
原先不是说prototype就是为了节省内存,共用相同的方法,你这又开辟不同的对象,还节省个啥?
我看的是黑马的就业班教程,es6就四天,后续会看codewhy王元红老师的2022课程进行查漏补缺
我的理解是构造函数woman和man的内存确实节省了,因为挂载了共用方法属性
但是为了不让男生添加吸烟方法,女生也被挂载上,所以必须牺牲内存去生成设置不同地址的对象。
构造函数分为父类和子类构造函数
如上的person为父类构造函数,woman和man是子类构造函数。
原型链
1-只要是对象,就都有–proto–,它指向生成这个对象的构造函数的原型对象prototype
2-1中的原型对象也是对象,它自然也有–proto–,指向更上一层的prototype
3-只要是原型对象,就有constructor
demo.constructor === demo.__proto__.constructor
为什么如上会相等?
因为有原型链的线路存在,demo.constructor === demo.proto.constructor找到的东西是一样的。
如arry.prototype.map(),如果在arry对象中没有map方法,事实上也确实没有,就会去array的prototype中寻找,就像我们自己定义的构造函数一样,如果再没有就会再查找上一层(事实上map方法就挂载在array.prototype)
instanceof运算符
<script>
// function Objetc() {}
console.log(Object.prototype)
console.log(Object.constructor)
console.log(Object.prototype.__proto__)
function Person() {
}
const ldh = new Person()
// console.log(ldh.__proto__ === Person.prototype)
// console.log(Person.prototype.__proto__ === Object.prototype)
console.log(ldh instanceof Person) //true ldh对象是由person构造函数生成的
console.log(ldh instanceof Object) //true,ldh在object这条原型链上
console.log(ldh instanceof Array) //false ldh对象不属于数组
console.log([1, 2, 3] instanceof Array) //true
console.log(Array instanceof Object) //true 万物皆对象哈哈
</script>
综合案例
面向过程的话,非常简单,只需要获取dom,然后设置事件就好了。但是我们这里需要联系面向对象,用构造函数实现。
什么意思呢
1-就是定义一个modal函数,它的作用是创造对象,也就是那些按钮的对象,如上的删除和登入
2-这个构造函数上面挂载上open和close方法。
<!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>
<!-- <div class="modal">
<div class="header">温馨提示 <i>x</i></div>
<div class="body">您没有删除权限操作</div>
</div> -->
<script>
// 1. 模态框的构造函数
function Modal(title = '', message = '') {
// 公共的属性部分
this.title = title
this.message = message
// 因为盒子是公共的
// 1. 创建 一定不要忘了加 this
this.modalBox = document.createElement('div')
// 2. 添加类名
this.modalBox.className = 'modal'
// 3. 填充内容 更换数据
this.modalBox.innerHTML = `
<div class="header">${this.title} <i>x</i></div>
<div class="body">${this.message}</div>
`
// console.log(this.modalBox)
}
// 2. 打开方法 挂载 到 模态框的构造函数原型身上
Modal.prototype.open = function () {
if (!document.querySelector('.modal')) {
// 把刚才创建的盒子 modalBox 渲染到 页面中 父元素.appendChild(子元素)
document.body.appendChild(this.modalBox)
// 获取 x 调用关闭方法
this.modalBox.querySelector('i').addEventListener('click', () => {
// 箭头函数没有this 上一级作用域的this
// 这个this 指向 m
this.close()
})
}
}
// 3. 关闭方法 挂载 到 模态框的构造函数原型身上
Modal.prototype.close = function () {
document.body.removeChild(this.modalBox)
}
// 4. 按钮点击
document.querySelector('#delete').addEventListener('click', () => {
const m = new Modal('温馨提示', '您没有权限删除')
// 调用 打开方法
m.open()
})
// 5. 按钮点击
document.querySelector('#login').addEventListener('click', () => {
const m = new Modal('友情提示', '您还么有注册账号')
// 调用 打开方法
m.open()
})
</script>
</body>
</html>
1.下面关于编程思想说法错误的是? (d) 分值1分
A: 面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用就可以了
B:面向对象是把事务分解成为一个个对象,然后由对象之间分工与合作
C: 面向过程编程性能比面向对象高,但是没有面向对象易维护、易复用、易扩展
D:面向对象编程 易维护、易复用、易扩展,性能也比面向过程高
回答正确+1分
答案解析:
面向对象性能一般情况下比面向过程低
2.下面是面向对象编程特性的是? (abc) 分值1分
A:封装性
B:继承性
C:多态性
D:优先级
回答正确+1分
3.原型(原型对象)是? (b) 分值1分
A:Object
B:prototype
C:__proto
D:consturctor
回答正确+1分
4.原型(原型对象)的作用说法错误的是? (d) 分值1分
A:构造函数通过原型分配的函数是所有对象所 共享的
B:JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象,所以我们也称为原型对象
C: 我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法
D:原型经常挂载函数,但是比较耗费内存
回答正确+1分
答案解析:
原型对象上面挂在函数,可以实现共享,反而更加节省内存,因为不需要每次都创建这个函数
5.关于构造函数和原型对象里面this指向说法正确的是? (a) 分值1分
A:构造函数和原型对象中的this 都指向 实例化的对象
B:构造函数的this指向window
C: 原型对象的this指向构造函数
D:构造函数和实例对象里面没有this,跟箭头函数一样
回答正确+1分
6.下面关于constructor 属性说法正确的是? (abcd) 分值1分
A:每个原型对象里面都有个constructor 属性
B:原型对象里面都有个constructor 属性指向该原型对象的构造函数
C: 对象原型__proto里面也有一个constructor属性,指向创建该实例对象的构造函数
D:原型对象constructor属性的使用场景:可以重新指回指向原来的构造函数
回答正确+1分
7.对象原型是? (c) 分值1分
A:Object
B:prototype
C:__ proto__
D:consturctor
回答正确+1分
8.为什么实例对象可以访问构造函数的原型对象中的属性和方法呢? (a) 分值1分
A:因为实例对象里面有对象原型 _ proto__ 它指向原型对象
B:因为实例对象有constructor 指向原型对象
C: 因为实例对象有 this指向原型对象
D:没啥原因,反正就是能….
回答正确+1分
9.下列选项中关于原型链说法正确的是? (abcd) 分值1分
A:对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线
B:当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性
C:如果没有就查找它的原型(也就是 proto指向的 prototype 原型对象)
D:如果还没有就查找原型对象的原型(Object的原型对象)依此类推一直找到 Object 为止(null)
回答正确+1分
10.下列选项中关于原型继承说法正确的是? (abc) 分值1分
A:核心是子类原型继承父类的实例: Man.prototype = new Person()
B:父类新增原型方法或是原型属性子类都可以访问到
C:Man.prototype = new Person() 其中 new Person() 生成一个对象,并且是独立的,互不影响
D:Man = new Person() 其实也可以的
回答正确+1分
答案解析:
不能直接 Man = new Person() 要不然就替换 Man函数了