JS实现继承的7种方式,你都知道哪几种???

news/2024/7/10 23:28:17 标签: js, javascript, es6, 继承, 原型链

题记

ECMAScript只支持实现继承,而且其实现继承主要是依靠原型链来实现的。

继承的方式

方式一:原型链

关于原型链实现继承的方式我们在 你清楚JS的原型链吗 这里提到过,是通过让原型对象等于另一个类型的实例来实现继承的,但是原型链继承也存在一些问题:

①最主要的问题来自包含引用类型值的原型

包含引用类型值的原型属性会被所有实例共享,在原型链继承方式中,原型实际上会变成另一个类型的实例,于是,原先的实例属性也就顺理成章的变成了现在的原型属性。

javascript">// Parent 父亲的构造函数
function Parent() {
  this.children = ["son1", "son2", "son3"];
}
// Son 儿子的构造函数
function Son() {}

// Son 继承了 Parent
Son.prototype = new Parent();

// 实例化了child1和child2 向child1的children属性push数据,同时会修改child2的children属性
var child1 = new Son();
child1.children.push("daughter");
console.log(child1.children);

var child2 = new Son();
console.log(child2.children);

打印结果:
在这里插入图片描述
②在创建子类型的实例时,不能向超类型的构造函数中传递参数。

方式二:借用构造函数

实现思想:在子类型构造函数的内部调用超类型构造函数

javascript">// Parent 父亲的构造函数
function Parent() {
  this.children = ["son1", "son2", "son3"];
}
// Son 儿子的构造函数
function Son() {
  // 继承了 Parent,借调了超类型的构造函数
  Parent.call(this);
}
// 实例化了child1和child2 每个对象都有Parent中所有的初始化代码
var child1 = new Son();
child1.children.push("daughter");
console.log(child1.children);

var child2 = new Son();
console.log(child2.children);

打印结果:
在这里插入图片描述
优点:相比于原型链来说,借用构造函数可以在子类型构造函数中向超类型构造函数中传递参数

javascript">// 1. 父构造函数
function Father(uname, age) {
    // this 指向父构造函数的对象实例
    this.uname = uname;
    this.age = age;
}
// 2 .子构造函数 
function Son(uname, age, score) {
    // this 指向子构造函数的对象实例,同时还传递了参数
    Father.call(this, uname, age);
    this.score = score;
}
var son = new Son('刘德华', 18, 100);
console.log(son);

打印结果:
在这里插入图片描述
缺点:仅仅使用借用构造函数,无法避免构造函数模式存在的问题—方法都在构造函数中定义,无法实现函数覆用。

方式三:组合继承

组合继承,也叫做伪经典继承,指的是将原型链和借用构造函数的技术组合到一块的继承模式。实现思想是使用原型链实现对原型属性和方法的继承,通过借用构造函数实现对实例属性的继承

javascript">// Parent 父亲的构造函数
function Parent(name) {
  this.name = name;
  this.children = ["son1", "son2", "son3"];
}
// 给 Parent 原型对象中添加 sayName 的方法
Parent.prototype.sayName = function() {
  console.log(this.name)
}
// Son 儿子的构造函数
function Son(name, age) {
  Parent.call(this, name);      // 第二次调用Parent()
  this.age = age
}
// Son 继承了 Parent
Son.prototype = new Parent();   // 第一次调用Parent()
// 手动设置 constructor
Son.prototype.constructor = Son;
Son.prototype.sayAge = function() {
  console.log(this.age)
}

// 实例化了child1和child2 
var child1 = new Son("Sam", 23);
child1.children.push("daughter");
console.log(child1.children);
child1.sayName()
child1.sayAge()

var child2 = new Son("Helen", 22);
console.log(child2.children);
child2.sayName()
child2.sayAge()

打印结果:
在这里插入图片描述
组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为JS中最常用的继承模式。组合继承最大的问题就是无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候;另一次是在子类型构造函数内部。

在上述代码中,第一次调用Parent()构造函数时,Son.prototype会得到两个属性:name和children,他们都是Parent的实例属性,只不过现在位于Son的原型中。当调用Son构造函数时,又会调用一次Parent构造函数,这一次又在新对象上创建了实例属性name和children。于是,这两个属性就屏蔽了原型中的两个同名属性。

方式四:原型式继承

借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。

javascript">function object(o) {
	function F(){}
	F.prototype = o;
	return new F();
}

在object()函数内部,先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回这个临时类型的一个新实例。从本质上讲,object()对传入其中的对象执行了一次浅复制。

javascript">var person = {
	name: "Helen",
	friends: ['sam', 'sunny']
};
var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");

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

alert(person.friends);   // sam,sunny,Rob,Barbie

这种原型式继承,要求必须有一个对象可以作为另一个对象的基础,在这个例子中,可以作为另一个对象的基础是person对象,于是我们把它传入到object()函数中,然后这个函数就会返回一个新对象。这个新对象将person作为原型,所以它的原型中就包含一个基本类型值属性和一个引用类型值属性。这意味着person.friends不仅属于person所有,而且也会被anotherPerson以及yetAnotherPerson共享。

ES5新增的Object.create()方法规范了原型式继承,这个方法接收两个参数:一个用做新对象原型的对象和一个为新对象定义额外属性的对象(可选)。在传入一个参数的情况下,Object.create()与object()方法的行为相同。在传入两个参数的情况下,定义的任何属性都会覆盖原型对象上的同名属性。

javascript">var person = {
	name: "Helen",
	friends: ['sam', 'sunny']
};
var anotherPerson = Object.create(person, {
	name: {
		value: "Greg"
	}
});
alert(anotherPerson.name);  // Greg

