博客中是自己的理解, 以及对大佬描述不清楚的地方进行了修正, 也算是自己的一个再 (xiao) 产(tu)出 (cao) 吧
上一篇: JavaScript 面向对象之一(封装)
构造函数进行继承
先来看个简单的:
- function Animal(){
- this.type = 'animal;'
- }
- function Cat(name, color){
- this.name = name
- this.color = color
- // 这里用 call, 个人觉得更好些
- Animall.call(this)
- }
- var cat = new Cat('po', 'orange')
- console.log(cat.type) //animal
创建了一个 Animal 和 Cat 构造函数, 然后在 Cat 里面调用 Animal 的构造函数, 在将 Cat 实例化, 就可以访问到 Animal 的属性了. 这个例子显然有问题, 放在第一个就是用来找茬的.
什么问题呢? 假如我想使用在 Animal 上的公共方法, 像这样
Animal.prototype.eat = function(){ console.log('animal eat') }
, 用 cat.eat()是访问不到的.
为什么呢? 因为我们 Cat 和 Animal 的原型根本就没有关联起来呀. 你看看咱们上面的代码, 那个地方关联过?
使用原型进行继承
那下面我们就将两者的原型关联起来试试看
- function Animal(){
- this.type = 'animal;'
- }
- Animal.prototype.eat = function(){ console.log('animal eat') }
- function Cat(name, color){
- this.name = name
- this.color = color
- }
- Cat.prototype = new Animal()
- var cat = new Cat('po', 'orange')
- console.log(cat.type) //animal
- console.log(cat.eat()) //animal eat
这种方法好! 可以拿到属性和方法, 一举两得. 但是, 这里有个陷阱(keng)!!!
Cat.prototype = new Animal()
之后, 我们的 Cat.prototype 里面的所有方法都消失了! 这是怎么回事? 因为 new,new 做了四件事, 这里再次回顾一下:
- var temp = {}
- temp.__proto__ = Animal.prototype
- Animal.call(temp)
- return temp
看到了吗? new 会使用一个空对象并且将其返回. 这样一来我们的 Cat.prototype 就被清空了(包括自身的 constructor). 所以我们需要自己多做一件事
- Cat.prototype.constructor = Cat
- .
如果我们不这样做, 那么 Cat.prototype 的构造函数会是什么? 我们在去看看 new 做的第二件事, 这个空对象指向了 Animal.prototype, 所以 Cat.prototype 自身如果没有 constructor 属性的话就会去 Animal.prototype 上面去找.
Cat.prototype.constructor === Animal //true
所以, 如果我们替换了 prototype, 需要手动去纠正它.
直接继承 prototype
既然上面这种方法有坑, 而且的的确确让你很容易漏掉, 那我们改进一下:
- function Animal(){}
- Animal.prototype.eat = function(){ console.log('animal eat') }
- Cat.prototype = Animal.prototype
- Cat.prototype.constructor = Cat
- var cat = new Cat('po', 'orange')
- console.log(cat.eat()) //animal eat
既然我们想从 Animal.prototype 上面那东西, 直接从上面拿不就行了? 而且我还机智的填了上面会出现的坑. 同时结果也是我想要的.
但是!! 我们的 Cat.prototype 和 Animal.prototype 指向的是同一个原型, 这会导致我在 Cat.prototype 上做了什么事, 会同时发生在 Animal.prototype 上. 这是为什么呢? MDZZ, 这两个就是同一个东西呀, 原型是堆中一块内存, Cat 和 Animal 都指向这块内存, 操作的是同一个东西, 怎么会不影响?
与此同时, 自以为聪明的填坑
Cat.prototype.constructor = Cat
, 此时的 Cat 和 Animal 的原型是同一个, 修改了 constructor 之后, 导致 Animal 的 constructor 变成了 Cat. 这种方法果断 PASS...
利用空对象作为中介
- var F = function(){}
- F.prototype = Animal.prototype
- Cat.prototype = new F()
- Cat.prototype.constructor = Cat
我们使用中间对象进行过度, 巧妙的将 Cat.prototype 和 Animal.prototype 解耦, 这样就不会出现问题了. 仔细观察会发现这个和 new 做的事情有异曲同工之处.
我们进行封装一下在使用
- function extend(Child, Parent) {
- var F = function() {};
- F.prototype = Parent.prototype;
- Child.prototype = new F();
- Child.prototype.constructor = Child;
- Child.uber = Parent.prototype;
- }
- extend(Cat, Animal);
- var cat1 = new Cat('po''orange');
- alert(cat1.eat()); // animal eat
这里
Child.uber = Parent.prototype
的意思类似于__proto__.
拷贝继承
这里还有一种简单粗暴的方式
- function extend2(Child, Parent) {
- var p = Parent.prototype;
- var c = Child.prototype;
- for (var i in p) {
- c[i] = p[i];
- }
- c.uber = p;
- }
- function Animal(){}
- Animal.prototype.eat = function(){ console.log('animal eat') }
- extend2(Cat, Animal);
- var cat1 = new Cat('po' 'orange');
- alert(cat1.eat()); // animal eat
直接遍历父类的原型, 一个个复制到子类原型上即可.
感慨一下, 大佬还是大佬呀...
来源: https://juejin.im/post/5acc6c07518825558a06f0fe