Javascript之对象组合继承

感悟:

最近看了一些关于Javascript对象继承的知识,发现自己之前虽然看了一些书,但是很多知识都忘了。虽然很多东西都忘了,但再次看的过程中对这些东西不会再向刚接触时那么陌生,而且理解起来也比之前顺畅和透彻多了。

充分说明:多看书是有意义的。

————————————————————————————————————————————————————————————————————————————————————————————碎碎念

关于对象之间的继承,在Javascript中主要是通过原型对象链来实现的,这一点与java这种基于类的面向对象语言有明显的不同,Javascript是基于原型的面向对象语言(大部分人说是基于对象的语言两种说法的观点不同)。

下面来具体说一下继承的实现方式:

一、组合继承

1.组合继承将原型链和借用构造函数技术组合在一起。通过使用apply或者是call借用构造函数,借用对象可以得到被借用对象的实例属性。首先来看一个栗子:

function People(){
    this.species = "人类";
}

People.prototype.nationality = "中国";
People.prototype.showSpecies = function(){
    return this.species;
}

function Person(name, sex){
    People.apply(this,arguments);    //此处是 Person 的实例属性,当然也可以添加一些实例方法
    this.name = name;
    this.sex = sex;
}

var person1 = new Person("二狗","男");
alert(person1.species);//人类
alert(person1.nationality);//undefined
alert(person1.showSpecies());//Uncaught TypeError: person1.showSpecies is not a function

通过结果可以看到,person1 是构造函数 Person 的一个实例,因为构造函数 Person 使用了借用构造函数技术

 People.apply(this,arguments);

Person 就可以获得 People 的实例属性 species;但是 Person 无法获得 People 的原型属性:nationality 和原型方法:showSpecies();

如果想让 Person 获得 People 的原型属性和原型方法,需要让 Person 获得 People 的原型对象(隐式)上的属性和方法。

本质上讲:就是要重写 Person 的原型对象。

a. 一种简单实用的做法是直接将 People 的原型属性和原型方法复制给 Person:(也称之为拷贝继承)

function People(){
    this.species = "人类";
}
People.prototype.nationality = "中国";
People.prototype.showSpecies = function(){
    return this.species;
}

function Person(name, sex){
    People.apply(this,arguments);
    this.name = name;
    this.sex = sex;
}
//遍历并复制
for(var i in People.prototype){
    Person.prototype[i] = People.prototype[i];
}

var person1 = new Person("二狗","男");
alert(person1.species);//人类
alert(person1.nationality);中国
alert(person1.showSpecies());人类

注意:上面这种复制是将 People.prototype 复制给了 Person.prototype。Person 拥有了和 People 一样的隐式原型对象。通过对原型对象上的数组进行操作可以证实:

function People(){
    this.species = "人类";
}

People.prototype.colorArray = ["red", "blue"];

function Person(name, sex){
    People.apply(this,arguments);
    this.name = name;
    this.sex = sex;
}

for(var i in People.prototype){
    Person.prototype[i] = People.prototype[i];
}

var person1 = new Person("二狗","男");
alert(person1.colorArray.push("green"));//3

var person2 = new People("二毛","男");
alert(person2.colorArray.push("black"));//4

两个不同构造函数实例化得到的对象,他们操作原型对象上的数组是同一个,说明这种原型对象上的原型属性和原型方法的复制是遵循一般的 Javascript 复制规则的。

可以对这个隐式的原型对象做一些别的操作,比如:

修改隐式对象对某个已引用方法:

function People(){
    this.species = "人类";
}
People.prototype.nationality = "中国";
People.prototype.showSpecies = function(){
    return this.species;
};
People.prototype.cheers = function(){
    return "中国加油!";
};

function Person(name, sex){
    People.apply(this,arguments);
    this.name = name;
    this.sex = sex;
}

for(var i in People.prototype){
    Person.prototype[i] = People.prototype[i];
}

Person.prototype.cheers = function(){
    return "中国必胜!";
};

var person1 = new Person("二狗","男");
alert(person1.species);//人类
alert(person1.nationality);//中国
alert(person1.showSpecies());//人类
alert(person1.cheers());//中国必胜!

