课程目标
- 面向对象
- 原型和原型链
知识点
- 对象是什么
- 构造函数
- 原型对象
- 实现继承以及不同继承方式
对象是什么?为什么要面向对象
特点:面向对象---逻辑迁移更加灵活、代码复用性高、高度的模块化
对象的理解
- 对象是对于物体的简单抽象
- 对象是一个容器,封装了属性 & 方法
- 属性:对象的状态
- 方法:对象的行为
//简单对象
const pet = {
name: '小菜',
age: 18
getName: function(name){
return name;
}
}
//函数对象
function pet(name) {
this.name = '小菜'
this.age = '18'
this.getName = function(name) {
return name;
}
}
构造函数--生成对象
- 需要一个模板--表征了一类物体的共同特征,从而生成对象
- 类即对象模板
- js其实本质上并不是基于类,而是基于构造函数 + 原型链
constructor+prototype
function Course(name){
this.name = name;
this.age = 18;
this.getName = function(name) {
return name;
}
}
const course = new Course('小菜');
- Course 本质就是构造函数
- 函数体内使用的this,指向所要生成的实例
- 生成对象用new来进行实例化
- 可以做初始化传参
提问:如果构造函数不初始化,可以使用吗?-- 答:无法使用
提问:如果项目中需要使用,通常(不被外界感知)如何解决?
function Course(){
//
const _isClass = this instanceof Course;
if(!_isClass) {
return new Course();
}
this.name = '小菜';
this.age = 18;
this.getName = function(name) {
return name;
}
}
const course = new Course();
- 启发:如果编写底层的api代码时,尽量做到不需要让外部感知内部类型
new是什么?| new 的原理 | new时候做了些什么?
- 创建了一个空对象,作为返回的对象实例
- 将生成空对象的原型对象指向了构造函数的prototype属性
- 将当前实例对象赋给了内部this
- 执行构造函数的初始化代码
提问:实例属性影响?-- 每个实例之间的属性是独立的,因为内部this指向
constructor是什么?
- 每个对象在创建时,会自动拥有一个构造函数属性constructor
- constructor继承自原型时,指向了构造函数的引用
提问:使用构造函数。没有问题吗?| 会有什么性能上的问题?
构造函数中的方法,会存在于每一个生成的实例里,重复的挂载其实是会导致资源浪费
原型对象
function Course(){}
const course1 = new Course();
const course2 = new Course();
- 构造函数:用来初始化创建对象的函数 -- Course
- 自动给构造函数赋予了一个属性prototype,该属性等于实例对象的原型对象
- 实例对象:conrse1是实例对象,根据原型对象创建出来的实例
- 每个对象中都有一个_proto_
- 每个实例对象都有一个constructor
- constructor有继承而来,并指向当前的构造函数
- 原型对象:Course.prototype
function Course(){}
Course.prototype.name = '小菜';
const course1 = new Course();
const course2 = new Course();
//刚提到的使用构造函数会重复的挂载现在就可以解决了
//方法挂载于prototype上,属性也可以挂载在prototype上
function Course(){
this.name = name;
this.age = 18;
}
Course.prototype.getName = function(name){
return name;
}
const course1 = new Course('小菜');
const course2 = new Course('小心');
继承
在原型对象的所有属性和方法,都能被实例所共享
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();
提问:原型链继承有什么缺点呢?
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('ss');
看下game1的输出结果:
是没问题的,那么看下game2
那么问题就来了,修改game1的时候父级的Game被修改,game2拿的就是Game被修改后的值。
- 父类属性一旦赋值给子类的原型对象,此时属性属于子类的共享属性
- 实例化子类时,无法向父类做传参
解决方法:构造函数继承
(经典继承):在子类构造函数内部调用父类构造函数
function Game(){
this.name = 'lol';
this.skin = ['s']
}
Game.prototype.getName = function() {
return this.name
}
function LOL(arg) {
//子类执行父类,构造函数的this指向了实例,就解决了父类共享属性的问题,以及子向父传参
//问题(arg)
console.info(arg);
Game.call(this, arg);
}
//或者用class实现
class LOL extends Game{
constructor (){
super();
}
}
const game3 = new LOL('arg');
追问:原型链上的共享方法无法被读取继承,如何解决?
解决方案:组合继承
function Game(){
this.name = 'lol';
this.skin = ['s']
}
Game.prototype.getName = function() {
return this.name
}
function LOL(arg) {
//子类执行父类,构造函数的this指向了实例,就解决了父类共享属性的问题,以及子向父传参的
//问题(arg)
Game.call(this, arg);//第二次调用父类
}
LOL.prototype = new Game();//第一次调用父类
LOL.prototype.constructor = LOL;
const game = new LOL();
追问:组合继承还有什么缺点?问题在于:无论何种场景都会调用两次父类构造函数
1.初始化子类原型时 2.子类调用函数内部call父类的时候
解决方案:寄生组合继承
function Game(){
this.name = 'lol';
this.skin = ['s']
}
Game.prototype.getName = function() {
return this.name
}
function LOL(arg) {
//子类执行父类,构造函数的this指向了实例,就解决了父类共享属性的问题,以及子向父传参的
//问题(arg)
Game.call(this, age);
}
LOL.prototype = Object.create(Game.prototype);
LOL.prototype.constructor = LOL;
提高:如何实现多重继承?
function Game(){
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) {
//子类执行父类,构造函数的this指向了实例,就解决了父类共享属性的问题,以及子向父传参的
//问题(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;
const game = new LOL();
输出结果:game 包含了Game、Store两个父类里属性,如下图: