js中的继承
箭头函数与普通函数中的 this 指向
首先引入一道面试题:
let name = 'Tom'
function Person() {
this.name = 'Jack'
this.say = () => {
console.log('my name is ' + this.name)
}
}
let P = new Person()
let func = P.say
func()
// 'Jack'
let name = 'Tom'
function Person(name) {
this.name = 'Jack'
this.say = function() {
console.log('my name is ' + this.name)
}
}
let P = new Person()
let func = P.say
func()
// 'Tom'
题目解析:
两道题目的区别在于构造函数中,定义方法时使用的函数声明方式不同,前者使用 箭头函数 ,后者使用 普通函数 ,而箭头函数与普通函数的区别在于: 执行时 this 的指向不同
。
- 箭头函数:箭头函数的 this 指向在 函数声明时即创建 ,理解为在定义函数的上下文中获取函数中的 this 指向。如题一中,虽然将函数整体赋值给变量 func ,但在函数调用的时候,仍然在箭头函数定义时的上下文中去查找 this ,即指向 Person 的实例化对象 P ,找到该对象的 name 属性,故打印 ‘Jack’
- 普通函数:普通函数的 this 指向在 函数调用时查找 。如题二,将函数整体赋值给变量 func ,相当于重新声明了一个函数名为 func 的函数,该函数在调用时与原构造函数和实例化对象都没有关系了,所以其中的 this 指向会向执行上下文中查找,故打印 ‘Tom’
ES5 继承
关于继承,简单来理解就是子类需要用到父类的属性和方法,而不用子类重新定义
如下代码中:
定义了一个 Person 类,我们希望 Student 类继承自 Person 类,并可以获取到 Person 类的 name/job/arr 属性 say 方法,但我们希望可以自己定义 job 为 ‘学生’
// Person 类:name/job/arr 属性,say 方法
function Person(name, job) {
this.name = name
this.job = job
this.arr = [1, 2, 3]
this.say = function() {
// 介绍自己的姓名和职业
console.log(`My name is ${this.name}, I'm a ${this.job}.`)
}
}
function Student() {}
-
构造函数继承
// Person 类:name/job/arr 属性,say 方法 function Person(name, job) { this.name = name this.job = job this.arr = [1, 2, 3] this.say = function() { // 介绍自己的姓名和职业 console.log(`My name is ${this.name}, I'm a ${this.job}.`) } } Person.prototype.on = 'on' function Student(name) { Person.call(this, ...arguments) this.job = 'student' } var s1 = new Student('Jack') var s2 = new Student('Tom')
此时的一个缺陷是:
- 若 Person 类的原型对象,即 prototype 上也定义了方法,那将无法继承到。如 Person.prototype.on 上的 ‘on’ 属性
-
prototype 继承
// Person 类:name/job/arr 属性,say 方法 function Person(name, job) { this.name = name this.job = job this.arr = [1, 2, 3] this.say = function() { // 介绍自己的姓名和职业 console.log(`My name is ${this.name}, I'm a ${this.job}.`) } } function Student(name) { this.name = name this.job = 'student' } Student.prototype = new Person() Student.prototype.constructor = Student var s1 = new Student('Jack') var s2 = new Student('Tom')
将 Student 的 prototype 属性指向 Person 的实例,当 Student 的实例没有找到属性时,会在 prototype 中寻找,但此时的问题有:
- 父类需要传入参数时,不能在 new Person() 中传入,只能在子类中重新写
- 所有的 Student 实例都会查找同一个 prototype ,所以不可以修改子类继承自父类的属性,如代码中的 arr ,一处修改会影响所有内容
- prototype.constructor 指向它本身的构造函数,修改以后要重新赋值
-
组合继承
// Person 类:name/job/arr 属性,say 方法 function Person(name, job) { this.name = name this.job = job this.arr = [1, 2, 3] this.say = function() { // 介绍自己的姓名和职业 console.log(`My name is ${this.name}, I'm a ${this.job}.`) } } Person.prototype.on = 'on' function Student(name) { Person.call(this, ...arguments) this.job = 'student' } Student.prototype = new Person() Student.prototype.constructor = Student var s1 = new Student('Jack') var s2 = new Student('Tom')
组合继承结合两种方法,实现继承。首先在构造函数内执行父类代码,再通过原型链获取到父类构造函数的原型对象 prototype 上定义的不变的属性和方法
ES6 继承
// Person 类:name/job/arr 属性,say 方法
class Person {
constructor (name, job) {
this.name = name
this.job = job
this.arr = [1, 2, 3]
}
say = function() {
// 介绍自己的姓名和职业
console.log(`My name is ${this.name}, I'm a ${this.job}.`)
}
}
class Student extends Person {
constructor () {
super()
this.job = 'student'
}
}
var s1 = new Student('Jack')
var s2 = new Student('Tom')
ES6 中通过关键字 extends 可以实现继承,如代码中, s1 和 s2 已经继承了Person 类中的属性和方法