js 继承

基本介绍

js 中继承可以分为两种:对象冒充和原型链方式,下面分别举例介绍。

对象冒充

包括三种:临时属性方式、call()及apply()方式

临时属性方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Person(name) {
this.name = name;
this.say = function() {
console.log('My name is ' + this.name);
}
}
function F2E(name, id) {
this.temp = Person;
this.temp(name);
delete this.temp;
this.id = id;
this.showId = function() {
console.log('My work number is ' + this.id);
}
}
var simon = new F2E('Simon',9527);
simon.say();
simon.showId();
call()/apply()方式(实质上是改变了this指针的指向)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Person(name) {
this.name = name;
this.say = function() {
console.log('My name is ' + this.name);
}
}
function F2E(name, id){
// apply()方式改成Person.apply(this,new Array(name));
Person.call(this, name);
this.id = id;
this.showId = function() {
console.log('My work number is ' + this.id);
}
}
var simon = new F2E('Simon',9527);
simon.say();
simon.showId();
缺点

先来看这么一张内存分配图:
内存分配图

说明:

  • 在OO概念中,new 实例化后,对象就在堆内存中形成了自己的空间,值得注意的是,上图中的代码段。成员方法就是存在这个代码段的,并且方法是共用的。问题就在这里,通过对象冒充方式继承时,所有的成员方法都是指向 this 的,也就是说 new 之后,每个实例将都会拥有这个成员方法,并不是共用的,这就造成了大量的内存浪费。
  • 并且通过对象冒充的方式,无法继承通过 prototype 方式定义的变量和方法,如以下代码将会出错:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function Person(name) {
this.name = name;
this.say = function() {
console.log('My name is ' + this.name);
}
}
Person.prototype.age = 20;
Person.prototype.sayAge = function() {
console.log('My age is ' + this.age)
};

function F2E(name, id) {
Person.call(this, name);
this.id = id;
this.showId = function() {
console.log('My work number is ' + this.id);
}
}

var simon = new F2E('Simon',9527);
simon.sayAge(); //提示TypeError: simon.sayAge is not a function

原型链方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Person() {
this.name = 'Simon';
}
Person.prototype.say = function() {
console.log('My name is '+this.name);
}

function F2E(id ){
this.id = id;
this.showId = function() {
console.log('My work number is '+this.id);
}
}
F2E.prototype = new Person();
F2E.prototype.constructor = Person;

var simon = new F2E(9527);
simon.say();
simon.showId();
console.log(simon.hasOwnProperty('id')); //检查是否为自身属性

接下来按照上面的例子来理解以下js原型链概念:
image

原型链可以理解成:js中每个对象均有一个隐藏的 __proto__ 属性,一个实例化对象的 __proto__ 属性指向其类的 prototype 方法,而这个 prototype 方法又可以被赋值成另一个实例化对象,这个对象的 __proto__ 又需要指向其类,由此形成一条链,也就是前面代码中的:

1
F2E.prototype = new Person();

这句是关键。js对象在读取某个属性时,会先查找自身属性,没有则再去依次查找原型链上对象的属性。也就是说原型链的方法是可以共用的,这样就解决了对象冒充浪费内存的缺点。

下面再来说缺点:
缺点显而易见,原型链方式继承,就是实例化子类时不能将参数传给父类,也就是为什么这个例子中 function Person() 没有参数,而是直接写成了 this.name='Simon' 的原因。下面的代码将不能达到预期的效果:

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

Person.prototype.say = function() {
console.log('My name is '+this.name);
}

function F2E(name,id) {
this.id = id;
this.showId = function(){
console.log('My work number is '+this.id);
}
}

//此处无法进行传值,this.name或者name都不行,直接写F2E.prototype = new Person('wood')是可以的,但是这样的话simon.say()就变成了My name is wood
F2E.prototype = new Person();
F2E.prototype.constructor = Person;

var simon = new F2E('Simon',9527);
simon.say(); // My name is undefined
simon.showId();

总结

最后,总结一下自认为较好的继承实现方式,成员变量采用对象冒充方式,成员方法采用原型链方式,代码如下:

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

Person.prototype.say = function() {
console.log('My name is ' + this.name);
}

function F2E(name, id) {
Person.call(this, name);
this.id = id;
}

F2E.prototype = new Person();
F2E.prototype.constructor = Person;
//此处注意一个细节,showId不能写在F2E.prototype = new Person();前面
F2E.prototype.showId = function() {
console.log('My work number is ' + this.id);
}

var simon = new F2E('Simon', 9527);
simon.say();
simon.showId();
------------- 本文结束 感谢您的阅读 -------------

本文标题:js 继承

文章作者:水中熊

发布时间:2018年06月12日 - 16:06

最后更新:2018年06月14日 - 14:06

原始链接:https://shuizhongxiong.github.io/js-inherit.html

许可协议: 知识共享署名-非商业性使用-相同方式共享 3.0 中国大陆许可协议进行许可。 转载请保留原文链接及作者。

🌹坚持原创技术分享,您的支持将鼓励我继续创作!🌹
0%