整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

JavaScript继承(一)-原型链

JavaScript继承(一)-原型链

创: 前端二牛

继承是面向对象语言中一个最为人津津乐道的概念。许多面向对象语言都支持两种继承方式:接口继承和实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。在ECMAScript中,函数没有签名,无法实现接口继承,只支持实现继承,继承主要依靠的是原型链。

先来看一个简单的原型链继承示例,如下图:

首先声明一个 Human函数,给它的原型添加一个 getHumanSex方法。然后声明一个 Person函数,要让 Person继承 Human,能使用 Human的属性和方法,只要把 Person的原型重新赋值为 Human的一个实例就好了。最后给 Person的原型(也就是 Human的这个实例)添加一个 getPersonSex方法。现在创建 Person的一个实例 personObj, name='张三',age=12。如果访问 personObj.name,那么就可以直接访问到是 张三了。假如现在要访问 humanSex,因为 personObj中没有,所以会顺着 __proto__属性到 Person的原型中去找,而 Person的原型就是 Human的一个实例,它的 humanSex属性就是 '男/女',所以 personObj.humanSex的值就是 '男/女',这就达到了继承的效果。假如要访问 getPersonSex, personObj中没有,也是到原型中去找,找到了就调用,因为是使用 personObj.getPersonSex,所以在方法 getPersonSex中, this指的就是 personObj, returnthis.personSex就是返回 personObj.personSex,即为 '男'。 如果是调用 personObj.getHumanSex,还是先顺着 __proto__到 Person的原型中去找, Person的原型是 Human的一个实例,里面没有,那怎么办呢,因为该原型是 Human的一个实例,所以它自然也会有个 __proto__指向 Human的原型,于是就继续顺着这个 __proto__到 Human的原型中去找,这就是所谓的原型链,找到了就开始调用,自然 getHumanSex中的 this指的也就是 personObj, returnthis.humanSex就是 returnpersonObj.humanSex,即 '男/女'。这就是原型链继承。

细心的读者也许会发现,在 Human的原型中也有个 __proto__,那么 Human的原型会不会也是某个函数的一个实例呢?如果我们调用 personObj.toString(),那么我们调用的究竟是谁的 toString()呢?要回答这个问题,我们来看下面这幅示意图:

Human的原型其实是 Object的一个实例,自然 __proto__指向的就是 Object的原型,而 toString就是 Object原型上的方法。

其实ECMAScript中所有引用类型的默认原型都是 Object的实例,也就是说所有引用类型默认都继承了 Object,而这个继承正是通过原型链实现的。

原型的好处是所有的实例能共享它所包含的属性和方法,也就是说不必在构造函数中定义对象实例的信息,但正如JavaScript创建对象(三)——原型模式中所说,这也带来了一个问题。原型属性会被所有实例共享,对于引用类型会出现一些应用时的共享问题,所以需要在构造函中,而不是在原型对象中定义属性。但是在通过原型来实现继承时,原型实际上就是另一个函数的实例。于是,原来的实例属性也就顺理成章地变成了现在的原型属性了,那么原型共享数据的问题又出现了。

另外一个问题是,创建子类型的实例时,没法向超类型的构造函数中传递参数。实际上,应该说是没办法在不影响所有对象的实例下,给超类型的构造函数传递参数。有鉴于此,实践中很少会单独使用原型链。

文实例总结了JavaScript类的继承操作。分享给大家供大家参考,具体如下:

一、类式继承

首先要做的是创建构造函数。按惯例,其名称就是类名,首字母应该大写。在构造函数中,创建实例属性要用关键字this 。类的方法则被添加到prototype对象中。要创建该类的实例,只需结合关键字new调用这构造函数即可。

/* Class Person. */
function Person(name) {
 this.name=name;
}
Person.prototype.getName=function() {
 return this.name;
}
var reader=new Person('John Smith');
reader.getName();

二、原型链

JavaScript的每个对象都有一个名为prototype的属性,这个属性要么指向另一个对象,要么是null.在访问对象的某个成员时,如果这个成员未见于当前对象,那么就会到prototype所指的对象中去查找。如果还是没有找到,那么就会沿着原型链逐一访问每个原型对象,直到找到这个成员。这意味着让一个类继承另一个类,只需将子类的prototype设置为超类的一个实例即可。

为了让Author继承Person,必须手工将Author的prototype设置为Person的一个实例。最后一步是将prototype的construct属性重设为Author(因为prototype属性设置为Person的实例)时,其construct属性被抹除了。