var person2 = new People("二毛","男");
alert(person2.cheers());//中国加油!

可以看到:person1 利用修改构造函数对应的原型对象中的方法,引用了一个新的方法。也可以向下面的样子先引用一个方法,再通过继承覆盖之前的引用,当然,这样做没什么意义:

function People(){
    this.species = "人类";
}
People.prototype.nationality = "中国";
People.prototype.showSpecies = function(){
    return this.species;
};
People.prototype.cheers = function(){
    return "中国加油!";
};

function Person(name, sex){
    People.apply(this,arguments);
    this.name = name;
    this.sex = sex;
}

Person.prototype.cheers = function(){
    return "中国必胜!";
};

for(var i in People.prototype){
    Person.prototype[i] = People.prototype[i];
}

var person1 = new Person("二狗","男");
alert(person1.species);//人类
alert(person1.nationality);//中国
alert(person1.showSpecies());//人类
alert(person1.cheers());//中国加油!

var person2 = new People("二毛","男");
alert(person2.cheers());//中国加油!

b. 将 People 的实例赋值给 Person 的原型对象,同时修改 Person 原型对象的 constructor 属性值为自身。

如果只是单单将 People 的实例赋值给 Person 的原型对象, 而不修改 Person 原型对象的 constructor 属性会怎么样?

来看看修改 Person 原型对象前后 Person 构造函数的 Person.prototype.constructor 属性:

function People(){
    this.species = "人类";
}

function Person(name, sex){
    People.apply(this,arguments);
    this.name = name;
    this.sex = sex;
}
alert(Person.prototype.constructor);//Person这个完整的函数

Person.prototype = new People();
alert(Person.prototype.constructor);//People这个完整的函数

可以看到在将 People 实例赋值给 Person 前后,Person 的构造函数变了,如果这个时候什么都不做,那么再对 Person 实例化:

function People(){
    this.species = "人类";
}

function Person(name, sex){
    People.apply(this,arguments);
    this.name = name;
    this.sex = sex;
}
alert(Person.prototype.constructor);//Person这个完整的函数

Person.prototype = new People();
alert(Person.prototype.constructor);//People这个完整的函数
var person1 = new Person("二狗","男");
alert(person1.constructor);//People这个完整的函数

会发现构造函数 Person 的实例: person1 的构造函数居然不是 Person 而是 People,这显然不对。

也就是说构造函数 People 在将其实例赋值给 Person 的原型对象时,同时也将 Person 的原型对象的属性 constructor 也更换了(很明显,因为 constructor 是 prototype 的属性,皮之不存,毛将焉附?),

而 prototype.constructor 的值表示由当前构造函数对象 实例化的 函数对象的构造函数(简单点说 Person.prototype.constructor 就是告诉构造函数对象 Person 的实例,他们是谁构造的,

而 person1.constructor 让作为实例对象的 person1 直指它自己的构造函数)。——可以看出,正常情况下 Person.prototype.constructor === person1.constructor 应该成立。

function People(){
	this.species = "人类";
}

function Person(name, sex){
	People.apply(this,arguments);
	this.name = name;
	this.sex = sex;
}
alert(Person.prototype.constructor);//Person这个完整的函数

Person.prototype = new People();
Person.prototype.constructor = Person;

var person1 = new Person("二狗","男");

alert(person1.constructor === Person.prototype.constructor);//true

通过上面讲到的组合继承的两种实现方式,虽然能够实现属性和方法、原型属性和原型方法的继承,但是存在一个不足:无论在什么情况下,被继承的函数对象都会被调用两次。

先来看一个有趣的地方,假如:在组合继承中不使用 借用构造函数技术 而直接重写原型对象,会发生什么?

function People(){
    this.species = "人类";
    this.arr = [1];
}
People.prototype.nationality = "中国";
People.prototype.showSpecies = function(){
    return this.species;
};

function Person(name, sex){
    // People.apply(this,arguments);
    this.name = name;
    this.sex = sex;
}

Person.prototype = new People();//让People的实例属性变成Person的原型属性
Person.prototype.constructor = Person;

var person1 = new Person("二狗","男");

person1.arr.push(2);//1,2
alert(person1.species);//人类
alert(person1.arr);
alert(person1.nationality);//中国
alert(person1.showSpecies());//人类

