从babel实现es6类的继承来深入理解js的原型及继承
栏目: JavaScript · 发布时间: 6年前
内容简介:自从有了babel这一个利器之后,es6现在已经被广泛的使用。JavaScript 类实质上是 JavaScript 现有的基于原型的继承的语法糖。类语法不会为JavaScript引入新的面向对象的继承模型。也就是说babel无论是实现类,还是实现继承,本质上都是基于原型及原型链的,那我我们就顺这这个思路,一步一步往下走,直到揭开babel是如何实现类及类的继承的。javascript中原型的概念比较抽象,不是很好理解,我们还是老老实实上代码,用代码来举例说明。我们创建了一个Person的构造函数,并用n
自从有了babel这一个利器之后,es6现在已经被广泛的使用。JavaScript 类实质上是 JavaScript 现有的基于原型的继承的语法糖。类语法不会为JavaScript引入新的面向对象的继承模型。也就是说babel无论是实现类,还是实现继承,本质上都是基于原型及原型链的,那我我们就顺这这个思路,一步一步往下走,直到揭开babel是如何实现类及类的继承的。
原型和原型链
javascript中原型的概念比较抽象,不是很好理解,我们还是老老实实上代码,用代码来举例说明。
function Person (name, age, job) { this.name = name; this.age = age; this.job = job; } let p1 = new Person('张三', 18, '前端攻城狮') 复制代码
我们创建了一个Person的构造函数,并用new 创建了一个该对象的实例对象p1。
- 在js中,每一个函数都有一个prototype属性。
- 每一个js对象(除null)外都有一个__proto__的属性。 那么问题来啦,函数Person的prototype的属性指向的是什么呢?是不是Person的原型呢?我们在chrome中输入如下代码 我们发现Person.prototype指向一个对象,实际上这个对象就是p1的原型。如图: 因此,我们也可以用下面的这个图来表示他们之间的关系
- 前面我们说过,Person.prototype指向一个对象,那么这个对象中都有什么呢?我们再在chrome中输入如下代码如图: 我们发现Person.prototype的确是一个对象,这个对象里面有两个属性:constrcutor, 和__proto__这两个属性。constructor指向Person(),也说明js对象果真都有一个__proto__的属性啊。那问题来啦constructor到底和Person是什么关系呢?实际上,constructor属性指向的就是构造函数本身。有图有真相
我们再来总结一下吧:
- 每个原型上都有一个constructor的属性指向关联的构造函数。他们的关系如下: 前面我们发现Person.prototype也有__proto__属性,那Person.prototype.__proto__是什么呢?换言之,原型的原型是什么呢。先来一张图 我们发现它只有constructor属性,该属性指向Object(),没有__proto__属性。是不是很有意思。那我们再做个实验吧。如图
通过这张图我们发现
- 原型对象就是通过Object构造函数生成的,实例的__proto__指向构造函数的prototype,Object构造函数的原型的__proto__指向null
- 我们用一张图总结一下吧: 实际上这个就是原型链啦。
ES5中的继承
前面我们聊了聊原型和原型链,其实就是为了我们聊继承做铺垫的。废话不多说,我们上代码吧:
// 我们定义一个动物类,里面有一个类型的属性 function Animal (type) { this.type = type } // 动物都有吃东西的方法 Animal.prototype.eat = function () { console.log('我在吃东西') } // 我们来个小猫咪类吧 function Cat () {} // 现在我们要实现让Cat继承Animal的属性和方法,该怎么做呢? 复制代码
- 第一种方法
// 我们将cat类的原型直接指向Animal的实例 Cat.prototype = new Animal() let cat = new Cat() console.log(cat.eat()) 复制代码
结果如图
我们发现无法继承父类的属性。我们改造一下,借助call方法,代码入下:
function Animal (type) { this.type = type } Animal.prototype.eat = function () { console.log('我在吃东西') } function Cat (type) { Animal.call(this, type) } Cat.prototype = new Animal() let cat = new Cat('喵咪') console.log(cat.eat()) console.log(cat.type) 复制代码
运行结果如下:
nice!!!似乎完美的解决了问题,但真的是这样么? 我就不卖关子啦,实际上是有问题的,有什么问题呢?我们上代码:
function Animal (type, val) { this.type = type this.sum = [4,5,6] // 随便给的属性啊,为了说明问题。 this.setSum = function () { this.sum.push(val) } } Animal.prototype.eat = function () { console.log('我在吃东西') } function Cat (type, val) { Animal.call(this, type, val) } Cat.prototype = new Animal() let cat = new Cat('喵咪', 1) let cat2 = new Cat('猫咪2', 2) console.log(cat.setSum()) console.log(cat2.setSum()) console.log(cat.sum) console.log(cat2.sum) 复制代码
运行结果如图:
发现了没有,图中setSum方法和sum属性都是父类定义的,但是子类可以调用。所以这个不是我们想要的结果;还有一点就是Cat的constructor属性,此时指向的并不是Cat而是Animal。那应该怎么解决呢?
- 第二种方法: 这个时候我们就不得不拿出我们的终极神器啦。它就是ES5的Object.create()方法。代码如下:
function inherit(C, P) { // 等同于临时构造函数 C.prototype = Object.create(P.prototype); C.prototype.constructor = C; // 修复constructor C.super = P;//存储超类 } function Animal(type) { this.type = type; } Animal.prototype.eat = function () { console.log('动物都是要吃饭滴') } function Cat(type, talk) { Cat.super.call(this, type) this.talk = talk this.getTalk = function () { return this.talk } } inherit(Cat, Animal) let cat = new Cat('猫咪', '我们一起学猫叫') console.log(cat.getTalk()) 复制代码
代码运行如下:
这样我们就比较完美的解决了继承的问题。
babel对es6类的实现
在背景中我们已经聊到了es6中类的继承实际上是一个语法糖,现在我们就想办法拨开这颗糖。
- es6类重写上面代码
class Animal { constructor (type) { this.type = type } eat () { console.log('动物都要吃饭') } } class Cat extends Animal { constructor (type,talk) { super(type) // 继承父类constructor的属性 this.talk = talk } getTalk () { return this.talk } } let cat = new Cat('喵咪', '喵喵喵') console.log(cat.getTalk()) // 喵喵喵 console.log(cat.type) // 喵咪 复制代码
-
babel中类的实现
babel中类的实现主要是三个步骤:
- 步骤一: 构造函数的实现,这部分的实现与我们普通的构造函数的区别就是,通过一个自执行函数包裹,代码如下
var Animal = function () { function Animal (type) { this.type = type this.getType = function () { return this.type } } return Animal }() var Cat = function () { function Cat (talk) { this.talk = talk this.getTalk = function () { return this.talk } } return Cat }() 复制代码
- 步骤二: 如下代码:
function Animal() { return { name: '牛逼', age: 18 } } let animal = new Animal() console.log(animal) // { name: '牛逼', age: 18} 复制代码
因此babel中对此做了校验,这就是第二步要干的事情。代码如下:var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("不能返回一个对象") } } // 嗯,你没有看错就是这么几行代码 var Animal = function () { function Animal (type) { _classCallCheck(this, Animal) this.type = type } }() ...此处省略喵咪类 复制代码
- 步骤三: 原型上方法的添加。前面两步主要是对构造函数的实现及校验,步骤三就是把原型上的方法添加到对应的类上。这里我们主要要聊聊babel是如何对 es6中类的方法的处理,也就是
class Animal { eat () { console.log('aaa') } sleep () { console.log('bbb') } } 复制代码
如上面代码中的eat方法和sleep方法的处理。那怎么能把方法添加到一个对象上呢?没错,Object.defineProperty()这个方法就是用来干这件事情的,babel中也是用他来处理的。回到我们的话题,在babel中,它是通过_createClass这个函数来处理的。废话不多说我们上代码:/* _createClass()这个函数接受两个参数, 一个是这个类的构造函数,即给哪个类添加方法。 第二个参数是一个数组对象,类似这样婶的 [{key: 'eat', val: function eat () { console.log('aaa') }}] 是不是恍然大悟?那接下来我们就实现一下吧 */ function definePropties (target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor) } } var _createClass = function () { return function (Constructor, protoProps, staticProps) { // 添加原型上的方法 if (protoProps) definePropties(Constructor.prototype, protoProps) // 添加静态的方法 if (staticProps) definePropties(Constructor, staticProps) return Constructor } }() _createClass(Animal,[{ key: 'eat', value: function () { console.log('aaa') } }]) 复制代码
到这里babel实现类就基本上实现啦。上面代码可以多看,多思考,还是挺收益的。
- 步骤一: 构造函数的实现,这部分的实现与我们普通的构造函数的区别就是,通过一个自执行函数包裹,代码如下
babel中继承的实现
接下来,终于到了我们的大boss啦,上面我们聊了聊babel中是怎么处理es6中类的,这节我们就聊聊babel中是怎么处理es6中的继承。
- babel中继承的实现 babel中继承的实现的思路基本跟我们前面聊的es5的继承思路基本一致。下面我们来聊一聊。
function _inherits(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true; } }) if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 复制代码
好啦,这个话题就聊到这里,欢迎大家拍砖啊。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 构造函数、原型、原型链、继承
- 前端基本功(七):javascript中的继承(原型、原型链、继承的实现方式)
- JavaScript原型链继承
- JavaScript 继承和原型链
- 彻底弄懂JS原型与继承
- 前端进击的巨人(七):走进面向对象,原型与原型链,继承方式
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。