您的位置:首页 > Web前端 > JavaScript

原生JavaScript中的原型链和继承(代码实例详解)

2017-12-28 10:39 1016 查看
       本文通过例子将原生JavaScript中的原型链和继承进行介绍,在学习时可以将代码运行下,查看下控制台相关的输出;在写代码的同时添加了很多注释,方便理解和代码分块。实例代码如下:
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<script type="text/javascript">
// ------
// 0.原型链
// ------
// 基本模式
function SuperType(){
this.property = true;
};

SuperType.prototype.getSuperValue = function(){
return this.property;
};

function SubType (){
this.subproperty = false;
};

//继承SuperType
SubType.prototype = new SuperType;

SubType.prototype.getSubValue = function(){
return this.subproperty;
};

var instance = new SubType();
console.log(instance.getSuperValue);

// 以上继承的实现是通过创建SuperType的实例,并将该实例赋值给SubTypr.prototype实现的。
// 本质是重写了原型对象,代之以一个新类型的实例。换句话说,原来属于SuperType的实例中的属性和方法,现在也存在于SubType.prototype中了。
// 确认继承关系后又给SubType.prototype添加了新的方法。
// 实例以及构造函数和原型之间的关系如下:
// -------------------------------------
// -------------------------------------
// SuperType
// prototype @SuperType.prototype
// -------------------------------------
// SuperType.prototype
// constructor @SuperType
// getSuperValue (function)
// -------------------------------------
// SubType
// prototype @SubType.prototype
// -------------------------------------
// SubType.prototype
// [[Prototype]] @SuperType.prototype
// prototype true
// getSubValue (function)
// -------------------------------------
// instance
// [[Prototype]] @SubType.prototype
// subproperty false
// -------------------------------------
// -------------------------------------
// 以上关系图中,SubType的默认原型换成了一个新的原型--SuperTyrpe的实例。于是,新原型不仅具有了SuperType实例的全部属性和方法,还内部还有一个新指针,指向
// 了SuperType的原型。
// 此外还需要注意的是instance.constructor现在指向的是SuperType,这是由于SubType.prototype被重写,该原型指向了另一个对象——SuperType的原型,而该原型对象
// 的constructor属性指向了SuperType

// 0.1
// 不要忘记默认的原型
// -------------------------------------
// -------------------------------------
// Object
// prototype @Object.prototype
// -------------------------------------
// Object.prototype
// constructor @Object
// hasOwnProperty (function)
// toString (function)
// valueOf (function)
// -------------------------------------
// SuperType
// prototype @SuperType.prototype
// -------------------------------------
// SuperType.prototype
// [[Prototype]] @Object.prototype
// constructor @SuperType
// getSuperValue (function)
// -------------------------------------
// SubType
// prototype @SubType.prototype
// -------------------------------------
// SubType.prototype
// [[Prototype]] @SuperType.prototype
// prototype true
// getSubValue (function)
// -------------------------------------
// instance
// [[Prototype]] @SubType.prototype
// subproperty false
// -------------------------------------
// -------------------------------------
// 确定原型和实例的关系
// instance instanceOf Object; //true
// instance instanceOf SuperType; //true
// instance instanceOf SubType; //true
// Object.prototype.isPrototypeOf(instance); //true
// SuperType.prototype.isPrototypeOf(instance); //true
// SubType.prototype.isPrototypeOf(instance); //true

// 谨慎定义方法
// 子类型有时候需要覆盖超类中的方法或者添加超类中不存在的方法。不管怎样在给原型添加方法时一定放在替换原型的语句之后。

// 原型链的问题
// 在包含引用类型属性的继承中,通过原型继承时,原型会变成另一种类型的实例,于是原先的实例属性也会变成现在原型的属性。
// 此时,之前的引用类型的实例属性在新的实例包含,该属性会被所有子类实例共享。

// ------------
// 借用构造函数
// ------------
// 在子类型构造函数的内部调用超类型的构造函数。通过使用apply()和call()方法可以在新创建的对象上执行构造函数。
function SuperType1(){
this.colors = ['red','blue'];
};

function SubType1(){
//继承了SuperType1
SuperType1.call(this);
};

var ins1 = new SubType1();
ins1.colors.push('black');
console.log(ins1.colors); //'red','blue','black'
var ins2 = new SubType1();
console.log(ins2.colors); //'red','blue'
// 用call()借调了SuperType的构造函数,在创建SubType实例时调用了SuperType的构造函数,会执行所有超类的初始化代码,并且每个子类对象都有自己的副本。
// 同时,借用构造函数可以向超类传递参数
function SuperType2(name){
this.name = name;
};

function SubType2(){
//继承了SuperType1
SuperType2.call(this,'jum');
this.age = 18;
};

var ins3 = new SubType2();
console.log(ins3.name); //'red','blue','black'
console.log(ins3.age); //'red','blue'

// ------------
// 组合继承(伪经典继承)(最常用的继承方式)
// ------------
// 对原型链和借用构造函数的组合使用,融合了两者的优点,避开了各自的缺点
// 使用原型链对原型属性和方法的继承,使用借用构造函数实现对实例属性的继承。
// 组合式继承结构如下:
function SuperType3(name){
this.name = name;
this.colors = ['red','blue'];
};

