- 面向对象三大特点: 封装, 集成, 多态
1. 什么是面向对象
程序中都是用对象结构来描述现实中一个具体的事物
1.1. 什么是对象:
程序中封装现实中一个事物属性和功能的存储空间
1.2. 为什么
- 现实中, 任何数据都有明确的归属, 都不是孤立的
1.3. 何时:
- 只要用程序描述现实中一个事物, 都要将事物的属性和功能封装在一个对象中
如何
- 三大特点: 封装, 继承, 多态
2. 封装
2.1. 什么是
将现实中一个事物的属性和功能集中定义在一个对象结构中:
- 事物的属性会成为对象的属性
- 事物的功能会成为对象的方法
2.2. 为什么
- 让每个数据都有其专门的归属, 便于维护和操作
2.3. 何时
- 只要使用面向对象, 都要先将数据封装到对象中, 再使用
2.4. 如何: 3 种
-
对象直接量
var obj = { // 创建一个新对象 属性名: 值, 属性名: 值, ...... // 方法名: function (参数列表) { ...... } 方法名(参数列表) { ...... // this.属性名 }, 方法名(...) {...} }-
何时使用直接量: 如果创建对象时, 就已经知道对象的成员
-
问题: 对象自己的方法, 要访问自己的属性
- 错误:
对象名.属性名, 一点对象名修改, 方法内的对象名要同时修改, 不便于维护 - 正确: 在方法内使用关键字
this, 自动指向当前对象本身 ->this.属性名- 优点: 即使对象名修改,
this也可自动获得对象本身, 和对象名无关 this可翻译为: 当前对象的 / 自己的
- 优点: 即使对象名修改,
- 错误:
-
总结: 今后只要对象自己的方法中, 要使用对象自己的属性, 就必须用
this.属性名
-
-
用
new: 2 步- 创建空对象:
var obj = new Object() - 向空对象中添加新属性及方法:
-
obj.属性名 = 值 -
obj.方法名 = function () { this.属性名 }
-
-
何时使用: 如果创建对象时暂时不知道对象的成员
- 对象创建后, 随时可以添加新成员
-
JS中对象的本质: 其实就是一个关联数组- 对象其实是关联数组的简化版用法:
关联数组 对象
访问元素:
数组['属性名']对象.属性名/对象['属性名'] - 创建: 2 步 先创建
[]可用直接量{}一次性创建 再添加成员 再添加成员
- 对象其实是关联数组的简化版用法:
关联数组 对象
访问元素:
-
问题: 一次只能创建一个对象, 反复创建对个相同结构的对象, 代码冗余太多, 不便于维护
- 解决: 用构造函数反复创建多个相同结构的对象
- 创建空对象:
-
用构造函数反复创建多个相同结构的对象
-
构造函数: 专门描述一类对象统一结构的函数, 还用于将一个新的空对象装修成想要的结构并存入数据
-
何时: 反复创建多个相同结构的对象时, 都要先用构造函数描述统一的结构
-
如何: 2 步
-
定义构造函数:
function 类型名(属性参数列表) { this.属性名 = 属性参数; ...... this.方法名 = function () { this.属性名 } } -
用
new调用构造函数创建新对象:js var obj = new 类型名(属性参数列表)
-
-
new4 件事:- 创建一个新的空对象
- 自动设置新对象继承构造函数的原型对象
- 调用构造函数, 向新对象中添加新属性
- 返回新对象的地址保存在对象变量中
-
2.5. 如何访问对象的成员
- 成员 = 属性 / 成员 = 方法
- 访问属性:
对象.属性名, 用法和普通变量完全一样- 属性其实是保存在对象中的变量
- 也可以通过
对象['属性名']方式访问
- 调用方法:
对象.方法名(), 用法和普通的函数完全一样- 方法其实是保存在对象中的函数
- 问题: 方法定义在构造函数内, 每创建一个新对象, 都会重复创建相同的函数副本 -> 浪费内存
- 解决: 继承
- 总结: 构造函数 -> 优点: 代码重用 缺点: 无法节约内存
3. 继承
3.1. 什么是
父对象的成员, 子对象无需重复创建就可直接使用
3.2. 为什么
- 不但可以代码重用, 且还可以节约内存
3.3. 何时
- 只要多个子对象, 拥有形同的成员, 都要将相同的成员, 仅保存在父对象中一份即可, 所有的子对象共用
3.4. 如何
- 原型对象(
prototype): 专门集中保存同一类型的多个子对象, 共有成员的父对象
3.4.1. 如何获得原型对象
- 买一赠一: 创建构造函数时, 已经自动创建了该类型的原型对象
- 构造函数的
prototype属性引用着原型对象, 原型对象的constructor引用着构造函数对象
- 构造函数的
- 自动集成: 创建子对象时, 会自动设置新对象的
__proto__属性继承构造函数的原型对象
3.4.2. 如何向原型对象中添加共有成员
-
构造函数.prototype.属性名 = 值/构造函数.prototype.方法名 = function (......) { ...... } -
强调: 原型对象中的方法, 要访问对象自己的属性, 也必须使用
this. -
总结
- 每个子对象, 值不同的属性, 都要定义在构造函数中
- 所有子对象共用的方法和属性值, 都要集中定义在原型对象中
3.4.3. 共有属性和自有属性
- 自有属性: 直接保存在当前对象本地的属性
- 共有属性: 保存在对象的原型对象中, 所有子对象共用的属性
- 相同: 取值时
- 不同: 修改时:
- 自有属性可以直接通过子对象修改:
子对象.自有属性 = 值
- 共有属性只能通过构造函数的原型对象修改:
构造函数.prototype.共有属性 = 值
- 自有属性可以直接通过子对象修改:
3.5. 原型链
由各级父对象逐级继承形成的链式结构
- 任何对象都有
__proto__属性指向其父对象 - 保存了所有对象的成员
- 控制着对象成员的访问顺序:
- 优先访问自有属性
- 自己没有才去通过原型链向父级找
- 只要找到了就不往上找
3.6. 内置对象的原型对象
-
其实每种内置对象都有一对构造函数和原型对象
-
其中, 构造函数负责创建新对象
-
Ex:
var arr = new Array()/var date = new Date()/var reg = new RegExp() -
特例:
Math和window不是构造函数, 不能new
-
-
原型对象负责集中存储该类型的可用的所有
API-
Ex:
arr.sort()/arr.push()/arr.slice()- 因为:
Array.prototype: { sort(......) {......}, push(......) {......}, slice(......) {......} }
-
-
3.6.1. 解决浏览器兼容性问题
- 旧浏览器无法使用新的
API: 2 步- 判断: 如果当前浏览器的指定类型的原型中不包含想要的
APIif (!'indexOf' in Array.prototype):in->用于检测左边的名称是否在右边的对象 或 对象的原型链中if (typeof Array.prototype.indexOf !== 'function')
- 如果没有, 就向原型中添加一个新函数
Array.prototype.indexOf = function (......) { // this 代表当前数组对象 }
- 判断: 如果当前浏览器的指定类型的原型中不包含想要的
3.7. 自定义继承
3.7.1. 何时
- 只要希望其他对象的成员
3.7.2 如何: 3 种
-
直接修改一个对象的
__proto__属性指向新对象child.__proto__ = father- 问题:
__proto__是内部属性, 不推荐使用- 解决:
Object.setPrototypeOf(child, father)
- 解决:
-
通过修改构造函数的原型对象来批量修改所有子对象的父对象
-
构造函数.prototype = father -
强调: 时机 -> 在开始创建子对象之前就要修改
-
-
两种类型间的继承: 更像
java的继承- 问题: 如果两个对象间拥有部分相同的属性结构和方法定义
- 解决: 抽象出一个父类型
- 父类型的构造函数包含子类型相同的部分属性
- 父类型的原型对象中包含子类型相同的部分方法
- 在子类型构造函数中借用父类型构造
- 错误: 直接调用父类型构造
- 任何一个函数 不用. / 不用
new调用, 其中的this指向window
- 任何一个函数 不用. / 不用
- 解决: 用
call强行调用, 并替换函数中的this- 何时: 如果函数中
this不是想要的 - 如何:
fun.call(obj, 参数值)- 调用
fun, 替换fun中的this指向obj
- 调用
- 何时: 如果函数中
- 错误: 直接调用父类型构造
- 让子类型原型集成父类型原型
Object.setPrototypeOf(子类型.prototype, 父类型.prototype)
- 问题: 从父对象继承来的成员不一定是自己想要的
- 解决: 多态
4. 多态
4.1. 什么是
同一个函数在不同情况下, 表现出不同的状态
4.2 重写( override )
- 如果子对象对的父对象的成员不好用, 就可在子对象本地定义同名成员, 覆盖父元素的成员
4.3. 为什么
- 从父对象继承来的成员不一定都是想要的
4.4. 何时
- 如果子对象觉得父对象的成员不好用
总结: 面向对象三大特点
- 封装: 将事物的属性和功能集中定义在一个对象中
- 为什么: 便于维护
- 继承: 父对象的成员, 子对象无需要重复创建, 就可直接使用
- 为什么: 代码重用, 节约内存
- 多态: 如果父对象的成员不好用, 就可在子对象中重写同名成员
- 为什么: 为了体现父子对象间的差异