JavaScript的原型继承和ES6的class语法

回顾一下JavaScript的原型链和原型继承及ES6的Class语法。

原型链

JavaScript中的每个对象,都有一个特殊内置的_proto_属性。这个属性是编程不可见的(虽然ES6标准中开放了这个属性,然而浏览器对这个属性的可见性的支持不同),它实际上是对另一个对象或者null的引用。几乎所有的对象在创建时_proto_属性都会被赋予一个非空的值 。

当一个对象需要引用一个属性时,JavaScript引擎首先会从这个对象自身的属性表中寻找这个属性标识,如果找到则进行相应读写操作,若没有在自身的属性表中找到,则在_proto_属性引用的对象的属性表中查找,如此往复,直到找到这个属性或者_proto_属性指向null为止。最后会找到Object对象上,如果还没有,那么就返回undefined 。

这个_proto_的引用链,被称作原型链

注意,此处有一个性能优化的问题:往原型链越深处搜索,耗费的时间越多。

原型继承实现的几种方式

在javaScript中,可以说一切皆对象。它并没有‘’类‘’这个概念,它跟Java等语言不同,在ES6标准出炉之前,它是没有类的定义的。对于在javaScript中提出类的概念,只是那些熟悉Java强类型语言的使用者,希望JavaScript中能通过类生成实例,通过子类复用代码。那在ES6中class的语法糖提出前,是怎么做到的???

原型链继承
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
34
35
36
37
function Parent () {
this.name = 'kevin';
}

Parent.prototype.getName = function () {
console.log(this.name);
}

function Child () {

}

Child.prototype = new Parent();

var child1 = new Child();

console.log(child1.getName()) // kevin
//1.引用类型的属性被所有实例共享,举个例子:
function Parent () {
this.names = ['kevin', 'daisy'];
}

function Child () {

}

Child.prototype = new Parent();

var child1 = new Child();

child1.names.push('yayu');

console.log(child1.names); // ["kevin", "daisy", "yayu"]

var child2 = new Child();

console.log(child2.names); // ["kevin", "daisy", "yayu"]

这种继承会产生一些问题:

​ 1.引用类型的属性被所有实例共享

​ 2.在创建 Child 的实例时,不能向Parent传参

原型式继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Foo(name) {
this.name = name;
}
Foo.prototype.myName = function() {
return this.name;
};
function Bar(name,label) {
Foo.call( this, name );
this.label = label;
}
Bar.prototype = Foo.prototype;
Bar.prototype.myLabel = function() {
return this.label;
};

var a = new Bar( "a", "obj a" );
console.log(Foo.prototype.hasOwnProperty('myLabel'))//true
console.log(a.myName()); // "a"
console.log(a.myLabel()); // "obj a"

这种继承是有缺点的,由于Bar.prototype引用了Foo.prototype对象,当执行Bar.prototype.myLabel 的赋值语句会直接修改Foo.prototype对象本身,同时导致包含引用类型的属性值始终都会共享相应的值

组合继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Parent (name) {
this.name = name;
consoel.log('a')
}

Parent.prototype.getName = function () {
console.log(this.name)
}

function Child (name, age) {
Parent.call(this, name);
this.age = age;
}

Child.prototype = new Parent();

var child1 = new Child('koa', '18');

缺点:两次调用两次父构造函数。那需要怎么样去让子类调用父类的构造函数一次

改进:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function Parent (name) {
this.name = name;
}

Parent.prototype.getName = function () {
console.log(this.name)
}

function Child (name, age) {
Parent.call(this, name);
this.age = age;
}

// 关键的三步 F相当于一个中介,去向买房者和购房者传递信息
var F = function () {};

F.prototype = Parent.prototype;

Child.prototype = new F();


var child1 = new Child('koa', '18');

但是这次改进还是存在对象的属性共享的问题:

1
2
3
4
5
6
7
8
9
Parent.prototype.a = { x: 1};

.......

var child1 = new Child('kevin', '18');

console.log(Parent.prototype.a ) //{x:1}
child1.a.x = 2;
console.log(Parent.prototype.a ) //{x:2}

在ES5中,还可通过Object.create()来实现继承:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Parent (name) {
this.name = name;
}

Parent.prototype.getName = function () {
console.log(this.name)
}

function Child (name, age) {
Parent.call(this, name);
this.age = age;
}
//我们创建了一个新的Child.prototype对象并关联到Parent.prototype
Child.prototype = Object.create( Parent.prototype );

var child1 = new Child('koa', '18');

ES6的Class

到此让我们来迎接ES6吧!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Parent {
constructor(name) { //构造函数
this.name = name;
}
getName() {
console.log(this.name)
}
}

class Children extends Parent {
constructor(name) { //构造函数
super(name); //调用父类构造函数
this.age = age;
}
}

var child1 = new Child('koa', '18');

本文由 Abert 创作,采用 知识共享署名 4.0 国际许可协议。

本站文章除注明转载/出处外,均为本站原创或翻译,转载请务必署名。