0
点赞
收藏
分享

微信扫一扫

JS 实现继承的七种方式


  • 原型链继承
  • 借助构造函数来继承
  • 构造函数+原型链 组合继承
  • 寄生组合式继承
  • 原型式继承
  • 寄生式继承
  • class+extends继承

一、原型链继承

核心:创建父类实例对象作为子类原型
优点:可以访问父类原型上的方法或属性,实现了方法复用
缺点:创建子类实例时,不能传父类的参数(比如name),子类实例共享了父类构造函数的属性值.

<script>// 原型链继承: 将构造函数(子)的原型设置为另一个构造函数(父)的实例对象。
function Person(name) {
this.name = name
this.sleep = function() {
console.log('睡觉');
}
}
Person.prototype.eat = function() {
console.log('吃饭');
}

function Student(score) {
this.score = score
}
// 关键步骤
Student.prototype = new Person('张三')
Student.prototype.constructor = Student
var s1 = new Student(98)
s1.eat() // 可以访问Person原型上的属性或方法
s1.sleep()</script>

吃饭
睡觉

<script>function Parent(name) {
this.name = name || '父亲' // 实例基本属性(该属性,强调私有,不共享)
this.arr = [1] //
}
Parent.prototype.say = function() { // 将需要复用、共享的方法写在父类原型上
console.log('hello');
}

function Child(like) {
this.like = like
}
Child.prototype = new Parent() // 核心,但此时,Child.prototype.constructor == Parent
Child.prototype.constructor = Child // 修正constructor 指向

let boy1 = new Child()
let boy2 = new Child()

// 优点: 共享了父类构造函数的say方法
console.log(boy1.say(), boy2.say(), boy1.say === boy2.say); // hello hello true
// * ?缺点1:不能向父类构造函数传参
console.log(boy1.name, boy2.name, boy1.name === boy2.name); // 父亲 父亲 true
// 缺点2: 子类实例共享了父类构造函数的引用属性,比如arr属性
boy1.arr.push(2)
// 修改了boy1的arr属性,boy2的arr属性,也会变化,因为两个实例的原型上(Child.prototype)有了父类构造函数的实例属性arr
// 所以只要修改了boy1.arr,boy2.arr的属性也会变化
console.log(boy2.arr); // [1,2]

// 注意1:修改boy1的name属性,是不会影响到boy2.name,因为设置boy1.name 相当于在子类实例新增了name属性
boy1.name = '小明'
console.log(boy1.name, boy2.name);
// 注意2:
console.log(boy1.constructor); // 为修复构造函数指向时,为 Parent 你会发现实例的构造函数居然是Parent
//而实际上,我们希望子类实例的构造函数是Child,所以要记得修复构造函数指向
// 修改如下:Child.prototype.constructor = Child</script>

二、构造函数继承

核心:在子构造函数中调用父构造函数
优点:解决了原型继承的缺点(使用构造函数来继承可以传父类的参数,可以解决子类共享父类构造函数中属性的问题)
缺点:子类无法访问父类原型中的方法和属性(这个原型链继承可以解决)

// 构造函数继承
function Person(name) {
this.name = name
this.sleep = function() {
console.log('睡觉');
}
}
Person.prototype.eat = function() {
console.log('吃饭');
}

function Student(name,) {
// 关键步骤
Person.call(this.name)
this.score = score
}
var s1 = new Student(98)
console.log(s1)
// 报错,无法访问父类原型链上的方法和属性
s1.sleep()
s1.eat()

三、构造函数+原型链 组合继承

核心:使用原型链实现对原型属性和方法的继承,而通过构造函数来实现对实例属性的继承。

缺点:调用了两次父类构造函数,会存在多一份的父类实例属性。

JS 实现继承的七种方式_前端

// 组合继承
function Person(name) {
this.name = name
this.sleep = function() {
console.log('睡觉');
}
}
Person.prototype.eat = function() {
console.log('吃饭');
}

function Student(name,) {
// 关键步骤 ,继承构造函数中的属性
Person.call(this.name)
this.score = score
}
// 关键步骤, 继承原型链中的属性和方法
Student.prototype = new Person()
Student.prototype.constructor = Student
var s1 = new Student(98)
s1.sleep()
s1.eat() // 可以访问父类原型链上的方法和属性

四、寄生组合式继承

(第三种方法)组合继承:无论什么情况下,都会调用两次父类的构造函数。寄生组合式继承就解决了上述问题,被认为是最理想的继承方式。

// 和上面的组合继承只有下面这一句不同
// 这样就不会调用两次父类构造函数了,只会调用一次
Student.prototype = Object.create(Person.prototype)
console.log(Student.prototype.__proto__ == Person.prototype) // true