var person2 = new People("二毛","男");
person2.arr.push(3);
alert(person2.arr);//1,3
alert(person1.arr);//1,2

这样看来在构造函数 People 和 构造函数 Person 的实例对象中都能正常使用 People 的实例属性。如果是单个构造函数 Person 的多个 实例对象呢?

function People(){
    this.species = "人类";
    this.arr = [1];
}
People.prototype.nationality = "中国";
People.prototype.showSpecies = function(){
    return this.species;
};

function Person(name, sex){
    // People.apply(this,arguments);
    this.name = name;
    this.sex = sex;
}

Person.prototype = new People();//让People的实例属性变成Person的原型属性
Person.prototype.constructor = Person;

var person1 = new Person("二狗","男");
var person3 = new Person("三狗","男");

person1.arr.push(2);
alert(person1.arr);//1,2

person3.arr.push(4);
alert(person3.arr);//1,2,4
alert(person1.arr);//1,2,4

var person2 = new People("二毛","男");
person2.arr.push(3);
alert(person2.arr);//1,3
alert(person1.arr);//1,2,4
var person4 = new People("四毛","男");
person4.arr.push(5);
alert(person4.arr);//1,5
alert(person2.arr);//1,3

注意看构造函数 People 和构造函数 Person 的实例通过相同操作后结果的差异。

构造函数 People 的实例会各自拥有自己的数组 arr, 各自的操作间是不会相互影响的,但是构造函数 Person 的实例都拥有相同的数组 arr 引用,他们操作的是同一个数组。那么实例化得到的不同对象无法正常使用各自的 arr。

所以在组合继承中,借用构造函数和原型链缺一不可。

这里的 person1.__proto__ 是帮助我们拿到 person1 的构造函数 Person 的原型对象。可以看到,上面直接将 People 的实例属性转化为 Person 的原型属性,而 People 的原型属性和原型方法也成为  Person 的原型属性和原型方法。

但是,组合继承也存在缺点,最明显的就是:无论在什么情况下,只要实例化了使用组合继承后的对象,被继承的对象都会调用两次。

function People(){
    this.species = "人类";
}
People.prototype.nationality = "中国";
People.prototype.showSpecies = function(){
    return this.species;
};

function Person(name, sex){
    People.apply(this,arguments);//第二次调用People()
    this.name = name;
    this.sex = sex;
}

Person.prototype = new People();//第一次调用People()
Person.prototype.constructor = Person;

var person1 = new Person("二狗","男");

第一次发生在将 People 的实例对象潜复制给 Person 的原型对象(可以是直接复制,也可以是通过 People 的实例 new People() );

第二次发生在调用构造函数 Person 时。

在第一次调用的时候,其实 Person 就已经拿到了 People 的实例化属性 species,而第二次调用发生在实例化 Person 的时候,通过借用构造函数,Person 又一次的调用了 People,会在 Person1 上创建实例属性 species。

总结:组合继承通过借用构造函数技术和重写原型对象,可以让一个对象继承另一个对象的属性和方法、原型属性和原型方法。在组合继承中借用构造函数技术和重写原型对象都是必要的,缺少了任何一个都会使继承不能正常工作。

组合继承也存在缺点,主要是被继承的对象的实例属性的重复调用。

时间: 2024-10-19 18:20:43

Javascript之对象组合继承的相关文章

3. 闭包_对象组合继承模式_事件轮询机制

1.谈谈闭包? (产生条件.是什么.在哪里.作用.生命周期.缺点) 产生闭包的三个条件: 函数嵌套 内部函数引用外部函数的局部变量 执行外部函数 包含被引用的局部变量的一个"对象",  通过 chrome 开发者工具可以调试查看到,就是 closure,它存在嵌套的内部函数中 作用: 延长了局部变量的存活时间, 让函数外部可以操作(读写)到函数内部的数据(变量/函数) 闭包的生命周期: 产生 :  在嵌套内部函数定义执行完时就产生了(不是在调用) 死亡 :  在嵌套的内部函数成为垃圾对

Javascript之对象的继承