function Author(name, books) {
 Person.call(this, name); // Call the superclass' constructor in the scope of this.
 this.books=books; // Add an attribute to Author.
}
Author.prototype=new Person(); // Set up the prototype chain.
Author.prototype.constructor=Author; // Set the constructor attribute to Author.
Author.prototype.getBooks=function() { // Add a method to Author.
 return this.books;
};
var author=[];
author[0]=new Author('Dustin Diaz', ['JavaScript Design Patterns']);
author[1]=new Author('Ross Harmes', ['JavaScript Design Patterns']);
console.log(author[1].getName());
console.log(author[1].getBooks());

三、extend函数

为了简化类的声明,可以把派生子类的整个过程包装在一个名为extend的函数中。它的作用与其他语言的extend关键字类似,即基于一个给定的类的结构创建一个新的类:

function extend(subClass, superClass) {
 var F=function() {};
 F.prototype=superClass.prototype;
 subClass.prototype=new F();
 subClass.prototype.constructor=subClass;
}

其实所做的事与之前的是一样的。它先设置了prototype,然后再将其constructor重设为恰当的值。并且中间利用了一个空函数,这样就可以避免创建超类的实例。使用extend继承的写法:

function Person(name) {
 this.name=name;
}
Person.prototype.getName=function() {
 return this.name;
}
/* Class Author. */
function Author(name, books) {
 Person.call(this, name);
 this.books=books;
}
extend(Author, Person);
Author.prototype.getBooks=function() {
 return this.books;
};

但上面的存在一个问题就是超类Person的名称被固化在Author类的声明当中。更普世性的做法应该像下面这样:

/* Extend function, improved. */
function extend(subClass, superClass) {
 var F=function() {};
 F.prototype=superClass.prototype;
 subClass.prototype=new F();
 subClass.prototype.constructor=subClass;
 subClass.superclass=superClass.prototype;
 if(superClass.prototype.constructor==Object.prototype.constructor) {
 superClass.prototype.constructor=superClass;
 }
}
/* Class Author. */
function Author(name, books) {
 Author.superclass.constructor.call(this, name);
 this.books=books;
}
extend(Author, Person);
Author.prototype.getBooks=function() {
 return this.books;
};
Author.prototype.getName=function() {
 var name=Author.superclass.getName.call(this);
 return name + ', Author of ' + this.getBooks().join(', ');
};

这个extend改进之后,多了一个superclass的属性,这个属性可以弱化Author和Person之间的耦合。extend后面三行用来确保超类的construtor已经被正确设置了。有了superclass的属性,就可以直接调用超类中的方法。这在既要重新定义超类的某个方法而又想访问其在超类中的实现时可以派上用场。例如,为了用一个新的getName的方法重新定义Person类中的同名方法,你可以先用Author.superclass.getName获得作者的名字,然后再次基础上添加新的信息。

四、原型继承

原型式继承与类式继承截然不同,我们在学习他的时候,最好忘记自己关于类和实例的一切知识,只从对象的角度来思考。使用原型式继承时,并不需要用类来定义对象的结构,只需直接创建一个对像就可以。这个对象随后可以被新的对象使用,该对象被称为原型对象。

下面使用原型对象来重新设计上面Person和Author:

var Person={
 name: 'default name',
 getName: function() {
 return this.name;
 }
};
var reader=clone(Person);
alert(reader.getName()); // This will output 'default name'.
reader.name='John Smith';
alert(reader.getName()); // This will now output 'John Smith'.

clone函数可以用来创建新的类Person对象,创建一个空对象,并且该对象的原型对象被设置为person。当新对象中找不到某个方法时就会在原型对象中查找。

你不必去为了创建Author而定义一个Person子类,只要执行一次克隆就可以:

var Author=clone(Person);
Author.books=[]; // Default value.
Author.getBooks=function() {
 return this.books;
}

然后你可以重定义该克隆中的方法和属性。可以修改Person的默认值。也可以添加新的属性和方法。这样一来就创建了一个新的原型对象,你可以将其用于创建新的Author对象:

var author=[];
author[0]=clone(Author);
author[0].name='Dustin Diaz';
author[0].books=['JavaScript Design Patterns'];
author[1]=clone(Author);
author[1].name='Ross Harmes';
author[1].books=['JavaScript Design Patterns'];
author[1].getName();
author[1].getBooks();

clone函数的写法:

function clone(object) {
 function F() {}
 F.prototype=object;
 return new F;
}

五、原型继承和类式继承之间的比较

可以自己去总结、

从内存,适用范围,优缺点等方面去分析

