Javascript 面向对象笔记
date
Mar 25, 2018
slug
javascript-oop
status
Published
tags
Javascript
summary
Javascript 中继承的详解以及几种不同的继承方式
type
Post
说到面向对象,大多数人都想到的是高级语言:c++、java,但是我认为对于一名coder来说不论什么语言,一定要有面向对象这种思想(封装、继承、多态),我们只需要用语言这个工具把思想表达出来即可。本文只讨论继承。
Javascript 创建对象new 关键字做了什么?prototype/proto 是什么?面向对象 — 继承使用 call/apply原型链继承改进后的原型继承class/extends 关键字实现继承参考资料
Javascript 创建对象
function Ball(name){
this.name = name;
}
var basketball = new Ball('basketball');
console.log(basketball.__proto__);
/* 输出
constructor: ƒ ball(name)
arguments: null
caller: null
length: 1
name: "ball"
prototype: {constructor: ƒ}
__proto__: ƒ ()
*/
console.log(ball.prototype)
/* 输出
constructor: ƒ ball(name)
arguments: null
caller: null
length: 1
name: "ball"
prototype: {constructor: ƒ}
__proto__: ƒ ()
*/
console.log(basketball.__proto__ === ball.prototype) // true
new 关键字做了什么?
利用 new & 构造函数 创建新的对象。这个创建新对象的过程分为三步:
- 声明新的变量 basketball
- new 将新变量的
_proto_
属性指向了构造函数(Ball)的 prototype 属性,这时内存为 basketball 分配了内存,其成为了对象。basketball._proto_ = ball.prototype
- 利用 call 函数将新产生的对象 basketball 的 this 指向 ball。即绑定 this。
prototype/proto 是什么?
有的书上别别用显示原型/隐示原型来分别代
prototype
_proto_
还有的用原型对象/对象原型。其实不论哪一种说法,代表的东西都是一样的。这里我们使用第二种说法。俩者区别如下表:- prototype: 指向函数的原型对象(包括拥有的变量与方法,constructor ,proto)只有函数拥有此属性
- proto:指向构造器的原型对象;不论对象或者函数都有此属性
面向对象 — 继承
使用 call/apply
该方式采取的办法是将父对象的构造函数绑定在子对象上。具体如下:
function Ball() {
this.general = "球类运动";
}
function Basketball(name, space) {
Ball.apply(this, arguments);
this.name = name;
this.space = space;
}
let bb = new Basketball('耐克7号球', '室内')
console.log(bb.general) // 球类运动
原型链继承
使子类原型对象指向父类的原型对象以实现继承。具体如下:
function Ball() {
this.general = "球类运动";
this.ballprint = function() {
console.log('ball');
};
}
function Basketball(name, space) {
this.name = name;
this.space = space;
this.print = function() {
console.log('basketball');
};
}
Basketball.prototype = new Ball();
let bb = new Basketball('耐克7号球', '室内');
console.log(bb.general) // 1、球类运动
console.log(bb.ballprint()) // 2、ball
console.log(bb.name) // 3、耐克7号球
console.log(bb.print()) // 4、basketball
console.log(Basketball.prototype == Ball.prototype) // 5、true
console.log(Basketball.prototype.__proto__ == Ball.prototype) // 6、true
如果子类与父类中的属性、方法同名那么结果怎样呢?结果如下:
function Ball() {
this.name = "球类运动";
this.print = function() {
console.log('ball');
};
}
function Basketball(name, space) {
this.name = name;
this.space = space;
this.print = function() {
console.log('basketball');
};
}
Basketball.prototype = new Ball();
let bb = new Basketball('耐克7号球', '室内');
console.log(bb.name) // 1、耐克7号球
console.log(bb.print()) // 2、basketball
console.log(Basketball.prototype == Ball.prototype) // 3、true
console.log(Basketball.prototype.__proto__ == Ball.prototype) // 4、true
此时虽然
bb._proto_ = Basketball.prototype = Ball.prototype
但是同名采取的就近访问的原则,所以执行 Basketball 中的语句。而不会通过 proto 原型链去去上级父类寻找变量与方法。改进后的原型继承
因为上述方法会修改构造函数(因为prototype中包含构造器,当调用语句
Basketball.prototype = new Ball()
时构造器也被改为了 Ball
),所以我们应该手动置回。具体如下:function Ball() {
this.name = "球类运动";
this.print = function() {
console.log('ball');
};
}
function Basketball(name, space) {
this.name = name;
this.space = space;
this.print = function() {
console.log('basketball');
};
}
Basketball.prototype = new Ball();
Basketball.prototype.constructor = Basketball;
let bb = new Basketball('耐克7号球', '室内');
这样即可。当然这样的继承方式是多占用了些内存,
Basketball.prototype = new Ball()
,当然还有不占内存的方式,比如利用空对象作为中介的方式。创建了一个临时的对象,理解起来不难。具体请参考:阮老师这里介绍的空对象方法,没什么问题。但是我觉得没有把临时对象使用完后手动置空的操作,自己加上即可。
class/extends 关键字实现继承
es6中引入了类的概念,用 class 关键字声明的函数作为对象模版。具体如下:
class Ball{
constructor(name) {
this.name = name
}
play() {
console.log('Ball is: ', this.name)
}
}
class Basketball extends Ball {
constructor(name) {
super(name)
this.name = name
}
playb() {
console.log('Basketball is: ', this.name)
}
}
let bb = new Basketball('nikeball')
bb.play(); // Ball is: nikeball
bb.playb(); // Basketball is: nikeball
console.log(typeof Ball) // 1、function
console.log(typeof Basketball) // 2、function
console.log(bb.__proto__ == Basketball.prototype) // 3、true
console.log(Basketball.prototype)
/* 4、
constructor: class Basketball
playb: ƒ playb()
__proto__: Object
*/
由打印出的结果1&2我们可以看出,class 好像是包在 function 上的语法糖;由3慢慢确定了这一点;由4我们更加确定了这一点,而且结合前面说的改进原型继承的方式,还可以尝试分析 class 继承的关键步骤:
Basketball.prototype = new Ball()
Basketball.prototype.constructor = Basketball
// playb() {...} 相当于:
Basketball.prototype.playb = function() {...}
使用 class 时一定要注意在使用 this 或者子类构造函数返回前,一定要在子类中使用 super 关键字调用父类的构造函数。说白了就是在子类中一定要使用 super 。