【javascript基础】7、继承

原文:【javascript基础】7、继承

前言


由于本人水平有限,所以有些高手觉得现在写的内容偏容易,要一点点来嘛,今天和大家学习或者复习一下javascript的继承。我也就是尽量写吧······

继承

javascript的继承其实主要就是通过原型链来实现的,原型链我们之前已经和大家一起学习过,这里就不浪费大家的时间了。javascript连类都没有,还说啥继承呢,这还是模拟类的继承。《javascript高级程序设计》上分成了几个方式,有的书上分为类式继承,原型式继承,这就是模拟其他语言类的继承,还有什么用掺元类实现的,在这里都和大家说下。

原型链

在这里在说一下原型链的概念,因为javascript的继承都是通过原型链来模拟的,所以在这里帮助大家理解一下。我们知道,每一个构造函数都有一个原型对象,这个原型对象中包含一个指向构造函数的指针,同时每一个实例都有一个指向原型对象的内部指针。好好想一下这个关系,当我们访问一个实例的属性时,现在实例中查找,没找到通过内部指针去原型中查找,还是没有再通过原型的内部指针查找原型的原型对象,一直迭代下去。嗯,就是这样,

现在我们知道了原型链是这样的话,我们想要继承的实现,我们要把父类的属性和方法放在子类的原型对象中就可以了,这样new出来的实例就会查找原型中的属性和方法了,那这样就可以实现继承了,那我们要怎样将父类的属性和方法放在子类的原型中呢?我们重写子类的原型对象是不是就可以了,这里有个选择的问题,我们可以让子类的原型对象指向父类的原型对象,也可以指向一个父类的实例,假如现在我们将它指向了父类的原型对象,我们知道父类构造函数中的属性就不会在子类中得到继承,看个例子就知道了


//父类
function Animal(){
this.className = "动物";
}
//父类原型
Animal.prototype.getClassName = function(){
console.log(this.className );
}
//子类
function Cat(){}
//重写子类原型
Cat.prototype = Animal.prototype;
var Tom = new Cat();
Tom.getClassName();//undefined

其实这是另外一种方式的雏形,寄生组合模式的雏形,下文我会讲到,这里暂且放过。

我们现在再看看指向一个实例对象的情况


//父类
function Animal(){
this.className = "动物";
}
//父类原型
Animal.prototype.getClassName = function(){
console.log(this.className );
}
//子类
function Cat(){}
//重写子类原型
Cat.prototype =new Animal();
var Tom = new Cat();
Tom.getClassName();//动物

这下子大家会明白了,实例是把构造函数中this的属性和原型中的属性结合起来了,如果指向原型对象那么构造函数中的属性就不会被继承。

这就是继承的最基础和最核心的东西,这还不完善,重新原型对象我们知道,要增加一个constructor属性,这里不添加了,不明白的看之前的原型与原型链的那篇文章。javascript用instanceof来判断实例与原型的关系,只要实例和原型链中出现过构造函数,就会返回true

console.log(Tom instanceof Cat);//true
console.log(Tom instanceof Animal);//true
console.log(Tom instanceof Object);//true

原型链的问题:其实这个和构造对象原型链的问题是一样的,主要是原型对象的属性是一个引用类型,会引起一些问题。这是因为所有的实例共用原型对象的属性,当属性为引用类型时,任何一个实例对这个对象的修改会影响所有的实例。例子来了


//父类
function Animal(){
this.className = "动物";
this.colors = ["black"];
}
//父类原型
Animal.prototype.getClassName = function(){
console.log(this.className );
}
//子类
function Cat(){}
//重写子类原型
Cat.prototype = new Animal;
var Tom = new Cat();
console.log(Tom.colors);//"black"
Tom.colors.push("yellow");
var Garfield= new Cat();
console.log(Garfield.colors);//"black", "yellow"

原型链还有一个问题就是,不能向父类的构造函数中传递参数,就是这样的我想给每一个子类起一个名字,这里是无法办到的,因为我给父类的名字都挂在了子类的原型上了。例如


//父类
function Animal(name){
this.name = name;
}
//子类
function Cat(){}
//重写子类原型
Cat.prototype = new Animal("无名氏");
var Tom = new Cat();
console.log(Tom.name);//无名氏
var Garfield= new Cat();
console.log(Garfield.name);//无名氏

这里要起一个名字,所有实例都会影响,所以说没有办法在不影响所有实例的情况下给父类传递参数。

借用构造函数