五、原型式继承

采用原型式继承并不需要定义一个类,传入参数obj,生成一个继承obj对象的对象。
优点:采用原型式继承并不需要定义一个类,传入参数obj,生成一个继承obj 对象的对象。当只想单纯地让一个对象与另一个对象保持类似的情况下,原型式继承是完全可以胜任的。
缺点:不是类式继承,而是原型式继承,缺少了类的概念。

// 原型式继承 不自定义类型,临时创建一个构造函数,借助已有的对象作为临时构造函数的原型吗,然后在此基础实例化对象,并返回。
// 本质上,是object() 对传入其中的对象执行了一次浅复制(浅拷贝)
// 当只想单纯地让一个对象与另一个对象保持类似的请况下,原型式继承是完全可以胜任的,
function object(o) {
function F() {}
F.prototype = o;
return new F()
}
var person = {
name: 'Nicholas',
friends: ['Shelby', 'Court', 'Van']
}
var anotherPerson = object(person)
anotherPerson.name = 'Greg'
anotherPerson.friends.push('Rob')

var yetAnotherPerson = object(person)
yetAnotherPerson.name = 'Linda'
yetAnotherPerson.friends.push('Barbie')

console.log(person.friends); // 'Shelby,Court,Van,Rob,Barbie'

六、寄生式继承

创建一个仅仅用于封装继承过程的函数,然后在内部以某种方式增强对象,最后返回对象。
优点: 原型式继承的扩展(其实就是在原型式继承得的对象的基础上,在内部再以某种方式来增强对象,然后返回)
缺点:依旧没有类的概念

// 新的对象中不仅具有original的所有属性和方法,而且还有自己的sayHi()方法
// 寄生式继承在主要考虑对象而不是自定义类型和构造函数的情况下非常有用
// 由于寄生式继承为对象添加函数不能做到函数复用,因此效率降低
function createAnother(original) {
var clone = object(original)
clone.sayHi = function() {
console.log('Hi');
}
return clone;
}

七、class + extends 继承

ES6的类,完全可以看作构造函数的另一种写法。

class Point{
// ...
}
typeof Point // function
Point === Point.prototype.constructor // true

上面代码表明,类的数据类型就是函数,类本身就指向构造函数。
使用的时候,也就是直接对类使用new命令,跟构造函数的用法完全一致。

class Point{
construtor(){}
toString(){}
toValue(){}
}
// 等同于
Point.prototype = {
constructor(){},
toString(){},
toValue(){}
}

class + extends 继承:

// class + extends 继承  语法糖
// 使用class定义构造函数,实例对象的属性和方法放在constructor中,原型上的属性和方法定义在constructor外
class Person {
// constructor 中的内容是定义在实例对象中
constructor(name) {
this.name = name
}
// constructor外的内容定义在Person.prototype中
// sayName 方法定义在Person.prototype上
sayName() {
console.log(this.name);
}
}
class Student extends Person {
constructor(name,) {
super(name); // 必须要写这句话,否则会报错 super作为函数调用时表示父类的构造函数,但super内部的this指向的是子类
this.score = score
}
sleep() {
console.log('睡觉');
}
}
var s1 = new Student('张三', 100)
s1.sayName() // 张三
console.log(Student.prototype.__proto__ == Person.prototype); // true
console.log(Student.__proto__ == Person); // true
console.log(Student.prototype.constructor == Student); // true
console.log(Person.__proto__ == Function.prototype); // true
console.log(Person.prototype.__proto__ == Object.prototype); // true

JS 实现继承的七种方式_构造函数_02

// 寄生式组合方式继承
function Person(name) {
this.name = name
}
Person.prototype.sayName = function() {
console.log(this.name);
}

function Student(name,) {
Person.call(this.name)
this.score = score
}
Student.prototype = Object.create(Person.prototype)
Student.prototype.constructor = Student
Student.prototype.sleep = function() {
console.log('睡觉');
}

var s1 = new Student('张三', 100)
s1.sayName() // 张三
console.log(Student.prototype.__proto__ == Person.prototype); // true
console.log(Student.__proto__ == Person); // false
console.log(Student.__proto__ == Function.prototype); // true

JS 实现继承的七种方式_构造函数_03


class+extends式继承 与 寄生组合式继承 的区别:

// 区别1: 寄生组合方式,输出false, class+extends 输出true
console.log(Student.__proto__ == Person);
// 区别2: 寄生组合方式继承时,需要手动更改
// Student.prototype.constructor 的值才输出true.而class不需要手动设置,内部会自动帮我们设置
console.log(Student.prototype.constructor == Student); // true


举报

相关推荐

0 条评论