JavaScript的继承方式(代码实例详解)

大部分人对原型链以及继承的概念上较为模糊,且网上对继承方式的叫法与解释不一,于是我对其进行了彻底的总结和理解:

首先,先理解构造函数,原型和实例之间的关系

每个构造函数(constructor)都有一个原型对象(prototype),原型对象都包含一个指向构造函数的指针,而实例(instance)都包含一个指向原型对象的内部指针.

其次,我们先了解一个规则

当访问一个对象的属性的时候,首先会在对象本身进行查找,如果找到就直接返回这个属性,如果找不到则继续在原型上找,也就是__proto__所指向的对象,直至找到object的原型对象。

这种搜索轨迹形似长链,因此我们称为原型链。

原型链的构建是通过将一个类型的实例赋值给另一个构造函数的原型实现的。

继承的几种方法:

1.构造函数继承
基本思想是在子类型的构造函数内部调用超类型构造函数。

function Person(name,age){
		//实例属性
	     this.name = name;
	     this.age = age;
	     //实例方法
	     this.showName = function() {
	         return this.name;
	     }
	     this.showAge = function() {
	         return this.age;
	     }
     }
     function Student() {
       Person.apply(this, arguments);
     }
     let boy = new Student('煜夏', 21);
     console.log(boy.showName());
}

构造函数继承解决了两大问题:
1.保证原型链中引用值的独立,不被所有实例所共享
2.创建子类实例时,可以向父类传递参数

但是,这种方式只能继承父类的实例属性和方法, 不能继承原型属性和方法;无法实现函数复用, 每个子类都有父类实例函数的副本, 影响性能,因此构造函数继承很少使用。

2.组合继承
组合继承是将原型链与构造函数继承结合,也叫伪经典继承,思路是使用原型链实现对原型属性和方法的继承,使用构造函数实现对实例属性的继承。既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。避免了原型链和构造函数继承的缺陷,融合了他们的优点。

 function Person(name){
            this.name = name;
        }
        Person.prototype.showName = function(){
            return this.name;
        }
      
		function Student(name,age){
            Person.call(this, name);//第二次调用
            this.age = age;
        }
        
        Student.prototype = new Person();//第一次调用
        Student.prototype.constructor = Student;//使用constructor指定Student原型的构造函数
        
       //Student.prototype = Object.create(Person.prototype)//方法2
       
        Student.prototype.showAge = function(){
            return this.age;
        }
        
        var boy = new Student('煜夏', 21);
        var girl = new Student('小米', 10);
		console.log(boy.showName());//"煜夏"
		console.log(boy.showAge());//“21”

		console.log(girl.showName());//"小米"
		console.log(girl.showAge());//“10”

在这个例子中,Person构造函数定义了一个属性:name。他的原型定义了两个方法showName()。Student构造函数调用Person构造函数时传入name参数,然后定义了自己的属性age。然后,将Person的实例作为Student的原型,并在其原型上定义了新的方法showAge()。这样的话,两个Student的实例就有了自己的属性,而且共享了方法。
在这里插入图片描述
在第二种写法中,我们通过Object.create创建了一个空的对象,将student.prototype赋给该空对象,该对象的__proto__指向参数,也就是Preson.prototype。请看下图:
在这里插入图片描述
这样的话,我们不仅可以实现继承,而且也不会因为改变了student而影响到person.prototype的内容。

我们发现组合继承调用了两次父类构造函数,生成两份实例,造成不必要的消耗,所以下面有更好的解决方式。

3.原型式继承

子类型的原型是父类型的一个实例对象

	 	var person= {
            name : "人类",
            needs :["car","house"]
        }
        
        var boy = Object.create(person);
        boy.name = "小明";
        boy.needs.push("book");
        
		var girl = Object.create(person);
		boy.name = "小米";
        boy.needs.push("piano");
		
        console.log(person.needs);

ECMAScript 5 通过新增 Object.create()方法规范化了原型式继承。这个方法接收两个参数:一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象(已该参数定义的属性会覆盖原型对象的同名属性)。

支持 Object.create()方法的浏览器有 IE9+、 Firefox 4+、 Safari 5+、 Opera 12+和 Chrome。

只想让一个对象与另一个对象保持类似的情况,而不需要构造函数,原型式继承刚刚合适。不过我们可以看出,包含引用类型值的属性始终都会共享相应的值,就像使用原型模式一样。

4.寄生式继承

寄生式继承的思路是,封装一个继承过程的函数,在函数内部增强对象,下面代码示范了寄生式继承:

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

在示例中,createAnother()接受了一个参数作为新对象基础的对象。然后将参数传入object(),返回的结果赋值给clone,接下来给clone添加了新的方法sayHi(),最后返回克隆对象。我们可以这样使用它:

var person = {
	name = "小米"
}

var girl = createAnother(person);
console.log(girl.name);//"小米"
girl.sayHi();//'hi'

我们可以看出,新对象girl,不仅具有person的所有属性和方法,而且还有属于自己的方法sayHi()。

寄生式继承的缺点和构造函数类似,无法做到函数复用,从而导致降低效率。

5.寄生组合式继承

最完美,最理想的继承方式

其基本模式如下:

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

该函数传入两个参数,一个为 子类型的构造函数,另一个为父类型的构造函数。第一步:创建一个父类原型的副本;第二步,给副本添加constructor属性,弥补因重写原型链而失去的默认的constructor属性,指向子类型的构造函数;第三部,将副本赋值给子类型的原型;如图:
在这里插入图片描述
我们可以这么用:

	function SuperType(name){
		this.name = name;
	}
	SuperType.prototype.sayName = function(){
		alert(this.name);
	};
	function SubType(name, age){
		SuperType.call(this, name);
		this.age = age;
	}
	inheritPrototype(SubType, SuperType);
	SubType.prototype.sayAge = function(){
		alert(this.age);
	};
	

其高效性体现在,只调用了一次superType,而且SubType.prototype上不在拥有多余的属性;且原型链保持不变,还能够正常使用instanceof 和 isPrototypeOf(),所以它是最完美的继承方式。

over!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值