面向对象
为什么要面向对象?
- 优势:简化对行为岔路的预备
- 特点:逻辑迁移更加灵活、代码复用性高,高度模块化的体现
对象是什么?
- 对单个物体的简单抽象
- 对象是一个容器,封装了属性和方法,属性代表对象的状态,方法代表对象的行为
- 模块、接口=>对象
简单对象
- 对外开放,属性和方法可以被直接修改
js
const Course = {
teacher :'张三',
leader:'cc',
startCourse : name =>{
return `开始${name}课`
}
}
// A
Course.teacher = '李四';
Course.startCourse('react')
// B
Course.startCourse('vue');
// 以上会修改对象里面的数据,为了防止修改,所以使用函数对象
函数对象
构造函数的实例化
- 需要一个模板,表征了一个类物品的共同特征,从而生成对象。
- 构造函数即对象模板,构造函数可以理解为类。
- JS本质上并不是直接基于类,基于构造函数+原型链=>constructor+prototype。
- 需要使用new来实例化对象。
- 构造函数体体内使用的this指向所要生成的实例。
- 可以初始化传参。
js
function Course(name){
this.name = name;
this.teacher = '张三';
this.leader = 'xh';
this.startCourse = function () {
return `开始${ this.name}课`
}
}
const course = new Course('vue');
console.log(course)
提问1:如果构造函数不实例化,可以作为对象生成器使用吗?
答案:不可以
js
function Course(name) {
this.name = name
this.teacher = '小明'
this.teachestartCourse = function () {
return `开始${this.name}课`
}
}
console.log(Course.teacher) // undefined
提问2:如果项目需要使用,又不想被外界感知,通常如何解决?
答案:单例模式=>全局维护统一实例=>解决对象实例化生成问题
js
function Course() {
const isClass = this instanceof Course
if (!isClass) {
return new Course()
}
this.name = '语文'
this.teacher = '小明'
this.teachestartCourse = function () {
return `开始${this.name}课`
}
}
console.log(Course().name) // 语文
提问3:new是什么?new的原理?new的时候做了什么?
- 结构上:创建一个空的对象,作为返回的对象实例。
- 属性上:将生成空对象的原型对象指向了构造函数的prototype属性。
- 关系上:将当前实例对象赋给了内部的this。
- 生命周期上:执行了构造函数的初始化代码。
手写代码模拟 new
js
function usernew(obj,...args){
const newObj = Object.create(obj.prototype)
const result = obj.apply(newObj,args)
return typeof result === 'object'? result:newObj
}
function person(name, age) {
this.name = name;
this.age = age
}
const p = usernew(person '小明',10)
console.log(p)
提问4:constructor是什么?
构造器
- 每个对象实例化时,都会自动拥有一个
constructor
属性。 constructor
属性继承原型对象,并指向当前的构造函数。constructor
代表了实例化对象是被谁生成的。
提问5:使用构造函数继承属性就没有问题吗?会有什么性能问题?
- 构造函数中的方法实例化后会存在于每个生成的实力内
- 而方法往往需要统一模块化,重复挂载只会浪费资源
- 如何进行优化:把方法挂在构造函数的
prototype
属性上,让实例对象自动通过自身的__proto__
属性去引用方法
提问6:前面提到的原型对象又是什么? - prototype
js
function Course() {}
const course1 = new Course();
const course2 = new Course();
// 1. Course - 用来初始化创建对象的函数 | 类
course1.__proto__ === Course.prorotype
// 2. course1 - 根据原型创建出来的实例
course1.constructor === Course
prototype 只有构造函数有prototype属性。
js
function Course() {}
const course1 = new Course()
- 构造函数:用来初始化创建对象的函数|类 - Course
- 自动给构造函数赋予了一个属性 prototype(原型对象),该属性等于实例对象的
__proto__
,course1.__proto__
===Course.prototype
// true - 每个对象实例化时,都会自动拥有一个 constructor 属性,(course1是根据原型创建出来的实例)course1.constructor => Course
- constructor属性继承自原型对象,并指向当前构造函数
course1.constructor
=>course1.__proto__.constructor
=>Course.prototype.constructor
=>Course
提问7:原型对象有自己的原型吗?有
js
function Course() {}
const course1 = new Course()
course1.__proto__.__proto__ === Object.prototype
Course.prototype.__proto__ === Object.prototype
course1.__proto__.__proto__.__proto__ === null
原型链
原型&原型链
原型
prototype:
Js通过构造函数来创建一个对象,每个构造函数内部都会有一个原型prototypr
属性,它指向另外一个对象,这个对象包含了可以由该构造函数的所有实例共享的属性和方法。proto:
当使用构造函数创建一个实例对象后,可以通过__proto__
访问到prototype
属性。constructor:
实例对象通过这个属性可以访问到构造函数
原型链
- 每个实例对象都有一个
__proto__
属性指向它的构造函数的原型对象,而这个原型对象也会有自己的原型对象,一层一层向上,直到顶级原型对象null
,这样就形成了一个原型链。 - 当访问对象的一个属性或方法时,当对象身上不存在该属性方法时,就会沿着原型链向上查找,直到查找到该属性方法位置。
- 原型链的顶层原型是
Object.prototype
,如果这里没有就只指向null
继承
重写原型对象继承
- 在原型对象上的属性和方法,都能被实例共享
- 本质:重写原型对象,将父类实例对象的属性和方法,作为子对象原型的属性和方法
缺点:
- 父类属性一旦赋值给子类的原型属性,此时属性属于子类的共享属性了。( 子类的 prototype 来自父类的一个实例对象,其中一个子类实例对象改了继承而来属性或方法,其他子类实例对象继承而来的属性或方法也就被修改了 )
- 实例化子类时,无法向父类传参。
js
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');
//本质:重写原型对象,将父类实例对象的属性和方法,作为子对象原型的属性和方法,同时重写构造函数
构造函数继承/拷贝继承
- 在子类的构造函数内部调用父类的构造函数
- 缺点:无法通过原型链继承原型对象上共享的方法
js
function Game(arg) {
this.name = 'lol';
this.skin = ['s'];
}
Game.prototype.getName = function() {
return this.name;
}
function LOL(arg) {
Game.call(this, arg);
}
const game3 = new LOL('arg');
// 解决了共享属性问题 + 子向父传参的问题
组合继承
- 组合继承:构造函数继承 + 重写原型对象继承
- 缺点:父类被调用了两次
js
function Game(arg) {
this.name = 'lol';
this.skin = ['s'];
}
Game.prototype.getName = function() {
return this.name;
}
function LOL(arg) {
Game.call(this, arg);
}
LOL.prototype = new Game();
LOL.prototype.constructor = LOL;
const game4 = new LOL('arg');
寄生组合继承
以Course.prototype
为原型,通过Object.create()
创建一个方新的对象赋值给LOL.prototype
js
function Game(arg) {
this.name = 'lol';
this.skin = ['s'];
}
Game.prototype.getName = function() {
return this.name;
}
function LOL(arg) {
Game.call(this, arg);
}
LOL.prototype = Object.create(Game.prototype);
LOL.prototype.constructor = LOL;
const game5 = new LOL('arg');
多重继承
通过Object.assign()
合并多个父类的原型对象给当前子类的原型对象
js
function Game(arg) {
this.name = 'lol';
this.skin = ['s'];
}
Game.prototype.getName = function() {
return this.name;
}
function Store() {
this.shop = 'steam';
}
Game.prototype.getPlatform = function() {
return this.shop;
}
function LOL(arg) {
Game.call(this, arg);
Store.call(this, arg);
}
LOL.prototype = Object.create(Game.prototype);
Object.assign(
Store.prototype,
LOL.prototype
);
LOL.prototype.constructor = LOL;