在只想让一个对象与另一个对象保持类似的情况下,原型式继承是完全可以胜任的,不过别忘了,包含引用类型值的属性始终都会共享相应的值,就像使用原型模式一样。

方式五:寄生式继承

创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真的是它做了所有工作一样返回对象。

javascript">function createAnother(original){
	var clone = object(original);   // 通过调用函数创建一个对象
	clone.sayHi = function(){		// 以某种方式来增强这个对象
		alert("hi"); 
	};
	return clone;   				// 返回这个对象
}

createAnother()函数接受了一个参数,也就是将要作为新对象基础的对象。然后,把这个对象(original)传递给object()函数,将返回的结果赋值给clone。再为clone对象添加一个新方法sayHi(),最后返回clone对象。可以像下面这样来使用
createAnother()函数。

javascript">var person = {
	name: "Helen",
	friends: ['sam', 'sunny']
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi();  // hi

任何能够返回新对象的函数都适用于此模式。但是与构造函数模式类似,使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率。

方式六:寄生组合式继承

即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。本质上,就是使用寄生式继承继承超类型的原型,然后再将结果指定给子类型的原型。是为了解决组合继承调用两次超类型构造函数的问题。

javascript">function inheritPrototype(subType, superType) {
	var prototype = object(superType.prototype);   // 创建对象
	prototype.constructor = subType;			   // 增强对象
	subType.prototype = prototype;				   // 指定对象
}

inheritPrototype函数接收两个参数:子类型的构造函数和超类型的构造函数。在函数内部,第一步是创建超类型原型的一个副本;第二步是为创建的副本添加constructor属性;第三步是将新创建的对象赋值给子类型的原型。这样就可以用调用inheritPrototype函数的语句,去替换组合继承中为子类型原型赋值的语句。

javascript">// Parent 父亲的构造函数
function Parent(name) {
  this.name = name;
  this.children = ["son1", "son2", "son3"];
}
// 给 Parent 原型对象中添加 sayName 的方法
Parent.prototype.sayName = function() {
  console.log(this.name)
}
// Son 儿子的构造函数
function Son(name, age) {
  Parent.call(this, name);  
  this.age = age
}

inheritPrototype(Son, Parent);

Son.prototype.sayAge = function() {
  console.log(this.age)
}

开发人员认为寄生组合式继承是引用类型最理想的继承范式,因为它只调用了一次超类型构造函数,并且避免了在子类型的原型上面创建不必要的、多余的属性。与此同时,原型链还能保持不变,还能正常使用instanceof和isPrototypeOf()。

方式七:ES6中的继承

ES6中有了类的概念。

javascript">class Father {
    constructor() {}
    money() {
        console.log(100);
    }
}
class Son extends Father {}
var son = new Son();
son.money();    // 100

也支持向超类型构造函数中传参:

javascript">class Father {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
    sum() {
        console.log(this.x + this.y);
    }
}
class Son extends Father {
    constructor(x, y) {
        super(x, y);     // 调用了父类中的构造函数
    }
}
var son = new Son(1, 2);
var son1 = new Son(11, 22);
son.sum();    // 3
son1.sum();	  // 33

http://www.niftyadmin.cn/n/1659463.html

相关文章

Vue 从零开始搭建PC端项目完整框架(附GitHub地址和Vue项目执行流程)

如何用Vue搭建一个PC端的项目呢?搭建完成都需要配置哪些内容呢?本篇文章会告诉你答案。 搭建项目的主要步骤如下: 1.根据脚手架创建一个项目 2.引入axios发送请求 3.配置拦截器(注意http的error问题) 4.引入组件库 5.如…

10.正则表达式

由于在Javascript学习中,已经涉及了部分的“正则表达式”相关知识,在这里,就PHP而言,再来深究探讨一番,来看看这两者有什么不同吧! 内容要点: 1、正则表达式语法 2、正则表达式元素 3、perl…

前端布局系列---居中布局的多种实现方式

布局是指HTML的整体结构,好的布局直接影响到用户的体验,同样,在前端面试的过程中,布局也是一个必问点,所以,在这里小编进行了统一整理,形成一个布局系列博客,主要包含以下布局方式&a…

11.日期和时间

1、验证时间 checkdate(月,日,年) 参数:3个参数(月、日、年) 作用:(1)该函数用于验证时间, (2)判断日期是否合法 返回…

微信小程序原生框架实现界面上拉加载和下拉刷新

在移动端开发中,上拉加载和下拉刷新是很常见的功能,可以用它来替代PC端表格分页的功能。今天我们来复盘一下在小程序中实现该功能的思路。 上拉加载 当用户上滑界面时,滚动条触底,开始加载下一页数据,实现思路如下&a…

12.表单与验证

学习要点: 1、Header()函数 2、接收及验证数据 表单作用:用于验证 1、Header()函数 标头header是服务器以HTTP协议传html资料到浏览器前所送到的字符串, 在标头与HTML文件之间需空一行的间隔。 (1)重新导向一个指定…

小程序入门到上手开发项目,这些知识点应该知道(附项目的git地址)

小程序的特点 小程序在目前越来越流行,离不开小程序的特点: 对于用户来说:   ①无需安装,即搜即用;   ②优秀的用户体验,和APP体验相同;   ③依靠微信,使用方便 对于开发者…

13.Cookie的应用与Session会话处理(小案例:登录验证)

背景 在HTTP(超文本传输协议)定义通过万维网(WWW)传输文本、图形、视频和所有其他数据的所有规 则。HTTP是一种无 状 态的协议,说明每次请求的处理都与之前或之后的请求无关。虽说对HTTP的普及做 出了相应的贡献…