这个方式可以解决上面的问题,我们知道上面的原型链的方法是子类的原型对象指向了父类的实例,就是把所有父类的属性都挂在了子类的原型对象上,所有就会出现所有实例共享同一个属性引发的问题,那我们可以换一种思路,我们把一些父类的属性放在子类的构造函数中,就是在子类的构造函数中的this添加属性,这样就不需要所有的属性都弄到子类的原型对象上了,这样每个子类的实例都会有自己的属性和方法,不用共享原型中的属性了。这是一个简单的思路,我们在子类的构造函数中给this添加父类的属性,我们想到了之前的apply和call方法,看例子


//父类
function Animal(){
this.className = "动物";
this.colors = ["black"];
}
//子类
function Cat(){
Animal.call(this);//相当于 this.className = "动物";this.colors = ["black"];
}var Tom = new Cat();
console.log(Tom.colors);//"black"
Tom.colors.push("yellow");
console.log(Tom.colors);//"black", "yellow"
var Garfield= new Cat();
console.log(Garfield.colors);//"black"

这时候你也可以给父类传参数了,因为这些属性都添加了子类构造函数中了,看例子


//父类
function Animal(name){
this.name = name;
}
//子类
function Cat(name){//传入参数
Animal.call(this,name);
}
var Tom = new Cat("Tom");
console.log(Tom.name);//Tom
var Garfield= new Cat("Garfield");
console.log(Garfield.name);//Garfield

借用构造函数问题:这个又回归到了构造函数模式上出现的问题了,我们所有的方法都是在构造函数上定义的,无法复用。

组合继承(类式继承)

原型链和借用构造函数结合一起,使用原型链实现原型属性和方法的继承,使用借用构造函数实现对实例属性的继承,这样通过在原型上定义方法实现函数的复用,又能保证每个实例都有自己的属性。上例子


//父类
function Animal(name){
this.name = name;
this.colors = ["black"];
}
//父类原型
Animal.prototype.getName = function(){
return this.name;
}
//子类
function Cat(name,age){//传入参数
Animal.call(this,name);
this.age = age;
}
Cat.prototype = new Animal("无名氏");
Cat.prototype.constructor = Cat;
Cat.prototype.getAge = function(){
return this.age;
}
var Tom = new Cat("Tom",20);
console.log(Tom.getName() +" : "+ Tom.getAge());//Tom : 20
Tom.colors.push("red");
var Garfield= new Cat("Garfield",21);
console.log(Garfield.getName() +" : "+ Garfield.getAge());//Garfield : 21
console.log(Garfield.colors);//black

这是最常用的继承方式。有些书叫这种为类式继承,把这种通过构造函数方式来实现继承的叫做类式继承,上面的我们可以把Animal看成一个类,通过构造函数原型链之间的关系实现继承。

原型继承

这种没有方式类的概念,也就是没有使用构造函数来实现,就是使一个函数的原型指向一个原有的对象,通过这个函数来创建一个新的对象。上例子


function create(obj){
function F(){};
F.prototype = obj;
return new F();
}
var Animal = {
name : "无名氏",
colors : ["black"]
}
var Tom = create(Animal);
console.log(Tom.name);//"无名氏"

这就是原型继承,可以看出它存在不少问题,只有在特定的情况下可以使用该方式,无法判断类与实例之间的关系,共享引用类型属性的问题等等。

寄生式继承

如果知道了上面的知识,这个很好理解了,我们在创建对象那章的时候,就提到了寄生构造对象,所谓的寄生就是在函数的内部通过某种方式来增强对象之后,在返回这个对象,那么寄生式继承也类似


function create(obj){
function F(){};
F.prototype = obj;
return new F();
}
var Animal = {
name : "无名氏",
colors : ["black"]
}
function getCat(obj){
//创建对象继承自obj
var newObj= create(obj);
//增加方法
newObj.getName = function(){
return this.name;
};
return newObj;
}
var Tom = getCat(Animal);
console.log(Tom.getName());//"无名氏"

这个看看就知道是怎么回事了,在函数内部继承一个对象之后,又增加了方法,之后返回这个对象。

寄生组合式继承

组合继承上面我们说完了,组合继承还有一个问题就是,任何时候会调用两次父类的构造函数,一次是创建子类的原型的时候,另一次是在子类的构造函数内部。看看就知道了


//父类
function Animal(name){
this.name = name;
this.colors = ["black"];
}
//父类原型
Animal.prototype.getName = function(){
return this.name;
}
//子类
function Cat(name,age){//传入参数
Animal.call(this,name);//第二次调用Animal()
this.age = age;
}
Cat.prototype = new Animal("无名氏");//第一次调用Animal()
Cat.prototype.constructor = Cat;
Cat.prototype.getAge = function(){
return this.age;
}
var Tom = new Cat("Tom",20);
console.log(Tom.getName() +" : "+ Tom.getAge());//Tom : 20

