什么是面向对象?
程序中先用对象来保存现实中一个事物的属性和功能,然后再按需访问对象中保存的属性和 功能。
优点:便于大量数据的维护和使用。
面向对象三大特点:
- 封装
- 继承
- 多态
封装
什么是封装?
创建一个对象,集中保存现实中一个事物的属性和功能
如何封装一个对象,如何创建一个对象?
①用{ }直接创建对象
var 对象名={
属性名:属性值,
... :...,
方法名:function(){
... this.属性名....
}
}
通过.操作符进行访问对象里的内容
访问对象里的属性值: 对象名.属性名
访问对象里的方法 : 对象名.方法名()
方法 vs 函数
相同:都是function
不同:独立于任何对象之外,单独存在的function,称为函数
保存在对象内的function,称为方法
注:
在对象中的函数中不能直接获取对象的属性 要用 this.属性名访问
this 是每个函数中自带的,可以自动获得正在调用当前函数的 .前的对象 的关键词
例如 :LiMing.interSelf() 在调用时.前是谁 this就指谁,这里指LiMing 相当于LiMing.sname
只要对象自己的方法内 想使用对象自己的属性,必须加“this.属性名”
//创建一个对象LiMing
var LiMing={
sname:"Li Ming",
sage:19,
interSelf:function(){
console.log(`我叫${sname}`) //报错sanme is not defined
//对象自己的函数 访问对象自己的属性 报错 因为所有不加.的访问默认只能在作用域链(函数作用域
和全局作用域window)中查找变量,无权进入对象属性中查找 用this.属性名解决
console.log( ` 我叫${this.sname} ,年龄 ${this.sage} ` )
}
}
//获取李明的年龄
console.log(LiMing.sage);
//请李明做自我介绍
LiMing.interSelf();
//李明长了一岁
LiMing.sage++;
//再获取李明的年龄
console.log(LiMing.sage);
② 用new Object( )创建;
先用new Object()创建一个空对象
为空对象强行添加新属性
// new Object()是 { } 的标准写法 , { }是简写
var LiMing = new Object();
LiMing.sname="Li Ming";
LiMing.sage=19;
LiMing.interSelf=function(){
...this.sname...
}
js中一切对象 底层都是关联数组!以下均可证明
- 对象和关联数组一样 都是“名值对儿”的集合
- 在对象访问成员属性(对象.sname)和关联数组中访问成员属性(arr["sage"])
LiMing.sname 和 arr["sage"] 一样 (对象.属性名 访问 和数组[" "]访问 一样)
LiMing.sname 会自动翻译为LiMing["sname"] 同样 arr["sage"] 可简写 arr.sage- 关联数组可以通过强行赋值的方式随时添加新元素,而对象也可以 不会报错
- 访问关联数组中 不存在的位置 不会报错而是返回underfined 同样访问对象中一个不存在的属性也不会报错 返回underfined
- 都可以用 for in 遍历每个对象中的属性 for ( kay in object ){ ${key} : object.[key]}
③ 用构造函数constructor创建;
前两种创建对象的方式适用于只创建一个对象的情况 ,而反复创建多个想同结构的对象时,代 码会很繁琐,且不便于维护,还占内存空间
//假设创建多个学生对象
var student1 ={
sname:"student1",
sage:19,
intrSelf:function(){
console.log(`姓名 :${this.sname} , 年龄:${this.sage}`)
}
}
var student2 ={
sname:"student2 ",
sage:20,
intrSelf:function(){
console.log(`姓名 :${this.sname} , 年龄:${this.sage}`)
}
}
var student3 ={
sname:"student3 ",
sage:21,
intrSelf:function(){
console.log(`姓名 :${this.sname} , 年龄:${this.sage}`)
}
}
var student4 = { .... }
//如果还用之前{ }的方式定义对象 会造成代码冗余 而且及其不便于修改和维护,那么我们就要用构造函数创建对象 来规避这个缺点
如果要反复创建多个相同结构的对象时,都要用构造函数
如何使用构造函数?
①定义一个构造函数来描述一类对象统一的结构
function 类型名 (形参1,形参2){ //新对象有几个属性 构造函数上就要定义几个形参变量
this.属性名=“形参1”, //新对象中的每个属性的方法都要用this.定义
this.属性名=“形参2”,
this.方法名=function(){ ... this.属性名 ...},
}
②反复用 new 调用构造函数,就会反复创建多个相同类型的对象
用法: var 新对象 = new 类型名(属性值1,属性值2,...)
//用构造函数来定义 对象
function Student (sname,sage){
this.sname = sname,
this.sage = sage,
// this.intrSelf = function(){ console.log(`姓名:${this.sname} , 年龄:${this.sage}`) },
}
//用new 调用构造函数 创建对象
var student1= new Student("Li Ming",19) //输出和 { } 创建的对象是一样的
console.log(student1)
//反复创建多个对象 就很便于维护
var student2= new Student("Zhang San",20)
var student3= new Student("Li Si",20)
首先,new 会创建一个新对象
然后,调用构造函数时的实参值(属性1,属性2,...)会传给构造函数的形参变量
然后,构造函数中规定的所有属性和方法,都会被添加到新对象中
最后,new会返回新创建的对象 保存到=左边的变量里
那么new 是怎样执行的呢?(构造函数的原理)
①new创建一个新的空对象
②让新创建的子对象,自动继承构造函数的原型对象
③用新对象调用构造函数,此时的 构造函数里的this指向正在创建的新对象new,通过强行赋值的方式,将构造函数的所有属性和方法,强行添加到 正在创建的新对象中
问题:反复用构造函数创建多个对象 那么凡是在构造函数中的方法定义 每次创建一个新对象都要重复创建一遍这个方法 会造成占用内存 所以构造函数中不应该包含方法的定义 只包含属性的定义。
继承
因为构造函数只能重用代码结构,但是会浪费内存,所以构造函数不包含方法的定义
所以 今后所有对象中要使用的公共的方法的定义 要使用继承
什么是继承?
父对象中的成员,子对象无需重复创建就可以直接使用
既可以重用代码 ,又可以节约内存!
什么时候使用继承?
只要多个对象都需要公共的该方法定义(函数)时,就要用继承来实现
如何使用继承?
①找到所有子对象 共同的父对象 (原型对象:为将来所有子对象,保存共有特征的一个父对象)
什么是原型对象?
其实在 定义构造函数中都会附赠一个原型对象prototype(父对象),
并且可以通过构造函数函数名.prototype访问到 该对象暂时为空
new会让创建的子对象自动继承构造函数的原型对象
new会自动设置 新创建的子对象._ _ proto_ _ = 构造函数.prototype
②将所有子对象公共的方法定义添加到共同的父对象中
构造函数名.prototype.公共方法名=function(){ ... this.属性名 ...}。
③所有子对象因为继承关系 就可以直接使用父对象中的公共方法。
当子对象调用原型对象上的公共方法时,现在子对象中找该方法,如果没有找到就会
延着_ _proto_ _属性自动去原型对象中查找。
所以原型对象中定义的公共方法(函数)里的this ,不是看定义在哪儿,而是谁调用这个方法
this就指向谁 调用时 .前是谁this就指向谁 比如student1.interSelf()那么这里的this 就指向 student1。
// 用继承的方式定义对象
// 把构造函数里的方法删掉 加到原型对象中
function Student (sname,sage){
this.sanme=sname;
this.sage=sage;
}
//把孩子需要的公共函数 定义在原型对象上
Student.prototype.interSelf = function(){
console.log(`姓名:${this.sname},年龄:${this.sage}`)
//这里的this 指向谁 要看谁调用的这个公共函数
}
//调用构造函数 创建新的子对象
var student1 = Student("Li Ming",19);
var student2 = Student("Zhang San",20);
console.log( student1, student2)
//孩子在这里调用公共函数interSelf();
student1.interSelf();
student2.interSelf();
自有属性 和 共有属性
自有属性:保存在子对象内部,归该子对象独有的属性。 例如 sname sage属性
共有属性:保存在原型对象中,归多个子对象共有的属性。 例如 interSelf( )属性
获取属性值时:自有属性和共有属性都可以通过 ”子对象.属性名“来访问。
修改属性值时:自有属性 可以通过 “子对象.属性名 = 新值” 来修改 student1.sname="xxx"
共有属性 只能用 “原型对象.属性名 = 新值” 来修改 Student.prototype.className="初一二班"
function Student (sname,sage){
this.sname=sname;
this.sage=sage;
}
Student.prototype.className="初一二班"
var student1 = new Student("Li Ming" ,19);
var student2 = new Student("Zhang San" ,20);
var student3 = new Student("Li Si" ,20);
//修改自有属性
student1.sname="李明"
//修改共有属性
Student.prototype.className="初二二班"
console.log(student1 ,student3 ,student3)
如果需要某种操作 但是原型对象上没有 我们其实可以自己添加 为这个类型的原型对象,添加一个自定义的共有函数 例如:经常需要对数组中元素求和
//Array是function Array(){...}的构造函数 所以创建数组时可以var arr = new Array() ;
//其实 [ ] 是new Array()的简写;
//所以 Array 也有原型对象 在原型对象中的方法数组可以直接调用 例如 arr.push() arr.sort()都是再原型对象中的
//向Array的原型对象上添加一个 sum()数组
Array.prototype.sum = function(){
var total = 0;
for ( var i = 0 ;i < this.length; i++){
total+=this[i]
}
return total;
}
var arr1 = [1,2,3]
var arr2 = [5,6,7,8]
console.log(arr1.sum(), arr2.sum())
原型链 prototype chain
什么是原型链?
由多级父对象逐级继承,形成的链式结构,原型链保存着一个对象所有的属性和方法,控制着属性和方法的使用顺序先自有属性 再延原型链向父级查找 先找到那个就用那个
逐级继承 每个对象都有自己的原型链