function Person(name) {
this.name = name
}
Person.prototype.say = function() {
console.log(`你好,我是 ${this.name}`)
}
// 尝试定义一个子类,来继承 Person
function Student(name, id){
Person.call(this, name) // 调用父类构造函数
this.id = id
}
Student.prototype.fun = function(){
console.log(`你好,我是 ${this.name},我的学号是${this.id}`);
}
let s = new Student('玛丽','1534543')
s.fun()//你好,我是 玛丽,我的学号是1534543
s.toString()
// s.say() s的proto中找,proto指向器构造函数的prototype,原型是一个对象也有proto,proto又可以指向构造函数的prototype
console.log(Student.prototype.__proto__===Object.prototype);//true
s.fun()//你好,我是 玛丽,我的学号是1534543
说明Person的属性成功被继承了
但是这个时候的say()方法未定义,s.say() s的proto中找,proto指向器构造函数的prototype,这
原型是一个对象也有proto,proto又可以指向构造函数的prototype,这个时候应该是指向Object,身上并没say方法
加上这句话
Student.prototype=Person.prototype;
找不到say时,就 proto->Student.prototype->Object.prototype现在把指向改成了
但是!如果这个时候 Student 想要给自己的 prototype 加一个新方法,怎么办?我们知道因为只是复制了地址,如果修改了 Student.prototype,Person.prototype 也将被修改,这显然是我们不愿意看到的。
Person.prototype,找到了了Person上的say方法,但是Student.prototype.fun上的fun找不到了
再进行更改:
Student.prototype.__proto__ = Person.prototype
改成这样,就会先去prototype寻找,没找到的话再顺着proto往上找到Person
是不是看起来有点眼熟?
Array.prototype.__proto__ === Object.prototype // true
这就是原型链!这就是我们所说的继承!是不是有点感觉了?
当然,刚才也说了,我们最好用下面这种写法:
Student.prototype = Object.create(Person.prototype)
复制代码回顾一下到现在我们做了什么?
首先我们通过 call 父级构造函数,来实现属性的继承,有了 姓名
然后我们通过建立原型链,来实现方法的继承,我们的 小明 可以 自我介绍 了
看似已经结束,但是实际上还有一个隐藏的 Bug,我们接下来来解决这个 Bug。
解决 constructor 的问题
细心的你会发现(我们在最开始也说过了),我们的对象实例和构造函数中是有一个 constructor 属性的,比如:
const arr = [1, 2]
arr.__proto__.constructor === Array // true
Array.prototype.constructor === Array // true
但是,Student.prototype 中的 constructor 被刚才的那一番操作给搞没了,我们需要把它弄回来:
Student.prototype.constructor = Student
这样就完成了一波类的继承。
总结
function Person(name) {
this.name = name
}
Person.prototype.say = function() {
console.log(`你好,我是 ${this.name}`)
}
// 尝试定义一个子类,来继承 Person
function Student(name, id){
Person.call(this, name) // 调用父类构造函数
this.id = id
}
Student.prototype.fun = function(){
console.log(`你好,我是 ${this.name},我的学号是${this.id}`);
}
// Student.prototype=Person.prototype;
Student.prototype.__proto__ = Person.prototype;
Student.prototype.constructor = Student
let s = new Student('玛丽','1534543')
let p = new Person('张三')
s.say()
s.fun()
用 class 继承
既然他本身就是语法糖,我个人认为没必要搞那么细,其实本质跟上面的使用原型链的继承是一样的,搞清楚是怎么写的就好啦:
// 定义父类
class Person {
constructor(name) { // 定义属性
this.name = name
}
say() { // 定义方法
console.log(`你好,我是 ${this.name}`)
}
}
// 定义子类
class Student extends Person {
constructor(name, id) {
super(name) // 这里的 姓名 两个字要与父类中的一样,继承属性和方法
this.id = id // 定义新属性
}
fun() { // 定义新方法
console.log(`我的学号是 ${this.id}`)
}
}
let s = new Student('小红', 345678)
s.say() // 你好,我是 小红
s.fun() // 我的学号是 345678
class类内部定义的所有方法都是不可枚举的。这点和ES5行为不一致。
类和模块的内部默认使用严格模式,所以不需要使用use strict指定运行模式。
类必须使用 new 来调用,否则会报错。这是他跟普通构造函数的一个主要区别,后者不用 new 也可以执行。
类内部不存在变量提升,这一点与ES5完全不同。
class继承可以实现原生构造函数的继承,而ES5不可以。