对象继承的集中方式
1.原型链继承
将父类的实例作为子类的原型
javascript
// 父类
function Person(name,friend) {
this.name = name;
this.friend = friend
this.say = function () {
console.log(`My name is ${this.name}.`);
};
}
Person.prototype.listen = function () {
console.log('I am listening...');
}
function Student(no) {
this.no = no;
}
Student.prototype = new Person(); // 将父类的实例作为子类的原型
Student.prototype.constructor = Student; // 修正
Student.prototype.sayNo = function () {
console.log(`My No. is ${no}`);
}
const stu = new Student(1);
stu.sayNo(); // My No. is 1
stu.say(); // My name is undefined.
stu.listen(); // I am listening...
console.log(stu instanceof Student); // true
console.log(stu instanceof Person); // true
缺点:
- 从父类中继承的属性没有添加到子类实例自身上,而是需要通过原型链访问,打印stu对象, 继承的属性是看不到的
- 在子类实例上,直接修改对象上继承的属性, 是给本对象添加了一个新属性(不论是基本类型还是引用数据类型)
- 在子类实例上,访问继承的属性,如果是引用数据类型,通过引用修改该引用数据,则多个子类实例之间会互相影响
javascript
var stu1 = new Student(1)
var stu2 = new Student(2)
// 1.直接修改继承的属性,是给本对象添加了一个新属性
stu1.name = "kobe"
console.log(stu1) // {no: 1, name: 'kobe'}
console.log(stu2) // {no: 2}
// 2.访问继承的引用数据类型,获取引用, 修改引用中的值, 会相互影响
stu1.friends.push("kobe")
console.log(stu1.friends) // ['kobe']
console.log(stu2.friends) // ['kobe']
- 无法实现多继承(即一个子类继承多个父类的属性或方法)?
- 创建子类实例时,不能向父类构造函数传参数,因为这个对象是一次性创建的没有办法定制化
2.借助构造函数继承
在子类型构造函数的内部调用父类型构造函数,使用父类的构造函数来增强子类 实例,等同于复制父类的实例给子类(不使用原型)。
- 因为函数可以在任意的时刻被调用;
- 因此通过apply()和call()方法让父类构造函数内部的this指向子类实例;
javascript
function Person(age) {
this.name= name;
this.age = age
}
Person.prototype.say = function(){
console.log('啦啦啦~')
}
function Student(no, name, age) {
Person.call(this, name);
this.no = no;
}
const stu = new Student(233, 'Ben', 18);
stu.say();// 无法继承到父类原型的方法
console.log(stu2 instanceof Student2); // true
console.log(stu2 instanceof Person); // false
缺点:
- 只能继承父类实例上的属性,不能继承原型上的属性
3.组合式继承
原型链继承和借用构造函数继承方法的结合,用原型链实现对原型属性和方法的继承,用借用构造函数技术来实现实例属性的继承。
javascript
// 父类: 公共属性和方法
function Person(name, age, friends) {
this.name = name
this.age = age
this.friends = friends
}
Person.prototype.eating = function() {
console.log(this.name + " eating~")
}
// 子类: 特有属性和方法
function Student(name, age, friends, sno) {
Person.call(this, name, age, friends) //1.借用构造函数
this.sno = 111
}
var p = new Person()
Student.prototype = p //2.原型链继承
p.constructor = Person
Student.prototype.studying = function() {
console.log(this.name + " studying~")
}
const stu = new Student("why", 18, ["kobe"], 111)
console.log(stu) // {age:18,name:'why',sno:111,firends:['kobe']}
缺点:
1.无论在什么情况下,都会调用 两次父类构造函数 。
- 一次在创建子类原型的时候
- 另一次在子类构造函数内部(也就是每次创建子类实例的时候)
2.所有的子类实例事实上会 拥有两份父类的属性
- 一份在当前的实例自己里面(也就是person本身的),
- 另一份在子类对应的原型对象中(也就是 person.__proto__里面);
当然,这两份属性我们无需担心访问出现问题,因为默认一定是访问实例本身这一部分的;
4.原型式继承
为了理解这种方式,我们先再次回顾一下JavaScript想实现继承的目的:重复利用另外一个对象的属性和方法.
- 这种继承方法不是通过构造函数来实现的,而是通过一个原型式继承函数实现。
- 最终的目的:student对象的原型指向了person对象。
javascript
// 原型式继承函数1
function createObject1(o) {
var newObj = {}
Object.setPrototypeOf(newObj, o)
return newObj
}
// 原型式继承函数2
function createObject2(o) {
function Fn() {}
Fn.prototype = o
var newObj = new Fn()
return newObj
}
// 原型式继承3
// Object.create可以替代上述两个函数的功能
var obj = {
name: "why",
age: 18
}
var info = Object.create(obj)
console.log(info)
console.log(info.__proto__===obj) //true
优点:
创建一个新的对象(子类实例),这个对象的原型是传入的对象(父类构造函数的实例),子类实例就可以通过原型链访问到父类实例的属性。
缺点:
- 继承的属性和方法只能通过原型链访问,并没有在子类实例上添加属性和方法,
- 具有和原型链继承一样的缺点,继承的引用数据类型,修改了之后会影响其他实例
5.寄生式继承
- 寄生式(Parasitic)继承是与原型式继承紧密相关的一种思想
- 寄生式继承的思路是结合原型式继承和工厂模式的一种方式
- 即创建一个封装继承过程的函数, 该函数在内部以某种方式来增强对象,最后再将这个对象返回