我们分析一下这个过程:第一次调用的时候,在Cat.prototype对象上添加了name和colors属性,添加到了子类的原型对象上,第二次调用父类的构造函数时,是将name和colors属性添加到了子类的实例上,也就是说子类的原型对象和实例中都有了这两个属性,实例中的属性屏蔽了原型中属性。

我们想一下怎样才能解决这问题呢?我们可以这样,让子类的原型对象直接指向父类的原型对象,就像文章开始我们说的那么选择的问题,我们这次使用父类的原型对象,这里可以使用,是因为我们结合使用了借用构造模式,可以继承父类构造函数中的属性了,看看例子先


//父类
function Animal(name){
this.name = name;
this.colors = ["black"];
}
//父类原型
Animal.prototype.getName = function(){
return this.name;
}
//子类
function Cat(name,age){//传入参数
Animal.call(this,name);//第二次调用Animal()
this.age = age;
}
Cat.prototype = Animal.prototype;//指向父类的原型
Cat.prototype.constructor = Cat;
Cat.prototype.getAge = function(){
return this.age;
}
var Tom = new Cat("Tom",20);
console.log(Tom.getName() +" : "+ Tom.getAge());//Tom : 20

这样是可以的,但是我们这里就有问题了,我们在给子类的原型指定constructor属性时,修改了父类的constructor属性,


console.log(Animal.prototype.constructor);
/*
function Cat(name,age){//传入参数
Animal.call(this,name);//第二次调用Animal()
this.age = age;
}
*/

所以我们不能直接这样指向父类的原型,要通过一种中转,使子类的原型和父类原型指向不同的对象,就是使用原型模式继承,建一个对象,这个对象的原型指向父类的原型,之后子类的原型对象再指向这个对象,这样就使子类的原型和父类原型指向不同的对象。


//父类
function Animal(name){
this.name = name;
this.colors = ["black"];
}
//父类原型
Animal.prototype.getName = function(){
return this.name;
}
//子类
function Cat(name,age){//传入参数
Animal.call(this,name);//第二次调用Animal()
this.age = age;
}
function F(){};
F.prototype = Animal.prototype;
Cat.prototype = new F();
Cat.prototype.constructor = Cat;
/*封装起来就是这样
function create(obj){
function F(){};
F.prototype = obj;
return new F();
}
function inheirt(sub,sup){
var pro = create(sup.prototype);
pro.constructor = sub;
sub.prototype = pro;
}
inherit(Cat,Animal);
*/
Cat.prototype.getAge = function(){
return this.age;
}
var Tom = new Cat("Tom",20);
console.log(Tom.getName() +" : "+ Tom.getAge());//Tom : 20

就这样循序渐进,我们就完成了javascript的继承的内容。

参元类(复制继承)

复制继承,顾名思义就是一个一个复制原型对象的属性,将给定的类的原型的属性循环复制到指定的原型中,


function inherit(subClass,supClass){
for(var name in supClass.prototype){
if(!subClass.prototype[name]){
subClass.prototype[name] = supClass.prototype[name]
}
}
}

function Animal(){}
Animal.prototype.aname = "无名氏";
function Cat(){};
inherit(Cat,Animal);
var Tom = new Cat();
console.log(Tom.aname);//无名氏

就是复制继承,参元类就是通过这种方式来实现的,参元类是包含了一系列的通用方法,如果哪个类想用这些方法就适使用这种方式来继承参元类。

小结

就这样循序渐进,我们就完成了javascript的继承的内容,继承这块的知识初学者要多看书,《javascript高级程序设计》的继承部分,多看几遍,自己好好想想它们的优缺点,就知道该如何设计继承了,自己在谢谢实例就会明白这些方式是大神们怎么想出来的。

【javascript基础】7、继承,布布扣,bubuko.com

时间: 2024-10-12 20:17:43

【javascript基础】7、继承的相关文章

JavaScript基础--面向对象三大特性(八):继承封装多态

一.构造函数基本用法:function 类名(参数列表){属性=参数值} 1 function Person(name,age){ 2 this.name = name; 3 this.age = age; 4 } 5 6 //创建Person对象的时候,可以直接给名字和年龄 7 var p1 = new Person('abc',80); 8 window.alert(p1.name); 9 var p2 = new Person('hello',9); 10 window.alert(p2.

javascript基础集锦_Json——原型继承(十)