继承是面向对象语言一个非常重要的部分.许多OOP语言都支持接口继承和实现继承两种方式.接口继承:继承方法签名:实现继承:继承实际的方法.在ECMAScript中函数是没有签名的,所以也就无法实现接口继承,只能支持实现继承. 在JavaScript中有大概六种继承方式,它们分别是:原型链继承,借用构造函数继承,组合继承,原型式继承,寄生式继承和寄生组合式继承.下面就是对着六种继承方式的详细介绍. 1.原型链 基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法.在这里还得补充一下,每个构

JavaScript的对象和继承

本文记录一种JavaScript的对象定义和继承的书写方式,也是目前使用比较普遍的一种. 1.定义对象(混合的构造函数和原型方式) // 属性在构造函数里定义 function Person(name, age, sex) { this.name = name; this.age = age; this.sex = sex; } // 方法在原型里定义 Person.prototype.hello = function() { alert("Hello, my name is " +

JavaScript教程——对象的继承

面向对象编程很重要的一个方面,就是对象的继承.A 对象通过继承 B 对象,就能直接拥有 B 对象的所有属性和方法.这对于代码的复用是非常有用的. 大部分面向对象的编程语言,都是通过“类”(class)实现对象的继承.传统上,JavaScript 语言的继承不通过 class,而是通过“原型对象”(prototype)实现,本章介绍 JavaScript 的原型链继承. ES6 引入了 class 语法,基于 class 的继承不在这个教程介绍,请参阅<ES6 标准入门>一书的相关章节. 原型对

javascript实现对象的继承的方式

在JavaScript将原型链作为实现继承的主要方法.基本原理是利用原型让一个subType引用superType的属性和方法 推荐链接 http://www.jb51.net/article/20431.htm http://zhidao.baidu.com/link?url=6gOYMdFgQlotkHu5-B7Lp-CDjd0BwfKoIcQZzNQtoW4u9UMVvRZVaEBAETt0zU_eo652JhR58CQHvQp5JbOHFa http://www.cnblogs.com/

对象(一)--对象的继承

聊一聊JavaScript中的对象的继承 吐槽 时间过得是真的快,感觉才更新博客怎么就快一个礼拜了...这两天看了点python和flask框架了解了下,最后还是打算去系统地学习下node,又看了MongoDB,之后觉得Linux挺有意思的又去找资料学习(选发行版本,装虚拟机...), 感觉把时间都折腾在这儿了,有点不务正业(想了想还是日后抽时间学习吧,现在还是把前端知识巩固好,毕竟目前是个连实习都找不到的渣渣... 不过node还是要学的,计划之后手撸一个个人博客练练手... 前言 在前一篇对

Javascript对象的继承

对象的继承 Javascript主要通过原型链实现继承,原型链的构建是通过将一个类型的实例赋值给另一个构造函数的原型实现的. 由于所有的实例对象共享同一个prototype对象,那么从外界看起来,prototype对象就好像是实例对象的原型,而实例对象则好像"继承"了prototype对象一样.这就是Javascript继承机制的设计思想. 继承方法: (1)原型链继承方法:基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法.继承是通过创建超类型的实例,并将该实例赋给子类型

javascript之对象(二)&amp;&amp; 继承问题

JavaScript的继承本质上是通过原型链来实现的,主要的模式有如下  1 原型链模式 //思想是根据每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型的内部指针. //问题是在于所有的实例都会共享同一份父类对象,因为所有子类的prototype只有一份,而也只有这一份去实例化了一个父类对象.也正是因为如此,造成子类不能给超类对象传参,因为这样会直接修改了这一份父类的实例. function SuperType() { this.property =

javascript中的对象之间继承关系

相信每个学习过其他语言的同学再去学习JavaScript时就会感觉到诸多的不适应,这真是一个颠覆我们以前的编程思想的一门语言,先不要说它的各种数据类型以及表达式的不同了,最让我们头疼,恐怕就是面向对象的部分了,在JavaScript中,是没有给定一个创建对象的关键词的,它不像Java中一个class就可以创建一个对象,在JavaScript中,对象是一个十分松散的的key-value对的组合,通常,我们在创建对象时,可以通过{}来直接生成一个对象,就像我们之前所学的,对象中有属性,有行为,这里我