六、掺元类

有一种重用代码的方法不需要用到严格的继承,如果想把一个函数运用到多个类当中,可以通过扩充的方法让这些类共享函数。其实际大体做法就是:先创建一个包含各种通用的方法类,然后再扩充其他类,这种包含通用方法类称为掺元类,他们通常不会被实例化和直接调用,其存在的目的是向其他类提供自己的方法。

var Mixin=function() {};
Mixin.prototype={
 serialize: function() {
 var output=[];
 for(key in this) {
 output.push(key + ': ' + this[key]);
 }
 return output.join(', ');
 }
};
augment(Author, Mixin);
var author=new Author('Ross Harmes', ['JavaScript Design Patterns']);
var serializedString=author.serialize();
function augment(receivingClass, givingClass) {
 for(methodName in givingClass.prototype) {
 if(!receivingClass.prototype[methodName]) {
 receivingClass.prototype[methodName]=givingClass.prototype[methodName];
 }
 }
}

但是有时候你并不需要所有的方法,因此我们还需要提供额外的参数来选择我们所需要的方法。如果不提供,那就全部复制。

function augment(receivingClass, givingClass) {
 if(arguments[2]) { // Only give certain methods.
 for(var i=2, len=arguments.length; i < len; i++) {
 receivingClass.prototype[arguments[i]]=givingClass.prototype[arguments[i]];
 }
 }
 else { // Give all methods.
 for(methodName in givingClass.prototype) {
 if(!receivingClass.prototype[methodName]) {
 receivingClass.prototype[methodName]=givingClass.prototype[methodName];
 }
 }
 }
}

最后

以下是总结出来最全前端框架视频,包含: javascript/vue/react/angualrde/express/koa/webpack 等学习资料。

【领取方式】

关注头条 前端全栈架构丶第一时间获取最新前端资讯学习

手机用户可私信关键词 【前端】即可获取全栈工程师路线和学习资料!

家好,很高兴又见面了,我是姜茶的编程笔记,我们一起学习前端相关领域技术,共同进步,也欢迎大家关注、点赞、收藏、转发,您的支持是我不断创作的动力

面试官:如何理解继承和原型链?

我(屌毛):巴拉巴拉一堆,看下面

原型链(Prototype Chain)

  1. 原型(Prototype):JavaScript 中的每个对象(除了 null 外)都有一个原型。这个原型也是一个对象,可能有自己的原型。对象可以通过 __proto__ 属性(在 ES5 中标准化为 Object.getPrototypeOf())访问它的原型。
  2. 原型链(Prototype Chain):当尝试访问对象的属性时,如果对象本身没有这个属性,JavaScript 引擎会沿着原型链向上查找,直到找到该属性或者到达原型链的顶端(即 Object.prototype)为止。

继承(Inheritance)

JavaScript 中的继承是通过原型链实现的,这种方式通常称为原型继承。与传统的类继承不同,JavaScript 的继承基于对象。

  1. 构造函数和原型:构造函数(使用 function 关键字定义)用于创建对象实例。每个构造函数都有一个 prototype 属性,指向一个对象,这个对象作为用该构造函数创建的实例的原型。
  2. 实现继承:通过构造函数和原型的结合使用,可以建立对象之间的继承关系。子对象可以通过原型链访问父对象的属性和方法,实现代码重用和逻辑共享。

加深理解

// 构造函数
function Animal(name) {
    this.name=name;
}

// 方法定义
Animal.prototype.sayName=function() {
    console.log('My name is ' + this.name);
};

// 创建子对象
function Dog(name, breed) {
    Animal.call(this, name); // 调用父构造函数以初始化父属性
    this.breed=breed;
}

// 设置原型链
Dog.prototype=Object.create(Animal.prototype);
Dog.prototype.constructor=Dog; // 修正构造函数引用

// 添加子对象方法
Dog.prototype.bark=function() {
    console.log('Woof! I am a ' + this.breed);
};

// 创建实例
var myDog=new Dog('Buddy', 'Golden Retriever');

// 调用方法
myDog.sayName(); // 输出: My name is Buddy
myDog.bark();    // 输出: Woof! I am a Golden Retriever

在这个例子中:

  • Dog 通过原型链继承了 Animal 的属性和方法。
  • Animal.prototype 作为使用 new Animal() 创建的实例的原型。
  • Dog.prototype 链接到 Animal.prototype,使得 myDog 实例可以访问 Animal 中定义的 sayName() 方法。

最后

如果你有任何问题或建议,欢迎在评论区留言交流!Happy coding!