在传统的基于Class的语言如Java.C++中,继承的本质是扩展一个已有的Class,并生成新的Subclass. 由于这类语言严格区分类和实例,继承实际上是类型的扩展.但是,JavaScript由于采用原型继承,我们无法直接扩展一个Class,因为根本不存在Class这种类型. 但是办法还是有的.我们先回顾Student构造函数: function Student(props) { this.name = props.name || 'Unnamed'; } Student.prototyp

JavaScript之基础-16 JavaScript 原型与继承

一.JavaScript 原型 原型的概念 - 在JavaScript中,函数本身也是一个包含了方法和属性的对象 - 每个函数中都有一个prototype属性,该属性引用的就是原型对象 - 原型对象是保存共享属性值和共享方法的对象 为对象扩展属性 - 扩展单个对象的成员 - 扩展共享的属性值 - 内存图描述 删除属性 - 可以使用delete关键字删除对象的属性 自由属性与原型属性 - 自有属性:通过对象的引用添加的属性;其它对象可能无此属性;即使有,也是彼此独立的属性 emp1.job = '

javascript基础语法——词法结构

× 目录 [1]java [2]定义 [3]大小写[4]保留字[5]注释[6]空白[7]分号 前面的话 javascript是一门简单的语言,也是一门复杂的语言.说它简单,是因为学会使用它只需片刻功夫:而说它复杂,是因为要真正掌握它则需要数年时间.实际上,前端工程师很大程度上就是指javascript工程师.前端入门容易精通难,说的是前端,更指的是javascript.本文是javascript基础语法的第一篇——词法结构 与java关系 关于javascript有这样一个说法,java和jav

Javascript基础篇小结

Javascript基础篇小结 字数9973 阅读3975 评论7 喜欢28 转载请声明出处 博客原文 随手翻阅以前的学习笔记,顺便整理一下放在这里,方便自己复习,也希望你有也有帮助吧 第一课时 入门基础 知识点: 操作系统就是个应用程序 只要是应用程序都要占用物理内存 浏览器本身也是一个应用程序 浏览器本身只懂得解析HTML 调用浏览器这个应用程序的一个功能绘制 1.javascript介绍 JavaScript操作DOM的本质是=获取+触发+改变 目的:就是用来操作内存中的DOM节点 修改D

JavaScript的原型继承

JavaScript是一门面向对象的语言.在JavaScript中有一句很经典的话,万物皆对象.既然是面向对象的,那就有面向对象的三大特征:封装.继承.多态.这里讲的是JavaScript的继承,其他两个容后再讲. JavaScript的继承和C++的继承不大一样,C++的继承是基于类的,而JavaScript的继承是基于原型的. 现在问题来了. 原型是什么? 原型我们可以参照C++里的类,同样的保存了对象的属性和方法.例如我们写一个简单的对象 function Animal(name) { t

一个简单的、面向对象的javascript基础框架

如果以后公司再能让我独立做一套新的完整系统,那么我肯定会为这个系统再写一个前端框架,那么我到底该如何写这个框架呢? 在我以前的博客里我给大家展示了一个我自己写的框架,由于当时时间很紧张,做之前几乎没有完整的思考过我到底该如何去写这个框架,所以事后对于这个框架我有很多遗憾之处,当我重构过一次代码后我就没再做过任何重构操作的工作,因为我根本不想再去给它修修补补了,之所以有这个想法,就是我对我写的那个框架的基础架构不满意. 为什么不满意这个基础架构了?我们先来看看我当时封装框架的方式: (functi

【javaScript基础】异常处理

理解异常在javaScript面向对象编程是非常重要的,异常是一种非常强大的处理错误的方式. 错误处理 首先我们来看一个有问题的代码: nonexistant(); 在以上这个例子中,访问一个不存在的变量,在这种情况下,程序会怎么处理?很早以前的处理方式就是程序直接崩溃死掉,所以我们不能容忍这种处理方式,需要有办法来处理. 最简单的处理方式是先检查,像这样: if (window.func) { func(); } 上面这种处理方式,仍然可能还会出现错误.因为window.func可能不是一个函

【javascript基础】4、原型与原型链

原文:[javascript基础]4.原型与原型链 前言 荒废了好几天,在宿舍闷了几天了,一直想着回家放松,什么也没搞,论文就让老师催吧.不过,闲的没事干的感觉真是不好,还是看看书,写写博客吧,今天和大家说说函数的原型. 原型是什么 第一次看到这个的时候,没太理解这个概念,其实也就是一个概念呗,没啥神秘的.书上说每个函数都有一个prototype属性(原型属性),这个属性是一个指针,指向一个对象(原型对象),这个对象包含这个函数创建的实例的共享属性和方法.也就是说原型对象中的属性和方法是所有实例