前端面试题之JS继承方法

前端面试题之JS继承方法

在js中,继承的方法有以下几种:
构造函数继承

function Parent() {
    this.name = 'parent';
}
function Child(age) {
    Parent.call(this);
    this.age = age;
}
var c = new Child(12);
console.log(c.name); //输出parent

原理是在Child构造函数中利用call改变了this指向,使得Child对象增加了name属性,值为’parent’,完成了继承。
这种方法的缺点就是:父函数原型链上的东西不能被继承

function Parent() {
    this.name = 'parent';
}
Parent.prototype.say = function () {
    console.log('say');
};
function Child(age) {
    Parent.call(this);
    this.age = age;
}
var p = new Parent(); //new一个Parent对象用来对比
p.say(); //输出say
var c = new Child(12);
c.say(); //undifined

say是Parent原型链上的方法,Parent对象调用方法时,如果自身不存在就回去原型链上寻找,在原型链上找到了say方法,而Child对象没有继承Parent对象的原型链,所以它在向上寻找时就找不到,输出undifined。

原型链继承

function Parent() {
    this.name = 'parent';
}
Parent.prototype.say = function () {
    console.log('say');
};
function Child(age) {
    this.age = age;
}
Child.prototype = new Parent();
var c = new Child(12);
console.log(c.name); //输出parent
c.say() //输出say

原型链继承是直接让Child构造函数的prototype直接指向Parent对象,这样Parent的东西Child对象可以直接从它的原型链上找到。
这种方法的缺点就是:当创建多个实例时,如果不同实例可能互相存在影响,例如:

function Parent() {
    this.name = 'parent';
    this.arr = [1,2,3,4]
}
Parent.prototype.say = function () {
    console.log('say');
};
function Child(age) {
    this.age = age;
}
Child.prototype = new Parent();
var c1 = new Child(12);
var c2 = new Child(12);
console.log(c1.arr);  //[1,2,3,4]
console.log(c2.arr); //[1,2,3,4]
c1.arr.push(5);
console.log(c1.arr);  //[1,2,3,4,5]
console.log(c2.arr);  //[1,2,3,4,5]

这里只对c1的arr进行了改变,却影响到了c2的arr,原因就是这种继承方式,实例的原型都是同一个对象,即new Parent()出来的Parent实例,当实例寻找arr时在自己这里找不到就会去找原型链上的Parent的属性,找到了arr,而arr又是引用类型,所以c1修改arr时,c2的arr也会发生改变。

构造函数和原型链继承组合方法

function Parent() {
    this.name = 'parent';
    this.arr = [1,2,3,4]
}
Parent.prototype.say = function () {
    console.log('say');
};
function Child(age) {
    Parent.call(this); 
    this.age = age;
}
Child.prototype = new Parent();
var c1 = new Child(12);
var c2 = new Child(12);
console.log(c1.arr); //[1,2,3,4]
console.log(c2.arr);//[1,2,3,4]
c1.arr.push(5);
console.log(c1.arr); //[1,2,3,4,5]
console.log(c2.arr); //[1,2,3,4]

这种组合方式与上一种方法的区别就是,实例创建时就有了Parent里的属性,这样就不会去Parent寻找arr,修改的arr就是各自属性中的arr,就不会产生影响,同时也继承了Parent原型链上的东西。
这里优化一下:
之前 Child.prototype = new Parent() 即上面第二种继承方式,是为了继承Parent原型链的同时继承Parent函数里面的东西,现在在Child函数里已经用 Parent.call(this)直接继承了Parent函数里面的东西,那么就直接继承父函数的原型链就完事了即:

function Parent() {
    this.name = 'parent';
    this.arr = [1,2,3,4];
}
Parent.prototype.say = function () {
    console.log('say');
};
function Child(age) {
    Parent.call(this);
    this.age = age;
}
Child.prototype = Parent.prototype;
var c = new Child(12);
console.log(c.name); //输出parent
c.say(); //输出say

优化完之后,还是有缺点:

console.log(c.constructor); //输出Parent() {this.name = 'parent';this.arr = [1,2,3,4];}

在这里找Child的原型的constructor找到的是Parent的constructor,原因就是因为Child.prototype = Parent.prototype这里直接将Parent的prototype直接赋值给Child的prototype,所以它的constructor肯定就是Parent的constructor,这时不能直接这样修改:

Child.prototype = Parent.prototype;
Child.prototype.constructor = Child;
console.log(c.constructor); //输出function Child(age) {Parent.call(this);this.age = age;}
console.log(new Parent().constructor); //输出function Child(age) {Parent.call(this);this.age = age;}

这时因为Parent.prototype(对象)是引用类型,所以修改Child的constructor,Parent的constructor也会改变,于是将Child.prototype = Parent.prototype;修改一下:

function Parent() {
    this.name = 'parent';
    this.arr = [1,2,3,4];
}
Parent.prototype.say = function () {
    console.log('say');
};
function Child(age) {
    Parent.call(this);
    this.age = age;
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
var c = new Child(12);
console.log(c.name);
c.say();
console.log(c.constructor); //输出function Child(age) {Parent.call(this);this.age = age;}
console.log(new Parent().constructor); //输出Parent() {this.name = 'parent';this.arr = [1,2,3,4];}

用Object.create(Parent.prototype)这种方法创建一个新对象,赋值给Child.prototype,这样修改Child的constructor 时,Parent的constructor 就不会受到影响。
这是最完美的方法了

猜你喜欢

转载自blog.csdn.net/qq593249106/article/details/83098432