js中如何实现继承?

js是门超级灵活的语言,实现一种功能往往有多种做法,
ECMAScript没有明确的继承机制,es6引入了extend来实现继承,真的是大块人心。
但我们还是应该看看es6之前,改如何实现继承。
js中继承大体上可以分为两种:对象冒充和原型链方式

一、对象冒充包括三种:临时属性方式、call及apply方式
1.临时属性方式
实现原理:
首先让父类的构造函数成为子类的临时方法,
然后调用该子类的方法,
最后通过this关键字给所有的属性和方法赋值

function Person(name){
this.name = name;
this.say = function(){
alert('My name is '+this.name);
}
}
function F2E(name,id){
this.temp = Person;
this.temp(name);
delete this.temp;
this.id = id;
this.showId = function(){
alert('Good morning,Sir,My work number is '+this.id);
}
}
var simon = new F2E('Simon',9527);
simon.say(); // ‘My name is Simon'
simon.showId(); // ‘Good morning,Sir,My work number is 9527’

2.call/apply方式
实质上是改变了this指针的指向

function Person(name){
this.name = name;
this.say = function(){
alert('My name is '+this.name);
}
}
function F2E(name,id){
Person.call(this,name); //apply方式改成Person.apply(this,new Array(name));
this.id = id;
this.showId = function(){
alert('Good morning,Sir,My work number is '+this.id);
}
}
var simon = new F2E('Simon',9527);
simon.say(); // ‘My name is Simon'
simon.showId(); // ‘Good morning,Sir,My work number is 9527’

通过对象冒充方式继承时,所有的成员方法都是指向this的,
也就是说new之后,每个实例将都会拥有这个成员方法,并不是共用的,这就造成了大量的内存浪费。
并且通过对象冒充的方式,无法继承通过prototype方式定义的变量和方法,如以下代码将会出错:

function Person(name){
this.name = name;
this.say = function(){
alert('My name is '+this.name);
}
}
Person.prototype.age = 20;
Person.prototype.sayAge = function(){alert('My age is '+this.age)};

function F2E(name,id){
Person.apply(this,new Array(name));
this.id = id;
this.showId = function(){
alert('Good morning,Sir,My work number is '+this.id);
}
}

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

二、原型链方式

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

function F2E(id){
this.id = id;
this.showId = function(){
alert('Good morning,Sir,My work number is '+this.id);
}
}
F2E.prototype = new Person();

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

js对象在读取某个属性时,会先查找自身属性,没有则再去依次查找原型链上对象的属性。
也就是说原型链的方法是可以共用的,这样就解决了对象冒充方式浪费内存的缺点。
下面再来说缺点:
缺点显而易见,原型链方式继承,就是实例化子类时不能将参数传给父类,
也就是为什么这个例子中function Person()没有参数,而是直接写成了this.name=”Simon”的原因。
下面的代码将不能达到预期的效果:

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

function F2E(name,id){
this.id = id;
this.showId = function(){
alert('Good morning,Sir,My work number
is '+this.id);
}
}
F2E.prototype = new Person();

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

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

function Person(name){
this.name = name;
}

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

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

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

var simon = new F2E("Simon",9527);
simon.say(); // ‘My name is Simon'
simon.showId(); // ‘Good morning,Sir,My work number is 9527’