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