从ES6中的extends讲js原型链与继承

背景

最近在实现一个表单页面复制功能的时候遇到一个问题,本来页面是有创建、编辑功能的,现在要先加一个一键复制的功能,具体流程和编辑一样,name 字段加一个 -copy ,最后提交是用创建的接口。Edit 是继承的 CreateCopy 来继承 Edit,具体代码可以抽象成以下:

转载

https://wulv.site/2017-05-29/%E4%BB%8EES6%E4%B8%AD%E7%9A%84extends%E8%AE%B2js%E5%8E%9F%E5%9E%8B%E9%93%BE%E4%B8%8E%E7%BB%A7%E6%89%BF.html

     
     
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
     
     
// 写法一
class Edit {
setValue(value) {
console.log(value);
}
}
class Copy extends Edit {
setValue(value){
value = `${value}-copy`;
super.setValue(value);
}
}
const copy = new Copy();
copy.setValue( 'test');
// test-copy

问题

上面的代码最终会输出 test-copy,也就是我想要的结果,但当时手贱,写了一个箭头函数,如下:

     
     
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
     
     
// 写法二
class Edit {
setValue = (value) => {
console.log(value);
}
}
class Copy extends Edit {
setValue = (value) => {
value = `${value}-copy`;
super.setValue(value);
}
}
const copy = new Copy();
copy.setValue( 'test');
// Cannot read property 'call' of undefined

结果报错了,这就奇怪了,难道箭头函数和普通的函数声明还有这个区别吗?拓展一下,还有以下两种写法:

     
     
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
     
     
// 写法三
class Edit {
setValue = (value) => {
console.log(value);
}
}
class Copy extends Edit {
setValue(value) {
value = `${value}-copy`;
super.setValue(value);
}
}
const copy = new Copy();
copy.setValue( 'test');
// test
// 写法四
class Edit {
setValue(value) {
console.log(value);
}
}
class Copy extends Edit {
setValue = (value) => {
value = `${value}-copy`;
super.setValue(value);
}
}
const copy = new Copy();
copy.setValue( 'test');
// test-copy

是不是有点晕了,四种写法结果居然出来三种结果,怎么看都觉得差不多啊。

原理

在 es5 的时候我们了解到继承的几种实现方式,原型链继承寄生式继承组合继承等,具体这篇博客写的很好,继承一个构造函数是要继承两个部分:

  1. 一个是实例属性,实例属性是每个实例都有一份各自互不干扰的属性。我们这么写:

           
           
    1
    2
    3
           
           
    function Edit() {
    this.name = 'test';
    }

    其实在 ES6 中对应的就是 = 这种写法:

           
           
    1
    2
    3
           
           
    class Edit {
    name = 'test'
    }
  2. 一个是方法,方法则是共用的,放在 prototype 上,以此来节省内存。我们这么写:

           
           
    1
    2
    3
           
           
    Edit.prototype.setValue = function() {
    console.log( this.value);
    }

    在 ES6 中对应的就是声明函数这种写法:

           
           
    1
    2
    3
    4
    5
           
           
    class Edit {
    setValue() {
    console.log( this.value);
    }
    }

    我们来看一下 babel 编译后 copy 继承做了哪些事:

     
     
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
     
     
var Copy = function (_Edit) {
_inherits(Copy, _Edit);
function Copy() {
_classCallCheck( this, Copy);
return _possibleConstructorReturn( this, (Copy.__proto__ || Object.getPrototypeOf(Copy)).apply( this, arguments));
}
_createClass(Copy, [{
key: 'setValue',
value: function setValue(value) {
value = value + '-copy';
_get(Copy.prototype.__proto__ || Object.getPrototypeOf(Copy.prototype), 'setValue', this).call( this, value);
}
}]);
return Copy;
}(Edit);
  1. 做了一个 _inherits 操作,这个里面做了两件事:
    • 将 Copy.prototype 设置为 Edit.prototype创建的对象, 并设置其constructor属性(不可枚举)
    • 将 Copy.__proto__ 指向 Edit
  2. 将 Copy.__proto__ 也就是 Edit执行 Edit.apply(this, arguments),这不就是经典继承么。
  3. 执行一个 _createClass操作,就是把自己的方法绑定到prototype上。

总结来说就是:

  1. 继承原型链方法
  2. 继承实例属性
  3. 扩展添加的方法

到这里我们解释了两个classextends所做的事情,那为什么创建对象执行方法就成了不同的结果呢?这要从js对象怎么查找属性,也就是原型链有关。当调用一个对象的某个方法时,首先对象会查找本身有没有设置这个属性,也就是这样:

     
     
1
2
3
4
5
     
     
var obj = {
say: function() {
console.log( 'haha');
}
}

如果找不到的话,其实每个对象都有一个__proto__属性,指向创建这个对象的构造函数的原型(这里也就是Object.prototype),原型也就是一个对象,也有自己的属性和__proto__,如果原型还找不到,就这样沿着__proto__一直找下去,这就构成了js的原型链。

回到我们上面的四种写法:

  1. 写法一:EditCopy都将setValue设置到原型上,Edit创建的对象可以通过__proto__找到自己的方法,super操作也可以通过(Object.getPrototypeOf(Copy.prototype), 'setValue', this).call(this, value)调用到Edit的 setValue 方法。
  2. 写法二:子类和父类都是实例属性,子类的setValue覆盖了父类,执行了子类的setValue,然而父类的prototype里并没有setValue属性,报错。
  3. 写法三:子类继承了父类的实例属性,子类的原型里也有相同的属性,然而根据js原型链查找规则,优先使用了实例属性,也就只执行了父类的setValue方法。
  4. 写法四:基本和写法一一样,不过子类先找到了自己的实例方法,没有从原型里查找。

总结

ES6里如果要使用super使用父类的同名方法,父类的方法不能设置为实例方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值