面向对象、原型及原型链
一、面向对象
1.1 对象是什么?
- 对象是对于单个物体的简单抽象
- 对象是一个容器,封装了属性和方法
属性:对象的状态
方法:对象的行为
// 简单对象
const school = {
teacher: '张三',
student: '李四',
startCourse: function(name) {
return `开始${name}课`;
}
}
// 函数对象
function school() {
this.teacher = '张三';
this.student = '李四';
this.startCourse = function(name) {
return `开始${name}课`;
}
}
1.2 为什么要面向对象
面向对象 —> 逻辑迁移更加灵活、代码复用性高、高度的模块化
二、构造函数 -> 生成对象
- 需要一个模板(表征了一类物体的共同特征,从而生成对象)
- 类即是对象模板
- js本质并不是基于类,而是基于构造函数和原型链
//construtor(构造函数) + prototype(原型链)
function School(){
//函数体内使用的this,指向所要生成的实例
this.teacher = '张三';
this.student = '李四';
this.startClass = function(name){
return `开始${name}课`;
}
}
//生成对象用new来进行实例化,可以做初始化传参
const school = new School;
根据上述代码可知,school本质就是构造函数
三.关于构造函数的追问
3.1 构造函数
3.1.1 如果构造函数不初始化,可以使用吗?
- 答:不可以使用
3.1.2 如果项目中需要使用,通常(不被外界感知)如何解决?
function Class(){
const _isClass = this instanceof Class; //instance of:查看变量是不是对象的实例(所有对象都是Object的实例)
if(!_isClass){
return new Class();
}
this.teacher = '张三';
this.student = '李四';
this.startClass = function(name){
return `开始${name}课`;
}
}
const course = new Class();
- 启发:如果编写底层的api代码时,尽量做到不需要让外部感知内部类型
3.2 new是什么 / new的原理 /new时候做了什么
function Class(){};
const class = new Class();
1.创建了一个空对象
2.将生成的空对象的原型对象指向了构造函数的prototype属性
3.将当前的实例对象赋给了内部this
4.执行构造函数的初始化代码
3.2.1 追问 实例属性之间的影响
- 答:实例属性之间是独立关系,原因如下:
function Class(teacher, student){
this.teacher = teacher;
this.student = student;
}
const class1 = new Class('张三','李四');
const class2 = new Class('张三','王五');
class2.leader = '佩奇';//不影响class1实例
3.3 constructor是什么?
1.每个对象在创建时,会自动拥有一个构造函数属性constructor
2.constructor继承自原型对象,指向了构造函数的引用
function Class(teacher, student){
this.teacher = teacher;
this.student = student;
}
const class = new Class('张三','李四');
3.3.1 使用构造函数没有问题吗?会有什么性能上的问题?
- 构造函数中的方法,会存在于每一个生成的实例里,重复的挂载其实是会导致资源浪费
function Class(name){
this.teacher = '张三';
this.student = '李四';
this.startClass = function(){
return `开始${name}课`;
}
}
const class1 = new Class('vue');
const class2 = new Class('react');
四、原型对象
function Class(){};
const class1 = new Class();
const class2 = new Class();
1.构造函数:用来初始化创建对象的函数 -> Class
自动给构造函数赋予一个属性prototype,该属性等于实例对象的原型对象
2.实例对象:class1、class2是实例对象,根据原型对象创建出来的实例
1)每个对象都有一个proto
2)每个实例对象都有一个constructor
3)constructor由继承而来,并指向当前的构造函数
3.原型对象:Class.prototype
function Class() {};
Class.prototype.teacher = '张三';
const course1 = new Class();
const course2 = new Class();
// 对上篇原型对象做优化
function Class() {
this.teacher = '张三';
this.student = '李四';
}
//方法挂载于prototype上
Class.prototype.startClass = function(name){
return `开始${name}课`;
}
const class1 = new Class('vue');
const class2 = new Class('react');
五、继承
- 在原型对象的所有属性和方法,都能被实例所共享
- 本质:重写原型对象方式,将父对象的属性方法,作为子对象原型对象的属性和方法
//Game类
function Game(){
this.name = 'lol';
}
Game.prototype.getName = function(){
return this.name;
}
function LOL(){}
LOL.prototype = new Game();
LOL.prototype.constructor = LOL;
const game = new LOL();
5.1 原型链继承有什么缺点
function Game(){
this.name = 'lol';
this.skin = ['s'];
}
Game.prototype.getName = function(){
return this.name;
}
function LOL(){}
LOL().prototype = new Game();
LOL().prototype.constructor = LOL;
const game1 = new LOL();
const game2 = new LOL();
game1.skin.push('s');//加入皮肤 game1 和 game2会同时拥有
- 1.父类属性一旦赋值给子类的原型属性,此时属性属于子类的共享属性了
- 2.实例化子类时,无法向父类做传参
5.1.1 以上问题的解决方法
//经典继承:在子类构造函数内部调用父类构造函数
function Game(arg){
this.name = 'lol';
this.skin = ['s'];
}
Game.prototype.getName = function(){
return this.name;
}
function LOL(arg){
Game.call(this,arg);//父级的东西可通过call给子级
}
//解决了共享属性的问题和子向父传参的问题
const game3 = new LOL('arg');
5.2 追问
- 原型链上的共享方法无法被读取继承,如何解决?
//解决方案:组合继承
function Game(arg) {
this.name = 'lol';
this.skin = ['s'];
}
Game.prototype.getName = function() {
return this.name;
}
// LOL类
function LOL(arg) {
Game.call(this, arg);
}
LOL.prototype = new Game();
LOL.prototype.constructor = LOL;
const game3 = new LOL();
5.3 再次追问
- 组合继承没有缺点吗?
答:有,问题在于:无论何种场景,都会调用两次父类的构造函数(1.初始化子类原型时;2.子类调用函数内部call父类时)
//解决方案:寄生组合继承
function Game(arg) {
this.name = 'lol';
this.skin = ['s'];
}
Game.prototype.getName = function() {
return this.name;
}
// LOL类
function LOL(arg) {
Game.call(this, arg);
}
LOL.prototype = Object.create(Game.prototype);
LOL.prototype.constructor = LOL;
5.4 终级追问
- 看起来解决了继承,但是如何实现多重继承?
function Game(arg) {
this.name = 'lol';
this.skin = ['s'];
}
Game.prototype.getName = function() {
return this.name;
}
function Store() {
this.shop = 'steam';
}
Store.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(LOL.prototype,Store.prototype);
LOL.prototype.constructor = LOL;
//LOL两类继承
const game3 = new LOL();