JavaScript中的继承方式

网友投稿 281 2022-09-21

JavaScript中的继承方式

文章目录

​​一、构造函数继承(call与apply)​​

​​1. 构造继承(利用call改变this指向)​​​​2. 多重继承(使用多个 apply、call)​​​​3. s2 继承了 s1 上的 showName 方法​​

​​二、原型链继承​​​​三、组合方式(混合了call方式、原型链方式)​​​​四、原型式继承​​​​五、寄生式继承​​​​六、寄生组合式继承( 常用)​​​​对象冒充​​

还不了解原型和原型链的小伙伴,请移步​​“深入理解javascript之原型和原型链”​​

首先,定义父类:

function Person(name) { this.name = name; this.showName = function () { return this.name; }}// 原型对象上添加属性Person.prototype.age = 18;Person.prototype.friends = ['小明', '小强'];

一、构造函数继承(call与apply)

1. 构造继承(利用call改变this指向)

优点:

创建子类实例时,可以向父类传递参数可以实现多重继承(call多个父类对象,如例2)

缺点:

实例并不是父类的实例,只是子类的实例只能继承父类的实例属性和方法,不能继承原型属性/方法无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

function Student(name) { Person.call(this, name); // Person.apply(this, [name]);}let s1 = new Student('小红')console.log(s1.name); // 小红console.log(s1.showName()); // 小红// 无法继承原型上的属性和方法console.log(s1.age); // undefinedconsole.log(s1.friends); // undefined

2. 多重继承(使用多个 apply、call)

function Class1() { this.showSub = function (a,) { console.log(a - b); }}function Class2() { this.showAdd = function (a,) { console.log(a + b); }}// 使用多个 apply、call 就实现多重继承了function Class3() { Class1.apply(this); Class2.apply(this); //Class1.call(this); //Class2.call(this); }var c = new Class3();c.showSub(3, 1); // 2c.showAdd(3, 1); // 4

3. s2 继承了 s1 上的 showName 方法

function Student(name) { this.name = name;}var s1 = new Person('张三');var s2 = new Student('李四');console.log(s1.showName.call(s2)); // 李四// console.log(s1.showName.apply(s2));

虽然调用的是 s1 上的 showName 方法,但是 this 指针指向的是 s2 ,所以 this.name 应该是 “李四”

二、原型链继承

优点:

Student 实例可继承父类构造函数(​​Person​​​)的属性方法和父类原型对象(​​Person.prototype​​)上的属性方法简单,易于实现

缺点:

Student 实例无法向父类构造函数(​​Person​​)传参(组合方式继承中解决)继承单一,无法实现多继承(组合方式继承中解决)来自原型对象的所有属性被所有实例共享。(原型上的属性是共享的,一个实例修改了原型属性,另一个实例的原型属性也会被修改!)

function Student(name) { this.name = name;}Student.prototype = new Person(); // prototype指向实例对象,可以继承Person的构造函数属性和原型对象上的属性// Student.prototype = Person.prototype; // 不能继承Person的构造函数属性,只能继承Person原型对象上的属性,还会修改子会影响父Student.prototype.constructor = Student;let s1 = new Student('刘一');console.log(s1.name); // 刘一console.log(s1.showName()); // 刘一console.log(s1.age); // 18console.log(s1.friends); // ["小明", "小强"]// 父子构造函数的原型对象之间有共享问题(修改子会影响父)s1.friends.push('小红');console.log(s1.friends); // ["小明", "小强", "小红"]let s2 = new Student();console.log(s2.friends); // ["小明", "小强", "小红"]

三、组合方式(混合了call方式、原型链方式)

组合式继承是比较常用的一种继承方法,其背后的思路是:

通过使用原型链实现对原型属性和方法的继承(​​Child.prototype = new Parent()​​)通过借用构造函数来实现对实例属性的继承(​​Parent.call(this,hello)​​)

优点:

可以继承父类原型上的属性,可以传参,可复用每个新实例引入的构造函数属性是私有的

缺点:

调用了两次父类构造函数 Person(耗内存),子类的构造函数会代替原型上的那个父类构造函数。父子构造函数的原型对象之间有共享问题(问题依旧)

function Worker(name) { Person.call(this, name); // 构造函数继承}Worker.prototype = new Person(); // 原型链继承Worker.prototype.constructor = Worker;let w1 = new Worker('张三');console.log(w1.name); // 张三console.log(w1.showName()); // 张三console.log(w1.age); // 18console.log(w1.friends); // ["小明", "小强"]//一个实例修改了原型属性,另一个实例的原型属性也会被修改 w1.friends.push('李四')let p1 = new Person()console.log(p1.friends); // ["小明", "小强", "李四"]

四、原型式继承

优点:

类似于复制一个对象,用函数来包装。

缺点:

所有实例都会继承原型上的属性。无法实现复用。(新实例属性都是后面添加的)

​​content()​​​ 对传入其中的对象执行了一次​​浅复制​​​,将构造函数​​Fn​​的原型直接指向传入的对象。

function content(obj) { // 先封装一个函数容器,用来输出对象和承载继承的原型 function Fn() {} Fn.prototype = obj; // 修改类的原型为obj, 于是Fn的实例都将继承obj上的方法 return new Fn();}let p1 = content(new Person('李四'));//上面在ECMAScript5 有了一新的规范写法,Object.create() 效果是一样的 // let p1 = Object.create(new Person('李四'));console.log(p1.name); // 李四console.log(p1.showName()); // 李四console.log(p1.age); // 18console.log(p1.friends); // ["小明", "小强"]

五、寄生式继承

优点:

没有创建自定义类型,因为只是套了个壳子返回对象(这个),这个函数顺理成章就成了创建的新对象。

缺点:

没用到原型,无法复用。

function content(obj) { function Fn() {} Fn.prototype = obj; // 继承了传入的参数 return new Fn();}// 在原型式继承外面套了个壳子function subObj(obj) { let sub = content(obj); sub.name = "王五"; return sub;}let p1 = subObj(new Person());console.log(p1.name); // 王五console.log(p1.showName()); // 王五console.log(p1.age); // 18console.log(p1.friends); // // ["小明", "小强"]

六、寄生组合式继承( 常用)

优点:

堪称完美

// 寄生function content(obj) { function Super() {} Super.prototype = obj; // 继承了传入的参数 return new Super();}let con = content(Person.prototype);// 组合function Student(name) { Person.call(this, name);}Student.prototype = con;con.constructor = Student; // Slet s1 = new Student('赵六');console.log(s1.name); // 赵六console.log(s1.showName()); // 赵六console.log(s1.age); // 18console.log(s1.friends); // // ["小明", "小强"]

将上面的简化下

function Student(name) { Person.call(this, name);}// 创建一个没有实例方法的类let Super = function () {};Super.prototype = Person.prototype;//将实例作为子类的原型Student.prototype = new Super();let s1 = new Student('赵六');console.log(s1.name); // 赵六console.log(s1.showName()); // 赵六console.log(s1.age); // 18console.log(s1.friends); // // ["小明", "小强"]

对象冒充

function Temp(name) { this.aaa = Person; // this.aaa是作为一个临时的属性,并且指向 Person 所指向的对象, this.aaa(name); // 执行this.aaa方法,即执行 Person 所指向的对象函数 delete this.aaa; // 销毁this.aaa属性,即此时 Temp 就已经拥有了 Person 的所有属性和方法 // 以上三行相当于: Person.call(this);}var s1 = new Temp('张三');console.log(s1.name); // 李四console.log(s1.showName()); // 李四// 无法继承原型对象上的属性方法console.log(s1.age); // undefinedconsole.log(s1.friends); // undefined

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:微信视频号公会入驻?
下一篇:js Math对象的常用方法
相关文章

 发表评论

暂时没有评论,来抢沙发吧~