JS继承的一些见解

# JS继承的一些见解 > js在es6之前的继承是五花八门的。而且要在项目中灵活运用面向对象写法也是有点别扭,更多的时候还是觉得面向过程的写法更为简单,效率也高。久而久之对js的继承每隔一段时间就会理解出现困难。所以这次我要把对对象的理解写下来,这样应该就深刻一点了。 我们先来看看一个对象是怎么生成的 ```javascript // 三种创建对象的方法 var obj = {} var obj2 = new Object() var obj3 = Object.create(null) // 创建一个空字符串对象 var obj4 = new String() obj.constructor === obj2.constructor // true obj.__proto__ === obj2.__proto__ === Object.prototype // true obj4.__proto__ === String.prototype // true obj4.__proto__.__proto__ === Object.prototype // ture ``` > 这三种方法,前面两种是一样的。它们创建的空对象都具有原型,而第三种方式创建的是一个真正意义上的空对象,它不继承任何属性;而obj4呢是一个字符串对象。看下图的对比: 下面对象除了obj3都有个__proto__的隐性属性,这个属性指向的是该创建该实例的构造函数的原型。 ![js对象图1](http://p0639a4mt.bkt.clouddn.com/1523691451000-012.jpg "js对象图1") 这里obj和obj2虽然是空对象,不过它们具有Object构造函数的属性的方法,而obj4除了有obj拥有的属性和方法,它还具备了String拥有的属性和方法;而obj3是真正的空对象,它的__proto__指向的是null。 ##### 我们再将obj4全部展开看看: ![js对象图2](http://p0639a4mt.bkt.clouddn.com/1523692671000-427.jpg "js对象图2") ##### 再来看看它们之间的关系: ![js对象图3](http://p0639a4mt.bkt.clouddn.com/1523695882000-212.png "js对象图3") > 如上图,我举得例子是Object和String这两个原生具有的构造器。它们的关系和我们平时写的父类和子类之间的关系是一样的,所有的类最终都会指向Object。 在es5的时代,我们用到的继承最简单的就是原型链继承了 ```javascript // 我们先创建一个父类 function Super (name) { this.name = name this.type = '我是父类' } Super.prototype.move = function () { console.log(this.name + '在跑') } // 子类继承父类 function Child (name) { this.name = name } Child.prototype = new Super() // 原型链继承,子类原型的私有方法和属性要在继承之后添加,不然会被覆盖 Child.prototype.constructor = Child // 修复对构造函数的指向 Child.prototype.eat = function () { // 做点啥 } ``` > 原型链继承就是将子类的原型等于父类的实例,然后再添加子类原型的属性和方法。这样的缺点就是只能继承一个父类。而且在创建子类的实例的时候,不能传参给父类。在做单一继承而且父类不需要传参的时候,这种写法是最好的选择了。 下面在介绍一种我个人觉得已经很不错的继承方式(组合继承) ```javascript // 子类继承父类 function Child (name) { Super.call(this) this.name = name } Child.prototype = new Super() Child.prototype.constructor = Child // 修复对构造函数的指向 Child.prototype.eat = function () { // 做点啥 } ``` >- 这种继承方式就是粗暴的将父类Super构造函数利用对象冒充的方式将父类的实例属性复制到子类里。在用原型继承的方式继承父类原型的属性和方法。 >- 这样的继承方式,你会发现子类的实例的属性和__proto__下都有父类的属性,而子类实例的把子类原型的覆盖了。 >- 这样的方式可以实现对多个父类(非原型部分)的继承。因为原型的继承是链式的,所以只能继承一个。 一般来说这种继承方式已经很好了,代码量少。继承性好,弊端也没有影响。这就是ES6之前的继承。下面再来看看ES6是怎么操作的。 ### ES6的class >- ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。 >- 基本上,ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。上面的代码用 ES6 的class改写,就是下面这样。 上面这两句话是引用阮一峰对ES6的class的简介(Class 的基本语法)。阮大神的ECMAScript 6 入门真的是对es6初学者居家必备的好东西。我在这里就谈谈我的理解好了。 ```javascript // 写一个class class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return '(' + this.x + ', ' + this.y + ')'; } } var point = new Point(2, 3) // new一个实例 ``` 下面看下point内部是怎样的,可以看出constructor,toString都是在“__proto__”内的。 ![js对象图4](http://p0639a4mt.bkt.clouddn.com/1523869463000-173.png "js对象图4") 在这里会等价于构造函数的什么写法呢 ```javascript // 写一个class function Point (x, y) { this.x = x this.y = y } Point.prototype.toString = function () { return '(' + this.x + ', ' + this.y + ')'; } var point = new Point(2, 3) // new一个实例 ``` 在不考虑class静态属性的情况下,可以简单理解为constructor内this下的新属性和方法都属于构造函数内的,而constructor外的方法都是prototype下的。 >- 在看看class的继承 ```javascript class ChildPoint extends Point { constructor (x, y, z) { super(x, y) this.z = z } toString () { return '(' + this.x + ', ' + this.y + ','+ this.z +')'; } changeX (x) { this.x = x } } var childPoint = new ChildPoint(2, 3, 4) // new一个ChildPoint的实例 console.log(childPoint.x) // 2 console.log(childPoint.y) // 3 console.log(childPoint.z) // 4 console.log(childPoint.toString()) // 2,3,4 childPoint.changeX(5) console.log(childPoint.x) // 5 console.log(childPoint.__proto__ === ChildPoint.prototype) // true console.log(childPoint.__proto__.__proto__ === Point.prototype) // true console.log(childPoint.constructor === ChildPoint) // true ``` 这里class是通过extends继承父类的,而子类的constructor方法内需要调用super()方法,这相当与调用了父类的constructor()方法。 下面看下childPoint的内部情况: ![js对象图5](http://p0639a4mt.bkt.clouddn.com/1523875338000-822.png "js对象图5") 从图片和上面打印的情况可以看出,es6的继承和组合继承很是相似。唯一的不同点就是es6的继承没有组合继承产生的多余的一份实例属性在原型里。看起来很顺眼。不过相对的es6的不能多继承(虽然组合继承只能多继承多个构造函数,并不能继承多个原型)。 其实组合继承还可以进一步升级成es6继承那个样子的(寄生组合继承): ```javascript // 我们先创建一个父类 function Super (name) { this.name = name this.type = '我是父类' } Super.prototype.move = function () { console.log(this.name + '在跑') } // 子类继承父类 function Child (name) { Super.call(this) this.name = name } (function() { // 创建一个没有Super实例的中间类 var MiddleSuper = function() {} MiddleSuper.prototype = Super.prototyoe // 跟Super共享原型 // 在这里MiddleSuper和Super的区别就是有没有实例了。 // 然后在正常进行组合继承的操作 Child.prototype = new MiddleSuper() })() Child.prototype.constructor = Child // 修复对构造函数的指向 Child.prototype.eat = function () { // 做点啥 } ``` ![js对象图6](http://p0639a4mt.bkt.clouddn.com/1523934606000-937.png "js对象图6") 经过这一系列复杂的操作后我们就得到了一份和es6一样的结构了。不过es6只需要一个extends,而es6之前我们就得长篇大论的写才能实现,而且还特别绕。所以一句话,快去学es6吧。 >- 该文纯属个人见解,有什么问题请指出。 本文参考了: >- 幻天芒的JS继承的实现方式 >- 阮一峰的ECMAScript 6 入门
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章