javaScript的面向对象与继承

343 阅读5分钟

js面向对象概述

"javaScript 没有提供传统面向对象语言中的类式继承,而是通过原型委托的方式来实现对象与对象之间的继承",这句话是摘自《javaScript设计模式与开发实践》书中第一章的一句话,这句话开门见山的说明了js的编程模式“原型”,而这样也直接说明了javaScript的面向对象是基于原型克隆的方式来创建对象,并以原型链的方式实现对象与对象之间的关系。

那我们先来了解一下这js这种基于原型编程语言的基本规则:

  • 一. 所有的数据都是对象,
  • 二. 要得到一个对象,不是通过实例化类,而是找到一个对象作为原型并克隆它,
  • 三. 对象会记住它的原型,
  • 四. 如果对象无法响应某个请求,它会把这个请求委托给它自己的原型。

上面这几句话,我同样是摘自《javaScript设计模式与开发实践》里面的,原模原样抄录下来分享给大家的。

js继承

1. 原型链继承

实现方式:新对象的实例的原型等于父对象类的实例。 `

let Animals = function (type, name) {   // 定义父类
	this.type = type;
	this.name = name;
    }
    Animals.prototype.animal = '动物';
    Animals.prototype.eat = function () {
		console.log('这是一只小'+ this.type + ': 它会吃东西')
    }			   
		   
let Cat = function (sex) {  // 定义子类
	this.sex = sex
    }
		   
Cat.prototype = new Animals('猫', '小花');  // 原型链继承
	let cat = new Cat('公') // 生成子类实例
	cat.eat()   // 执行继承来的方法

` 原型链继承的特点是:可继承父类的私有属性和原型上的属性和方法,但是,父类私有上的属性方法它们之间的继承是属于引用同一个内存地址,因此修改其中的一个,也会影响到另一个对象

2. 构造函数继承

实现方式:在子类的函数体里执行父类,通过call和apply来改变this的指向。

`

let Dog = function (sex) {
	Animals.call(this, '狗', '旺财');   // 构造继承:执行父类,通过call改变this指向
	this.sex = sex;
	}
let dog = new Dog('母');
dog.eat();  // 报错:Uncaught TypeError: dog.eat is not a function;

`

构造函数继承: 可继承父类里的私有属性和方法,但是不能继承父类原型上的方法和属性,此类继承修改一个对象的属性不会影响到另一个对象

3. 组合式继承

实现方式:该方式的继承其实就是上面的原型继承和构造函数继承的混合方式。

`

let Dog = function (sex) {
	Animals.call(this, '狗', '旺财');   // 构造继承: 执行父类,通过call改变this指向
	this.sex = sex;
	}
Dog.prototype = new Animals('猫', '小花');  // 原型链继承
let dog = new Dog('母');
dog.eat();  // 执行父类原型上的方法

` 组合式继承:可继承父类私有的属性和方法,继承的私有属性和方法都是子类私有的,可以继承父类原型上的属性,可以传参,可复用。但是组合式继承调用了两次父类方法,因此在性能上有一定的损耗

4. 包装式继承

实现方式: 通过一个包装函数,把父类实例作为参数传递进去,子类实例在包装函数体里生成。

`

function context (obj) {    // 定义包装函数,并传递父类实例
		function Pig () {
		   	this.sex = '公'		
		}
		Pig.prototype = obj;    // 继承父类实例
		return new Pig()    // 返回子类实例
	}
		   
    let animals = new Animals('猪', '小胖')
	let pig = context(animals);
    pig.type = '羊';  //这样写等于给子类添加一个新的type属性
	pig.eat()    // 输出:这是一只小羊: 它会吃东西
	animals.eat()    // 输出: 这是一只小猪: 它会吃东西

` 包装式继承:类似于函数闭包的用法,语义上不够明显。

5. 组合寄生式继承

实现方式: 通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点。

`

    function context (obj) {
		function Fn () {
		   this.sex = '公'
		}
		Fn.prototype = obj; // 函数的原型等于另一个实例
		   return new Fn()
	}
		   
    let objA = context(Animals.prototype);
		   
    function Cattle () {
		Animals.call(this, '牛', '小蛮'); //在函数中用apply或者call引入另一个构造函数
	}
		   
    Cattle.prototype = objA;
		   
	objA.constructor = Cattle;   // 修复实例
		   
    let cattle = new Cattle();
		   
	console.log(cattle)

`

组合寄生式继承:1、函数的原型等于另一个实例。2、在函数中用apply或者call引入另一个构造函数。继承方式太过复杂。

6. es6继承 (使用最多)

实现方式:使用class关键字声明类,通过extends关键字实现继承。

`

class Animals {
	constructor(type, name) {
		this.type = type;
		this.name = name;
	}			
	eat () {
		console.log('这是一只小'+ this.type + ': 它会吃东西')
			}
		}
		
    let animal = new Animals('狗', '旺财');
		
	class Car extends Animals {
		constructor(sex) {
		super('猫', '小花');   
		// 此处是重点在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则
		// 会报错。这是因为子类实例的构建,基于父类实例,只有super方法才能调用父类实例
				this.sex = sex;
			}
		}			
	let car = new Car('公')
	car.eat()

` es6继承:使用extends关键字实现继承,语法上更加清晰明了,子类继承父类后,子类的构造函数,必须执行super方法

new关键字

在上面的继承演示中,不管是es5还是es6,js创建一个类都必须使用new关键字去执行。 `

let Animals = function (type, name) {   // 定义父类
	this.type = type;
	this.name = name;
    }
    Animals.prototype.animal = '动物';
    Animals.prototype.eat = function () {
		console.log('这是一只小'+ this.type + ': 它会吃东西')
    }

`

在这里面,new关键字主要做了如下工作:

  • 一:创建了一个对象obj;
  • 二:将这个obj对象的__proto__成员指向了Animals函数的prototype;
  • 三:将Animals函数对象的this指针替换成obj;
  • 四:将obj对象返回出来,也就是我们的实例;

总结

javaScript的面向对象与继承,继承的核心思想是复用,不需要在去编写多余代码,还有就是代码的管理。喜欢的朋友给个赞吧,谢谢。