SuperType3.prototype.sayName = function(){
console.log(this.name);
};

function SubType3(name, age){
//继承属性
SuperType3.call(this,name);
this.age = age;
};

//继承方法
SubType3.prototype = new SuperType3();
SubType3.prototype.constructor = SubType3;
SubType3.prototype.sayAge = function(){
console.log(this.age);
};

var sub1 = new SubType3('jum3',18);
sub1.colors.push('black');
console.log(sub1.colors);
sub1.sayName();
sub1.sayAge();

var sub12 = new SubType3('po',25);
console.log(sub12.colors);
sub12.sayName();
sub12.sayAge();

// --------------
// 原型式继承 by 克罗克福德
// --------------
// 此种方法没有严格意义上的构造函数。借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。
// 实现方法d原版如下:
function object(o){
function F(){};
F.prototype = o;
return new F();
};
// @1
var person = {
name: 'zhou',
friends: ['a','b']
};

var person1 = object(person); //@2
person1.name = 'zhoujum';
person1.friends.push('c');

var person2 = object(person); //@3
person2.name = 'zhoujumpo';
person2.friends.push('d');

console.log(person.friends);
console.log(person1.friends);
console.log(person2.friends);

// ECMAScript5在Object中增加了create()方法进行了规范,因此可以直接从上段代码 @1 处开始,
// create()方法有两个参数,一个用作新对象原型对象;另一个可选,为新对象定义额外属性的对象。
// 在传入一个参数时 @2 和 @3 处可以改写为 var person1 = Object.create(person); 和上段代码一样。
// 第二个参数和Object.defineProperties()方法的第二个参数格式相同,每个属性都可以通过自己的描述符定义。
var person3 = Object.create(person, {
name: {
value: 'name123'
}
});
console.log(person3.name);

// --------------
// 寄生式继承 by 克罗克福德
// --------------
// 寄生式继承与寄生式构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数
// 用途范围:在主要考虑对象而不是自定义类型和构造函数的情况下都适用于这种模式。
function createPerson4(o){
var clone = Object(o);
clone.sayHi = function(){
console.log("Hi, 寄生式继承");
};
return clone;
};
// -------------------------------------------
var person4 = {
name: 'person4',
friends: ['a', 'b']
};
var subPerson4 = createPerson4(person4);
subPerson4.sayHi();

// 使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率,这与构造函数模式类似。

// -------------------
// 寄生组合式继承
// -------------------
// 组合继承式JavaScript最常用的继承方式,但是最大的问题是无论什么情况下都会调用两次超类的
// 构造函数:一次是在创建子类型的原型的时候,另一次是在子类型构造函数内部,构造和原型组合继承如下:

function SuperType4(name){
this.name = name;
this.colors = ["red","blue"];
};

SuperType4.prototype.sayName = function(){
console.log(this.name);
};

function SubType4(name, age){

b03d
SuperType4.call(this, name); //第二次调用SuperType4

this.age = age;
};

SubType4.prototype = new SuperType4(); //第一次调用SuperType4
SubType4.prototype.sayAge = function(){
console.log(this.age);
};
// * 第一次调用时SubType4.prototype会得到SuperType4的两个实例属性name和colors,此时两个属性存在于
// SubType4的原型中。当调用SubType4的构造函数时,又会第二次调用SuperType4的构造函数,这次又在新对象
// 上创建了实例属性name和colors,于是这两个属性就屏蔽了原型中的两个同名属性。
// * 此时name和colors属性在SubType4的原型和实例中都存在,这就是调用两次SuperType4的结果。
// 解决这个问题可以使用寄生组合继承。

// 寄生组合继承,即借用构造函数来继承属性,通过原型链的混成形式来继承方法。
// 基本思路是:不必为了指定子类型的类原型而调用超类的构造函数,我们需要的不过是超类型原型的一个副本
// 而已。本质上就是使用寄生式继承来继承超类型的原型,然后将结果指定给子类型的原型。
// 基本模式如下:
function inheritPrototype(SubType4, SuperType4){
var prototype = object(SuperType4.prototype);//创建对象
prototype.constructor = SubType4;//增强对象
SubType4.prototype = prototype;//指定对象
};
// inheritPrototype()方法实现了最简单形式的寄生组合继承。借此可替代上方代码给子类型原型赋值的语句。
// 改正后上方代码中
// SubType4.prototype = new SuperType4(); //第一次调用SuperType4
// 换为
// inheritPrototype(SubType4, SuperType4);

// * 借用寄生组合继承的高效之处是对SuperType4()函数的一次调用;并且避免了在SubType4.prototype上创建
// 不必要的多余属性;与此同时,还保证了原型链的不变。

// YUI 的YAHOO.lang.extend()方法采用了寄生组合继承,从而让该模式首次出现在了一个应用非常广泛的
// JavaScript库中。

</script>
</body>
</html>THIS END
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息