1.面向对象基本介绍
- 什么是面向对象
所谓的面向对象,是一种编程思想,编程思路,代码的书写格式
之前为了简化代码,我们是将程序封装为函数的形式来调用
函数会有一些小问题:
函数内部定义的局部作用域变量,函数外部无法直接调用
函数调用使用时,会占用大量的系统内存,容易造成数据溢出数据泄露,容易被黑客攻击
函数一般只是封装一些模块化的功能
复杂程序的封装会,封装成其他形式,使用面向对象的方式来封装程序 - 简单介绍面向对象的编程思想
我们之前的编程方式,称为面向过程的编程方式
面向过程和面向对象编程思想的区别
举例生活中的实例 : 中午要吃饺子
面向过程:
1,和面
2,和饺子馅
3,包饺子–擀皮,放馅,包饺子
4,烧水
5,水开了煮饺子
6,捞饺子
7,吃饺子
面向对象:
1,找一个饺子馆 — 封装的面向对象的程序,完成制作饺子的过程,我们只要点就可以了
2,点饺子 — 调用封装的程序
3,做好了吃饺子
4,结账
总结
面向过程 : 自己独立的一步一步的完成程序的定义和执行
面向对象 : 有封装好的面向对象的的程序
直接调用执行就可以了
功能和作用类似于 封装好的函数
但是 封装的语法和思想与函数不同
2. 对象的再介绍
为什么要面向对象,不是面向字符串等
对象的优点
-
对象中,可以定义并且存储多个数据单元以及所有JavaScript支持的数据类型
// const obj = {age:18,name:'张三',fun:()=>{},arr:[1,2,3,]}
-
对象中,调用具体数据很方便
// 调用数据时,不用考虑数据的顺序 // const obj1 = {age:18,name:'张三'} // const obj2 = {name:'张三',age:18} // 调用 name 和 age 都是不用考虑 数据单元的顺序 // 只要键名/属性 输入正确就可以了
-
对象中,可以定义函数,还可以通过函数的this,方便的调用对象本身的数据
// const obj1 = {age:18,name:'张三',fun:function(){ console.log(this.name) }} // const obj2 = {age:18,name:'张三',fun:function(){ console.log(this.name) }} // 调用对象的数据,不用管对象名称 是什么,只要通过this,就可以指向这个对象, // obj1 中的 this,指向的就是obj1 obj2 中的 this,指向的就是obj2 // this.name 分别就是 obj1.name obj2.name
4. 面向对象编程的基本思想
面向对象的基本思想
基本思路就是,创建一个对象,给这个对象,添加上属性和属性值,还有函数等方法
之后通过操作这个对象,来完成需要的效果
先通过一个函数的方法,来创建对象
function createObj(){
// 创建对象
const obj = {};
// 给对象添加属性和属性值
obj.name = '张三';
obj.age = 18;
obj.addr = '北京';
obj.sex = '男';
// 给对象添加方法
obj.funNameAge = function(){
console.log(this.name , this.age);
}
obj.funNameAddr = function(){
console.log(this.name , this.addr);
}
obj.funSexAge = function(){
console.log(this.sex , this.age);
}
obj.funAll = function(){
console.log(this.name, this.sex , this.age, this.addr);
}
// 返回这个创建好的对象
return obj;
}
// 调用函数,函数创建对象,并且作为返回值
// 变量中存储的就是函数创建的对象
const obj = createObj();
// 可以通过调用obj中存储的对方的方法,来实现功能
obj.funAll();
5. 工厂模式
工厂模式
所谓的工厂模式,是一种书写函数的语法形式,语法规范
就向工厂中的流水线一样,按照步骤来执行需要的操作
步骤1,创建对象
步骤2,给对象定义属性和属性值
步骤3,给对象添加方法
步骤4,定义对象为返回值
标准的工厂模式,会有对应的参数
function createDumpling(pi,mian,xian,tioliao){
// 创建包饺子对象
const dumpling = {};
// 给包饺子对象,添加属性
dumpling.pi = pi;
dumpling.xian = xian;
dumpling.mian = mian;
dumpling.tiaoliao = tiaoliao;
// 给包饺子对象,添加方法
// 和面
dumpling.huomian = function(){
console.log(this.mian);
}
// 和饺子馅
dumpling.huoxian = function(){
console.log(this.xian);
}
// 包饺子
dumpling.bao = function(){
console.log(this.xian,this.pi,this,mian);
}
// 煮饺子
dumpling.zhu = function(){
console.log('煮饺子了,等着吃吧');
}
// 返回包饺子对象
return dumpling;
}
// 要开始包饺子
// 创建一个执行包饺子功能的对象,并且输入参数
const baojiaozi1 = createDumpling('薄皮' , '白面粉' , '猪肉大葱');
const baojiaozi2 = createDumpling('厚皮' , '玉米面' , '鱼香肉丝');
console.log(baojiaozi1);
// 可以调用任意的封装的方法
baojiaozi1.huoxian(); // 调用和饺子馅方法
baojiaozi1.huomian(); // 调用和面方法
6. 面向对象编程的优点
面向对象编程的优点
优点与函数的优点类似
高内聚 低耦合
高内聚 : 将所有需要的程序,都定义封装在对象内 ; 对象中存储所有需要的属性,所有需要的方法
低耦合 : 尽量减少特殊程序的执行
面向对象编程的特点
抽象 — 通过描述 对象 共有的特点(属性和属性值) , 来形容一个对象
这个对象不是一个非常具体事例的内容,是一个抽象化的实例
制作饺子,制作包子,制作馅饼,制作馅窝头…
有皮,有汁,味甜 — 西瓜 , 橘子 , 桃 , 爆浆蛋糕 , 夹心糖果 …
四条腿的,有一个面的 — 桌子 , 凳子 , 床 …
封装 — 将所有的程序,都定义在一个对象中
7. 自定义构造函数
创建对象的方法,有两种
字面量 const obj = {}
构造函数 const obj = new Object()
这个构造函数,就是JavaScript程序定义好的构造函数,我们直接使用就可以了
所谓的构造函数,实际也是一种函数
构造函数专门用于生成,定义对象的
通过构造函数,生成的对象,称为实例化对象
强调: 构造函数,就是一种函数,是专门用于生成对象的函数
实例化对象,就是通过构造函数,生成的对象,称为实例化对象
构造函数分为两种,一种是JavaScript程序定义好的构造函数,称为 内置构造函数
一种是程序员自己定义的构造函数,称为 自定义构造函数
构造函数和普通函数的区别
- 构造函数一定要和 关键词 new 一起使用
new 关键词具有特殊的功能,
会自动的给 构造函数中 定义一个对象,并且返回这个对象
我们只要对这个对象设定属性,设定方法就可以了 - 构造函数为了和其他函数区别
语法规范规定,构造函数,函数名称,第一个字母必须大写,使用大驼峰命名法 - 构造函数,给对象定义属性和方法的语法,与一般函数不同
实例化对象和普通的对象,没有任何的区别,只是建立方式不同而已
自定义构造函数
注意:
(1) 函数名称要使用大驼峰命名法
(2) 自定义构造函数中,不要定义对象,和 定义return
new 关键词会执行,如果定义了,会产生冲突
function CrtObj(name,age,sex,addr){
// 在构造函数中,使用this,来指代对象
// 这个对象,就是我们使用构造函数,生成的实例化对象
// 定义属性
// 给实例化对象,添加name属性,属性值是输入的name参数
this.name = name;
this.age = age;
this.sex = sex;
this.addr = addr;
// 定义方法
this.funAll = function(){
console.log(this.name,this.age,this.sex,this.addr )
}
this.funNameAge = function(){
console.log(this.name,this.age)
}
this.funSexAddr = function(){
console.log(this.sex,this.addr )
}
}
// 通过自定义构造函数来生对象,实例化对象
// 调用执行构造函数时,都必须要和new 关键词一起使用
const obj1 = new CrtObj('张三',18,'男','北京');
console.log(obj1);
// 调用 对象/实例化对象 中的方法
obj1.funAll();
obj1.funNameAge();
obj1.funSexAddr();
总结
new 的作用
- 在构造函数中,自行创建一个对象,并且返回这个对象
- 因为new 关键词,创建了对象,此时,构造函数中的this,才会指向这个对象
也就是将来生成的实例化对象 - 所有的构造函数中,this的指向,都是将来通过这个构造函数生成的实例化对象
7. 解决构造函数中的小问题
<script>
// 自定义构造函数
function CrtObj(name, age, sex, addr) {
// 定义属性
this.name = name;
this.age = age;
this.sex = sex;
this.addr = addr;
// 定义方法
this.funAll = function () {
console.log(this.name, this.age, this.sex, this.addr);
}
}
// 定义了构造函数,可以生成实例化对象
const obj1 = new CrtObj('张三',18,'男','北京');
const obj2 = new CrtObj('李四',19,'女','上海');
const obj3 = new CrtObj('王五',20,'不详','火星');
// 每个实例化对象中,都有属性和方法
// console.log(obj1);
// console.log(obj2);
// console.log(obj3);
// 通过同一个构造函数,生成的实例化对象
// 属性相同,属性值可能不同
// 定义的方法的程序,是相同的
// 但是,如果做一个比较判断,结果是 false
// 表示,不同的实例化对象中,定义的是不同的方法/函数
// 不同的方法和函数,会占用过多的内存空间
// 要想办法,让使用同一个构造函数,生成的实例化对象,都是相同的方法
// 原因:每次创建对象,都会在对象上定义一个新的方法,也就是新的函数
// 函数存储时会生成一个独立的存储空间,不同函数有不同的存储空间
// console.log( obj1.funAll == obj2.funAll );
// 解决的方式
// 将构造函数需要定义给实例化对象的方法,定义在函数的 prototype 属性中
function CrtObj2(name, age, sex, addr) {
// 定义属性
this.name = name;
this.age = age;
this.sex = sex;
this.addr = addr;
}
// 在构造函数的 prototype 属性中,来定义实例化对象的方法
CrtObj2.prototype.fun = function(){
console.log(this.name, this.age, this.sex, this.addr);
}
const obj4 = new CrtObj2('张三',18,'男','北京');
const obj5 = new CrtObj2('李四',19,'女','上海');
// console.log(obj4);
// console.log(obj5);
// obj4.fun();
// console.log( obj4.fun === obj5.fun );
// 详细解释一下 prototype
// 到底 prototype 是什么?
// prototype 是每个函数本身就具有的一个特殊的属性
// 可以在这个特殊的属性中,存储 数据和函数
function fun(){}
// 向 fun 函数 中的 prototype 中,定义属性 name 属性值 张三
fun.prototype.name = '张三';
fun.prototype.f = function(){console.log(123)};
console.dir( fun );
console.log( fun.prototype.name );
fun.prototype.f();
// 在构造函数中, prototype 的所用和使用方法
// 在构造函数内部,定义实例化对象的属性
// 通过 this关键词,指向实例化对象
function CreFun(name,age){
this.name = name;
this.age = age;
}
// 如果是构造函数,创建实例化对象
// 给实例化对象,添加属性,是通过this方法来添加定义的
// 定义在 prototype 中的属性,就是写在 prototype 中的,不会写在实例化对象中
CreFun.prototype.name2 = '李四';
CreFun.prototype.age2 = 180;
// 在 prototype 中定义的方法/函数,也不会定义在实例化对象上
// 只会写在 prototype 中
CreFun.prototype.ff = function(){
console.log('1111');
}
// JavaScript中,每一个对象,都有一个 __proto__
// 实例化对象的 __proto__ 就是指向的 构造函数中, prototype 这个属性
// 通过构造函数,生成的实例化对象,这歌实例化对象, __proto__ 存储的地址
// 就是生成这个实例化对象的构造函数的 prototype 的地址
// 实例化对象的 __proto__ 实际上 就是指向 构造函数的 prototype
// 在对象中,调用数据,先在对象本身上找,有没有这个属性
// obj6.name 企图调用 obj6中,name属性
// 先在 obj6 对象本身的属性上找 , 如果有就调用本身属性上,对应的数据
// 如果调用的属性,对象本身没有这个属性,会自动去 __proto__ 中寻找
// 如果有,就使用 __proto__ 当中的数据
// obj6.name2 企图调用 obj6中,name2属性
// 实际上,obj6中,本身没有 name2属性
// 去 __proto__ 中 寻找,没有 name2
// 如果有就会使用 name2 的数据
const obj6 = new CreFun('张三',18);
console.dir( CreFun )
console.dir(obj6);
console.log(obj6.name);
console.log(obj6.name2);
总结
函数有一个 属性 叫 prototype
其中可以定义,存储, 属性属性值 函数名称函数等等数据
定义在 prototype 中的内容 就是 函数自己本身的数据
对象有一个 属性 叫 proto
构造函数,在生成实例化对象时,会将自己 prototype 这个空间的地址
赋值给 实例化对象 的 proto 来存储
实际上 构造函数的 prototype 和 生成的实例化对象的 proto 指向的是同一个 存储空间
可以相互调用数据
构造函数
function Fun(){}
只定义在函数 Fun 中的数据
Fun.prototype.name = 'abc';
Fun.prototype.f = function(){};
// 通过 构造函数,生成实例化对象
const obj = new Fun();
构造函数 Fun 的 prototype 的 空间地址 就赋值给了
实例化对象 obj 的 __proto__
obj 的 __proto__ 和 Fun 的 prototype 指向的是同一个空间地址
通过构造函数,定义给实例化对象的属性,必须使用this,在构造函数中操作
function Fun(){
通过this方法才是定义给实例化对象的属性和方法
this.name = '张三'
this.age = 18
}
只是定义在 构造函数 Fun 中的数据,跟实例化对象没有半毛钱关系
Fun.prototype.name = 'abc';
Fun.prototype.f = function(){};
当调用 实例化对象 中 f函数时 obj.f()
实例化对象本身中,是没有这个函数的
会继续在 __protop__ 中寻找,是否有这个方法函数 f
__protop__ 实际上指向的 就是 构造函数的 prototype
构造函数的 prototype 中,是有这个方法的,那么就可以正常调用
所有通过这个构造函数生成的实例化对象,实际上,本身都没有定义方法和函数
使用时,都是调用生成实例化对象的构造函数,本身prototype中定义的方法
就不会重复生成函数,占用内存空间
8. 原型对象原型属性原型链
-
原型对象
每一个函数,天生都有一个 prototype 属性,称为原型对象
是一个专门用来存储数据,函数等内容的空间 -
原型属性
每一个对象,天生都有一个 proto 属性,称为原型属性
实例化对象的原型属性,指向的创建实例化对象的构造函数的 prototype函数,数组,等,JavaScript中的数据类型 实际存储的形式都是一个对象,都会有 __proto__ 属性 在JavaScript中,可以用对象的形式存储任何数据类型
-
原型链
所谓的原型链,就是所有相互关联的变量,使用 proto 属性串联起来
调用数据数据时,会通过 proto 将所有相互关联的 变量 串联
只要有一个变量中 有相应的属性,就会调用成功
// 字面量方式,创建字符串
// let str1 = 'beijing';
// console.dir(str1);
// // 构造函数方式,创建字符串
// let str2 = new String();
// console.dir(str2);
function fun(){}
console.dir(fun);
// 实例化对象1 ---> 通过构造函数1创建的
// 构造函数1是 实例化对象2 的方法
// 实例化对象2 ---> 通过构造函数2创建的
// 构造函数2是 实例化对象3 的方法
// 实例化对象3 ---> 通过构造函数3创建的
// 构造函数3 ---> 生成 实例化对象3 ---> 构造函数2 ---> 生成 实例化对象2 ---> 构造函数1 ---> 生成 实例化对象1
// 构造函数本身有 prototype 也有 __proto__
// 实例化对象 有 __proto__
/*
实例化对象1 __proto__ 指向 构造函数1的 prototype
构造函数1本身也有 __proto__ 指向的是 实例化对象2
实例化对象2 __proto__ 指向 构造函数2的 prototype
构造函数2本身也有 __proto__ 指向的是 实例化对象3
实例化对象3 __proto__ 指向 构造函数3的 prototype
最终,所有的构造函数,对象,数组等都会指向 JavaScript中的顶级对象 Object
会在 单例模式中 给大家演示
为什么 实例化对象 本身没有的函数方法, 构造函数上有,为什么可以调用使用
原因就是因为原型链的存在
实例化对象的 __proto__ 指向构造函数的 prototype , 就可以调用使用 构造函数 prototype中定义的方法
*/
9. 选项卡之面向对象
<style>
*{
margin: 0;
padding:0;
}
ul,ol,li{
list-style: none;
}
.cont{
width: 800px;
height: 600px;
border: 5px solid #333;
margin: 0 auto;
display: flex;
flex-direction: column;
}
.cont ul{
width: 100%;
height: 60px;
display: flex;
}
.cont ul li{
flex:1;
font-size: 35px;
color: #fff;
border-left: 2px solid blue;
border-right: 2px solid blue;
background: hotpink;
display: flex;
justify-content: center;
align-items: center;
}
.cont ol{
flex:1;
position: relative;
}
.cont ol li{
width: 100%;
height: 100%;
font-size: 150px;
display: flex;
justify-content: center;
align-items: center;
position: absolute;
top:0;
left:0;
background: burlywood;
display: none;
}
/* 按钮标签 哪个标签有这个属性,哪个就显示特殊背景颜色 */
.cont ul li.active{
background: skyblue;
color: black;
}
/* 内容标签 哪个标签有这个属性,哪个就显示 */
.cont ol li.active{
display: flex;
}
</style>
- 按钮1
- 按钮2
- 按钮3
- 内容1
- 内容2
- 内容3
<script>
// 面向过程值事件委托形式
// 获取父级div标签对象
// var oDiv = document.querySelector('div');
// // 获取标签对象
// var ullis = document.querySelectorAll('ul li');
// var ollis = document.querySelectorAll('ol li');
// oDiv.onclick = function(e){
// if(e.target.getAttribute('name') === 'ulli'){
// // 1,给所有的li标签,清除样式
// ullis.forEach(function(item,key){
// item.className = '';
// ollis[key].className = '';
// // 给item,也就是ul中的li标签,定义属性
// item.setAttribute('index',key);
// })
// e.target.className = 'active';
// ollis[e.target.getAttribute('index')].className = 'active';
// }
// }
// 面向对象的方法
// 创建一个对象,这个对象中有属性有方法
// 属性是需要操作的标签等
// 方法就是实现选项卡效果的程序
// 定义构造函数
// 参数:需要执行的tab切换的标签对象,是所有需要执行选项卡效果标签的父级
// 面向对象方法1,建立变量,存储this
function SetTab1(ele){
// 先要单独,接收存储参数
this.ele = ele;
// 通过参数,来获取需要的标签对象
this.ullis = ele.querySelectorAll('ul li');
this.ollis = ele.querySelectorAll('ol li');
// 在构造函数中,定义的this指向的是实例化对象
}
// 定义构造函数的方法
SetTab1.prototype.fun = function(){
// 选项卡思路
// 给所有的ul中的li,添加点击事件,点击时,清除所有的ul,ol中li的样式
// 给当前点击的li,添加样式,再给对应的ol中的li,添加样式
// 定义一个变量,专门存储this指向
// 此时的this指向,还是 实例化对象
// oldThis 变量中 存储的就是 实例化对象
let oldThis = this;
// 循环遍历 ul中所有的li
// 这里的 this.ullis 指向的是 实例化对向中的属性 this.ullis 中存储的数据
// this.ullis this.ollis this.ele 其中的this,应该都是指向实例化对象
this.ullis.forEach(function(item,key){
// 给 li标签添加点击事件
item.addEventListener('click' , function(){
// 在点击事件中,此时this指向的是绑定点击事件的标签 item
// 就不是 实例化对象了
// 此时,要想正确调用 实例化对象属性的属性值
// 必须 使用 一个 指向是 实例化对象的内容
// 先循环遍历所有的ul和ol中的li,清除css样式
oldThis.ullis.forEach(function(item2 , k){
item2.className = '';
oldThis.ollis[k].className = '';
})
// 给点击的ul,li添加样式
item.className = 'active';
// 给对应的ol,li添加样式
oldThis.ollis[key].className = 'active';
})
})
}
// 面向对象方法2,箭头函数
function SetTab2(ele){
// 先要单独,接收存储参数
this.ele = ele;
// 通过参数,来获取需要的标签对象
this.ullis = ele.querySelectorAll('ul li');
this.ollis = ele.querySelectorAll('ol li');
// 在构造函数中,定义的this指向的是实例化对象
}
// 定义构造函数的方法
// 这里的function不能改为箭头函数,里面的都改
SetTab2.prototype.fun = function(){
// 将构造函数中,所有的函数,都写成箭头函数
// this 都会指向父级,最终都指向 实例化对象
this.ullis.forEach( (item,key)=>{
// 把普通的函数,改为箭头函数,this指向就是父级程序的this指向
// 也就是 实例化对象了
// 如果不改,this指向的是绑定事件的标签,也就是item
item.addEventListener('click' , ()=>{
// 这里的forEach也要改为箭头函数,不然this指向的是window
this.ullis.forEach((item2 , k)=>{
item2.className = '';
this.ollis[k].className = '';
})
// 给点击的ul,li添加样式
item.className = 'active';
// 给对应的ol,li添加样式
this.ollis[key].className = 'active';
})
})
}
// 获取标签对象
const oDiv = document.querySelector('div');
// 通过构造函数,生成实例化对象
// const tabs1 = new SetTab1(oDiv);
// console.log(tabs1)
// 调用实例化对象中的方法,来执行程序,实现选项卡效果
// tabs1.fun();
const tabs2 = new SetTab2(oDiv);
tabs2.fun()
// 特别注意:
// 调用构造函数,尤其是封装了方法的构造函数,必须要在定义构造函数之后,来调用
// 如果先调用,可以调用构造函数,也可以生成实例化对象
// 实例化对象中,也有属性
// 但是,对 构造函数 prototype 赋值定义方法,不会提前执行
// 生成的实例化对象,就没有绑定方法
// 面向对象语法,最关键的就是this的使用
// 一定要分清楚,每一个函数中,this的指向
// 常用 方法1: 找变量,存储this指向
// 方法2: 将function,写成 箭头函数,this为父级程序this
</script>