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