面向对象
1、对象是什么?
- 对象是对于单个物体的简单抽象
- 对象是一个容器,封装了属性 & 方法
- 属性:对象的状态
- 方法:对象的行为
2、为什么要面向对象?
- 逻辑迁徙更加灵活、代码复用性高、高度的模块化
// 简单对象
const Course = {
teacher: '希拉',
leader: '拉西',
startCourse: function(name) {
return `开始${name}课`;
}
}
// 函数类
function Course() {
this.teacher: '希拉';
this.leader: '拉西';
this.startCourse = function(name) {
return `开始${name}课`;
}
}
构造函数 - 生成对象
原理
- 需要一个模板 - 表征了一类物体的共同特征,从而生成对象
- 类即对象模板
- js其实本质上并不是基于类,而是基于构造函数 + 原型链
- constructor + prototype
function Course(teacher) {
this.teacher = teacher;
this.leader = '小可';
this.startCourse = function(name) {
return `开始${name}课`;
}
}
const course = new Course('云隐');
- Course 本质就是构造函数
- 函数体内使用的this,指向所要生成的实例
- 生成对象用new来进行实例化
- 可以做初始化传参
new 操作符
思考:new是什么 / new的原理 / new时候做了些什么?
function Course() {};
const course = new Course();
- 创建了一个空对象,作为返回的对象实例
- 将生成空对象的原型对象指向了构造函数的prototype属性
- 将构造函数内部this指向当前实例对象
- 执行构造函数的初始化代码
模拟 new 操作符
function _new(fn, ...args) {
const obj = Object.create(fn.prototype);
const res = fn.call(obj, ...args);
return typeof res === 'object' ? res : obj;
}
constructor
原理
function Course(teacher, leader) {
this.teacher = teacher;
this.leader = leader;
}
const course = new Course('云隐', '小可');
- 每个对象在创建时,会自动拥有一个构造函数属性constructor
- constructor继承自原型对象,指向了构造函数的引用
只使用构造函数 会有什么问题?
function Course(name) {
this.teacher = '云隐';
this.leader = '小可';
this.startCourse = function(name) {
return `开始${name}课`;
}
}
const course1 = new Course('es6');
const course2 = new Course('OOP');
// 构造函数中的方法,会存在于每一个生成的实例里,重复的挂载其实是会导致资源浪费
// 解决办法:使用原型链
原型对象
function Course() {};
const course1 = new Course();
const course2 = new Course();
- 构造函数: 用来初始化创建对象的函数 - Course
**自动给构造函数赋予一个属性prototype,该属性等于实例对象的原型对象 - 实例对象:course1是实例对象,根据原型对象创建出来的实例
**每个对象中都有一个__proto__
**每个实例对象都有一个constructor
**constructor由继承而来,并指向当前的构造函数 - 原型对象:Course.prototype
function Course() {};
Course.prototype.teacher = '云隐';
const course1 = new Course();
const course2 = new Course();
// 对上篇原型对象做优化
function Course() {
this.teacher = '云隐';
this.leader = '小可';
}
// 方法挂载于prototype上
Course.prototype.startCourse = function(name) {
return `开始${name}课`;
}
const course1 = new Course('es6');
const course2 = new Course('OOP');
继承
原型链继承
function Game() {
this.name = 'lol';
}
Game.prototype.getName = function() {
return this.name;
}
function LOL() {}
LOL.prototype = new Game();
LOL.prototype.constructor = LOL;
- 优点
**在原型对象上的属性和方法,能被所有实例共享 - 缺点
function Game() {
this.name = 'lol';
this.skin = ['s'];
}
Game.prototype.getName = function() {
return this.name;
}
// LOL类
function LOL() {}
LOL.prototype = new Game();
LOL.prototype.constructor = LOL;
const game1 = new LOL();
const game2 = new LOL();
game1.skin.push('ss');
**原型对象的属性因为共享,一个实例改变会影响所有实例
**实例化子类实例时,无法向父类传参
构造函数继承
function Game(arg) {
this.name = arg.name;
this.skin = arg.skin;
}
Game.prototype.getName = function() {
return this.name;
}
function LOL(arg) {
Game.call(this, arg);
}
let lol = new LOL({name: 'lol', skin: ['s']});
// 解决了无法向父类传参和子类原型属性共享的问题
- 优点
**解决了无法向父类传参和子类原型属性共享的问题 - 缺点
**无法使用父类原型对象上的方法
组合式继承
function Game(arg) {
this.name = arg.name;
this.skin = arg.skin;
}
Game.prototype.getName = function() {
return this.name;
}
function LOL(arg) {
Game.call(this, arg);
}
LOL.prototype = new Game();
LOL.prototype.constructor = LOL;
- 优点:解决了构造函数继承和原型链继承的问题
- 缺点:父类构造函数调用了2次
寄生组合式继承
function Game(arg) {
this.name = arg.name;
this.skin = arg.skin;
}
Game.prototype.getName = function() {
return this.name;
}
function LOL(arg) {
Game.call(this, arg);
}
LOL.prototype = Object.create(Game.prototype);
LOL.prototype.constructor = LOL;