js中的继承:在js中的继承只是属性和方法的继承 一般没有所谓的多态性,js的继承只是为了使代码的复用性更高。
js的继承是通过prototype来继承的
1 原型链继承
这种方式关键在于:**子类型的原型为父类型的一个实例对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function Person(name, age) { this.name = name, this.age = age, this.play = [1, 2, 3] this.setName = function () {} } Person.prototype.setAge = function () {} function Student(price) { this.price = price this.setScore = function () {} }
Student.prototype = new Person() var s1 = new Student(15000) var s2 = new Student(14000) console.log(s1,s2)
|
说明:定义一个Person父类型拥有对象和方法 Person.prototype.setAge = function () {} 这是一种在外部定义的一个在prototype原型上的方法 原理:每个function身上都有一个原型 在原型身上可以挂载方法和属性
Student.prototype = new Person() Student原型指向Person的实例对象,所以子类的实例就可以通过proto__访问到 Student.prototype 也就是Person的实例,这样就可以访问到父类的私有方法,然后再通过__proto指向父类的prototype就可以获得到父类原型上的方法。于是做到了将父类的私有、公有方法和属性都当做子类的公有属性
弊端:子类继承父类的属性和方法是将父类的私有属性和公有方法都作为自己的公有属性和方法 如果父类的私有属性有引用类型,子类继承的是这个父类的引用地址 如果子类操作父类的引用类型就会导致引用类型改变
我们需要在子类中添加新的方法或者是重写父类的方法时候,切记一定要放到替换原型的语句之后
2 借用构造函数继承
这种方式关键在于:在子类型构造函数中通用call()调用父类型构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13
| function Person(name, age) { this.name = name, this.age = age, this.setName = function () {} } Person.prototype.setAge = function () {} function Student(name, age, price) { Person.call(this, name, age)
this.price = price } var s1 = new Student('Tom', 20, 15000)
|
说明:在Student中构造Person的属性和方法,但是只是实现部分继承,如果父类的属性和方法太多,继承麻烦。但是解决了引用类型的问题因为这个构造函数是再次创建实例
弊端:子类的实例是根据父类的实例重新构造的,只能继承父类的实例属性和方法,不能继承原型属性和方法 代码复用性弱,每个子类都是父类的实例函数副本,影响性能
3 原型链+借用构造函数的组合继承
这种方式关键在于:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| function Person (name, age) { this.name = name, this.age = age, this.setAge = function () { } } Person.prototype.setAge = function () { console.log("111") } function Student (name, age, price) { Person.call(this, name, age) this.price = price this.setScore = function () { } }
Student.prototype = new Person() Student.prototype.constructor = Student Student.prototype.sayHello = function () { } var s1 = new Student('Tom', 20, 15000) var s2 = new Student('Jack', 22, 14000) console.log(s1) console.log(s1.constructor) console.log(p1.constructor)
|
说明:在子类中调用构造函数生成实例对象但是只是包含属性,在子类的原型链上继承Person的实例,再次使用构造函数,是刚继承的实例构造成一个新的实例赋值 这个实例属于Student 构造函数(constructor) –字面意思,能构造一个实例的函数
弊端:无论如何都会调用两次构造函数,创建子类的原型和子类构造函数内部
4 组合继承的优化-原型指向
这种方式关键在于 通过父类原型和子类原型指向同一对象,子类可以继承到父类的公有方法当做自己的公有方法,而且不会初始化两次实例方法/属性,避免的组合继承的缺点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function Person (name, age) { this.name = name, this.age = age, this.setAge = function () { } } Person.prototype.setAge = function () { console.log("111") } function Student (name, age, price) { Person.call(this, name, age) this.price = price this.setScore = function () { } }
Student.prototype = Person.prototype Student.prototype.sayHello = function () { } var s1 = new Student('Tom', 20, 15000) console.log(s1)
|
说明:原型的赋值会让两个类指向同一个对象 ,与上面的区别在于一个是构造函数的赋值 这是原型的赋值,不会生成两次实例
弊端:父类指向不明,都是原型的赋值 同一个对象,没有父子关系,引用类型问题,
5 组合继承的优化-创建对象
这种方式关键在于 借助原型可以基于已有的对象来创建对象,var B = Object.create(A)以A对象为原型,生成了B对象。B继承了A的所有属性和方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function Person (name, age) { this.name = name, this.age = age } Person.prototype.setAge = function () { console.log("111") } function Student (name, age, price) { Person.call(this, name, age) this.price = price this.setScore = function () { } } Student.prototype = Object.create(Person.prototype) Student.prototype.constructor = Student var s1 = new Student('Tom', 20, 15000) console.log(s1 instanceof Student, s1 instanceof Person) console.log(s1.constructor) console.log(s1)
|
说明:Student.prototype = Object.create(Person.prototype) 创建一个基于Person的的prototype创建一个新的对象给Student,继承Person 的所有的属性和方法 (Object.create)Object身上挂载create这个方法可以创建新的对象
目前来说,使用缺点都解决了,但是还未趋近完美
6 Es6的class继承
ES6中引入了class关键字,class可以通过extends关键字实现继承,还可以通过static关键字定义类的静态方法,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。
ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。
需要注意的是,class关键字只是原型的语法糖,JavaScript继承仍然是基于原型实现的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class Person { constructor(name, age) { this.name = name this.age = age } showName () { console.log("调用父类的方法") console.log(this.name, this.age); } } let p1 = new Person('kobe', 39) console.log(p1)
class Student extends Person { constructor(name, age, salary) { super(name, age) this.salary = salary } showName () { console.log("调用子类的方法") console.log(this.name, this.age, this.salary); } } let s1 = new Student('wade', 38, 1000000000) console.log(s1) s1.showName()
|
说明:本质上原型和class没有区别,但是代码的整洁可读性更高 ,super相当于原型继承的 Person.call(this, name, age)