一 在此之前先罗列下内容涉及的相关知识点
1 构造函数的调用过程(能通过new来调用的就是构造函数,反之没有通过new调用的就是普通函数)
step1 :创建构造函数
function A() { this.name = "a"; } var a = new A();
step2 创建a实例使用new经历了以下四个步骤
1 var obj = {};
2 obj.__proto__ = A.prototype;
3 A.call(obj);
4 return obj;
2 Object.create()函数,
原理类似于方法
function object(pro) { function F() {}; F.prototype = pro; return new F(); }
相当于对原型或者对象进行了浅拷贝
function a() { this.name = "a"; } a.prototype.getN = function() { return this.name; } var b = Object.create(a.prototype,{ name:{ value:"b" } }) console.log(b.name) //"b" console.log(b.getN()) //"b"
二 下面来罗列下js的继承种类
1 首先是最基础的原型链继承
function A(name) { this.name = name; this.age = 20; this.arr = [1,2,3]; } A.prototype.getName = function() { console.log("name: "+this.name); } function B() { } //B的原型指向A的一个实例 B.prototype = new A(); var obj1 = new B(); var obj2 = new B(); obj1.age = 10; console.log(obj1.age); //10 console.log(obj2.age); //20 obj1.arr.push(4); console.log(obj1.arr); //[1,2,3,4] console.log(obj2.arr); //[1,2,3,4]
此种原型链继承 包括两个缺点:
1 如果父类包含引用类型的属性,类似上面A有this.arr = [],这就是引用类型(引用类型的复制只是将内存中的地址重新指向给了新的而已),所以,上面当改变obj1中的arr时,就会沿着原型链先到B中寻找,最终找到A中,添加4,此时因为是引用类型,所以obj2等子类的实例都会共享次引用类型属性,所以导致obj2.arr也是[1,2,3,4]
2 当父类函数中有需要传值初始化构造函数属性时,创建子类实例的时候无法进行传参,例入上面的A函数中this.name就需要传值初始化
那么先来说下解决思路
对于1 来说,主要原因是因为B中并没有此arr属性,所以会一直查找原型链直到A中(那么可能会有疑问,obj1.age = 10 给age重新赋值为什么obj2.age没有变成10,这是因为此属性是基础类型,重新赋值/'也就是复制',就是拷贝了一份跟原来的无关),从而导致了子类实例共享;所以说只要在B中能找到需要的属性那就不用再去A中寻找了
//1浅拷贝一份A,2实现B的实例传值给A function B(name) { A.call(this,name); }
这也就是第二种继承方式
2 借用构造函数继承
function A(name) { this.name = name; this.age = 20; this.arr = [1,2,3]; } A.prototype.getName = function() { console.log("name: "+this.name); } function B(name) { A.call(this,name) } var obj1 = new B("obj1"); var obj2 = new B("obj2"); console.log(obj1.name); // obj1 console.log(obj2.name); // obj2 obj1.arr.push(4); console.log(obj1.arr); //[1,2,3,4] console.log(obj2.arr); //[1,2,3]
此种方法的缺点是:所有属性和方法都需要在A的构造函数中定义,并且A.prototype中定义的方法不能继承,这也就不能实现方法的复用,类似上面的obj1.getName == undefined
3 原型链继承+借用构造函数继承(组合继承)
-
原型链继承可以把方法定义在原型上,从而复用方法
-
借用构造函数继承法可以解决引用类型值的继承问题和传递参数问题
function A(name) { this.name = name; this.age = 20; this.arr = [1,2,3]; } A.prototype.getName = function() { console.log("name: "+this.name); } function B(name) { //再次调用A A.call(this,name) } //第一次调用A B.prototype = new A(); var obj1 = new B("obj1"); var obj2 = new B("obj2"); console.log(obj1.name); // obj1 console.log(obj2.name); // obj2 obj1.arr.push(4); console.log(obj1.arr); //[1,2,3,4] console.log(obj2.arr); //[1,2,3] console.log(obj2.getName());
此方法虽然解决了传参和引用类型共享的问题,但是也带来了新的缺点:
1 多次调用父类构造函数产生的资源浪费:因为调用B.prototype = new A()的时候 B包含了A的所有属性方法,当实例化obj1和obj2时,obj1和obj2通过call也具有了A的属性和方法,这就违背了原型链的意义:对象本身不存在属性方法时候,沿着原型链向上寻找,如果对象本身具有,则访问自身的属性方法,这就使B.prototype显得很多余(老子把钞票都准备好了,儿子们却拿着印钞机又搞了一份)
所以针对此现象引出了下面的寄生继承
4 寄生继承
function inheritPrototype(child,father) { //创建指向父类的原型对象 var prototpye = Object.create(father.prototype); //重写原型对象constructor指针 prototype.constructor = child; //子类原型指向这个复制的原型对象 child.prototype = prototpye; }
使用此寄生方法替代了原来的B.prototype = new A(); 不但实现了将A的prototype通过桥接给了B,而且B.prototype也没有了A构造函数中的属性和方法了(但是有从A.prototype复用的getName方法),摒弃了不必要的多余方法
function inheritPrototype(child,father) { //创建指向父类的原型对象 var pro = Object(father.prototype); //重写原型对象constructor指针 pro.constructor = child; //子类原型指向这个复制的原型对象 child.prototype = pro; } function A(name) { this.name = name; this.age = 100; this.arr = [1,2,3,4]; } A.prototype.getName = function() { console.log("name: "+this.name); } function B(name) { A.call(this,name); } inheritPrototype(B,A); var c1 = new B("c1"); var c2 = new B("c2"); console.log(c1.name); // c1 console.log(c2.name); // c2 c1.arr.push(5); console.log(c1.arr); // [12,3,4,5] console.log(c2.arr); // [12,3,4]