面向对象
1.什么是:对象解构描述每个事物的属性和方法,按照调用对象中保存的属性和方法;
2.为什么:便于大量数据的管理和使用
3.何时 :几乎所有程序都用面向对象思想开发出来
面向对象三大特点:封装 继承 多态
封装
创建对象
(1) 用{}创建一个对象:
var obj={
属性名:属性值,
方法名:function(){}
}
//例子
var lucy={
sname:"lucy",
sage:17,
intr:function(){
alert(`I'm ${this.sname},${this.sage}`)
}
}
(2)用new Object()创建一个对象
var 对象名=new Object();
对象名.属性名=属性值;
对象名.方法名=function(){}
//例子
var lilei=new Object();
lilei.sname="Li Lei";
lilei.sage=11;
lilei.intr=function(){
console.log(`I'm${this.sname,this.sage}`)
}
console.log(lilei)
缺点: 麻烦,所以很少用
揭示: 一切对象底层都是关联数组
1).存储结构相同: 都是名值对儿的集合
2).强行访问对象中/数组中不存在的一个成员/位置,返回undefined.
3).强行给对象/数组中一个不存在的属性/位置赋值,自动添加该属性/元素。
4). 其实无论是对象,还是关联数组,都可用["属性名"]方式访问成员的值。而且都可以简写为".属性名"
5). 都可用for in循环遍历: for(var 变量 in 对象或关联数组){}
(3)构造函数
问题:一个{}只能创建一个对象 如果要反复创建多个相同结构的对象时 代码就会很冗余 重复很多 极其不便于维护
解决: 使用构造函数
构造函数:专门描述同一类型的所有对象的统一结构的函数
何时: 今后只要需要反复创建多个相同结构,但是属性值不同的同一类型的多个对象时,都要用构造函数
定义构造函数:
function 类型名(形参1, 形参2, ...){//Student Product Food
//必须都具有以下属性和方法
this.属性名1=形参1;
this.属性名2=形参2;
this.方法名=function(){
... this.属性名 ...
}
}
示例:
function student(sname,sage){
this.sname=sname;
this.sage=sage;
this.inre=function(){
console.log(`我是${this.sname},年龄${this.sage}`)
}
}
var lilei=new student('LiLei',23); //用new调动构造函数
var hmm=new student('HanMei',21);
构造函数原理:new做了4件事
(1). 创建一个新的空对象:
(2). 自动设置新创建的子对象继承构造函数的原型对象
(3). 调用构造函数,new自动将构造函数中的this改为指向当前正在创建的空对象。
a. 每一句this.属性名=属性值,都变成了新对象.属性名=属性值。
b. 因为新对象中一穷二白,什么属性都没有,所以每一句"新对象.属性名=属性值",都变成了给新对象中强行赋值!
c. 如果构造函数中包含“this.方法名=function(){ ... }”,则每次调用构造函数时,都会反复创建一个新函数,给新对象使用!
(4). 返回新创建的对象地址保存到等号左边的变量里
问题:
1.在对象内的方法,也不能直接使用对象自己的属性,如果直接使用,会报错未被定义
原因:
1.对象的方法虽然保存在对象中,但因为不是一级作用域,方法的作用域链不包含对象本身,方法中所有不点的变量
2.方法中所有不点.的变量名,都只能在函数内和全局两个范围内查找。无权擅自进入任何对象中查号属性
解决:
1.写成"对象名.属性名" (此方法如果外部对象改名了,里面也得跟着变,不能写死)
2.写成"this.属性名"
原理:
1.this专门引用正在调动当前的.前的对象的关键字
2.只要想在函数中,获得正在调用函数的.前的对象中的成员时都用this来指代
函数 vs 方法
相同点:无论函数还是方法,本质都是function;
不同点:不属于任何对象的,在对象外创建的函数称为函数,对象内创建的function称为方法
克隆对象
示例:
<script>
var lilei={
sname:"Li Lei",
sage:11
}
function clone(oldobj){ //定义克隆函数,当调用时传入需要克隆的对象
var newObj={}; //创建一个新的空对象
for(var key in oldobj){ //遍历需要克隆对象的属性
newObj[key]=oldobj[key] //没遍历一个属性,就给新对象添加同名属性
}
return newObj; //返回组装好的新对象
}
var lilei2=clone(lilei)
console.log(lilei2==lilei) //false 从此lilei和lilei2不再执行同一个对象ok
</script>
继承
问题:如果构造函数中包含方法定义,没创建一次新对象,就会创建方法的副本,浪费内存.
解决:今后构造函数中不应该包含方法定义.
新问题:方法定义放在哪才能只定义一次就能让所有对象共同使用
解决:继承
什么是继承:父对象中的成员,子对象无需重复创建,可以使用
为什么要用:既重用方法,又能节约内存
何时用:只要希望方法只定义一次,就能让所有新对象共同使用,通过继承实现
如何使用:
(1)JS的继承都是用过继承原型对象来实现的
(2)什么是原型对象:专门保存一个类型的所有子对象共有的成员的父对象
(3)何时:只要为一个类型下的所有子对象定义共有的成员,都要放在原型对象中
(4)如何访问原型对象:构造函数.prototype
(5)何时子对象继承父对象:自动完成继承,new 自动设置新的子对象继承构造函数的原型对象
(6)结果:只要子对象的__proto__属性指向了父对象,子对象可直接调用父对象的成员
示例:
function student(sname,sage){
this.sname=sname;
this.sage=sage;
}
console.log(student.prototype) //不用自己创建原型对象,就自动附赠了一个Student类型的空的原型对象
student.prototype.intr=function(){ //强行向构造函数的原型对象中添加一个共有方法,让所有子对象共用
console.log(`我是${this.sname},${this.sage}`)
}
var lilei=new student('lilei',23)
var hanm=new student("hanmm",21)
//验证
console.log(lilei.__proto__==hmm.__proto__);//true
//lilei的proto是Student的原型对象吗?
console.log(lilei.__proto__==Student.prototype);//true
自有属性和共有属性
自有属性: 直接保存在当前对象内部,归当前内部独有的属性
共有属性: 保存在原型对象中,归所有子对象共有的属性
获取属性值:子对象.属性名
修改自有属性: 子对象.自有属性名=新属性值
修改共有属性:
错误做法: 虽然获取共有属性值时,可以用"子对象.共有属性名",但是,修改时,不能使用"子对象.共有属性=新值"
正确做法:构造函数.prototype.共有属性名=新值 结果: 所有子对象跟着一起变!
<script>
//定义构造函数妈妈
function Student(sname,sage){
this.sname=sname;
this.sage=sage;
}
//向Student类型的原型对象中添加一个共有属性——班级名
Student.prototype.className="初一2班"
//2. 用new来调用构造函数
var lilei=new Student("Li Lei",11);
var hmm=new Student("Han Meimei",12);
//尝试获取每个对象自己内部的自有属性值
console.log(lilei.sname, hmm.sname);
//尝试获取原型对象中的共有属性值
console.log(lilei.className, hmm.className);
//修改lilei的自有属性年龄
lilei.sage++;
console.log(lilei.sage, hmm.sage);
//错误: 使用lilei,拉着全班同学一起留级
lilei.className="六年级2班";
//结果lilei不但修改不了共有属性className,而且会自动在lilei对象中添加一个同名的自有属性className,保存和全班其它同学不同的班级名。
//从此,李磊在className这一个属性的使用上,跟全班同学,分道扬镳!
console.log(lilei.className, hmm.className);
//正确: 过了一年全班同学一起升级到初二2班
Student.prototype.className="初二2班";
console.log(lilei.className, hmm.className);
console.log(lilei);
console.log(hmm);
</script>
内置类型的原型对象
ECMAScript标准中规定的,浏览器已经实现的,我们可以直接使用的类型或对象。
string number boolean array date regexp math error function object global
除了math 和 global 其他9种类型 都由构造函数和原型对象组成
示例:Array类型:
function Array(){ ... } 构造函数
Array.prototype 原型对象
假如我们经常需要对数组的求和,可以自己创建Array的方法
Array.prototype.sum=function(){
var result=0;
for(var i=0;i<this.length;i++){
result+=this[i]
}
return result
}
var arr=[1,2,3];
console.log(arr.sum())
原型链
(1).问题: 构造函数和原型对象,有没有父对象呢?
(2). 其实js内存中也是大家庭!
a. 其实每个原型对象也有_ _proto_ _指向自己的父级对象
b. 因为原型对象是自动创建的,所以原型对象的_ _proto_ _都统一指向顶级父类型——Object类型
c. Object的原型对象=null,所以Object类型是顶级父类型
(3). 什么是原型链: 由多级父对象逐级继承形成的链式结构
(4). 保存着一个对象可用的所有属性和方法。
a. 只要在原型链上的属性和方法,这个对象都可直接使用!
b. 如果没在原型链上的属性和方法,这个对象就无法使用
(5). 控制着属性和方法的使用顺序: 就近原则
a. 当对象访问一个属性或方法时,现在当前对象内部查找自有属性和方法。优先使用自有属性和方法。
b. 除非对象自己内部没有想要的自有属性和方法,才被迫延原型链向父元素查找
总结一句话:保存着一个对象可用的所有属性和方法。控制着属性和方法的使用顺序:先自有再共有
多态
重写: 只要觉得从父对象继承来的成员不要用,都在子对象中重写同名成员
如果觉得这个父对象对象都不好用,可以自定义继承: 2种:
1). 只换一个子对象的父对象: 2种:
i. 子对象.__proto__=新父对象
ii. Object.setPrototypeOf(子对象, 新父对象)
2). 更换多个子对象的原型对象: 构造函数.prototype=新对象
//例子
<script>
function Student(sname,sage){
this.sname=sname;
this.sage=sage;
}
//为了让所有学生都有好用的toString()来输出学生信息,就需要在学生类型的原型对象中添加toString()方法
Student.prototype.toString=function(){
//拼一个我自己喜欢的字符串格式——可自定义
return `{
sname:"${this.sname}",
sage:${this.sage}
}`
}
var lilei=new Student("Li Lei",11);
var arr=[1,2,3];
var now=new Date();
console.log(lilei.toString());
console.log(arr.toString());//可以输出数组中的元素值
console.log(now.toString());//可以输出年月日时分秒
</script>
自定义继承
如果原型对象中只有个别东西不好用,可以重写.但是,如果整个原型对象毫无可取之处,我们就用到自定义继承
如果整个原型对象都不是想要的,想换成新的原型对象时
如何:
1). 只修改一个对象的父对象: 2种方法:
a. 子对象._ _proto_ _=新父对象
b. 问题: _ _proto_ _不建议直接使用
c. 解决: Object.setPrototypeOf(子对象, 新父对象)
示例:
function student(sname,sage){
this.sname=sname;
this.sage=sage;
}
var father={
money:10000000,
car:'benz'
}
var lilei=new student('lilei',11);
var hanm=new student('hanmm',12);
console.log(lilei,hanm) //prototype 是空的
Object.setPrototypeOf(hanm,father) /*******************************/
console.log(hanm.money,hanm.car) //10000000 benz
console.log(lilei.money,lilei.car) //undefined undefined
this
(1)全局作用域下 this指向window对象
(2)严格模式下的全局函数 this指向undefined
(3)构造函数 this指向new
(4)定时器 this指向window
(5)元素绑定事件触发后 this指向当前元素
(6)对象中调用的方法 谁调用this就指向谁