js中实现继承的几种方式
首先我们了解,js中的继承是主要是由原型链实现的。那么什么是原型链呢?
由于每个实例中都有一个指向原型对象的指针,如果一个对象的原型对象,是另一个构造函数的实例,这个对象的原型对象就会指向另一个对象的原型对象,如此循环,就行成了原型链。
在了解原型链之后,我们还需要了解属性搜索机制,所谓的属性搜索机制,就是当我们访问对象上的一个属性时,我们如何找到这个属性值。首先,我们现在当前实例中查找该属性,如果找到了,返回该值,否则,通过__proto__找到原型对象,在原型对象中进行搜索,如果找到,返回该值,否则,继续向上进行搜索,直到找到该属性,或者在原型链中没有找到,返回undefined。
根据《javascript高级程序设计》中,可以有六种继承方式,下面我们一一来介绍:
1. 原型链
1 // 父亲类 2 function Parent() { 3 this.value = 'value'; 4 } 5 Parent.prototype.sayHi = function() { 6 console.log('Hi'); 7 } 8 // 儿子类 9 function Child() { 10 11 } 12 // 改变儿子的prototype属性为父亲的实例 13 Child.prototype = new Parent(); 14 15 var child = new Child(); 16 // 首先现在child实例上进行查找,未找到, 17 // 然后找到原型对象(Parent类的一个实例),在进行查找,未找到, 18 // 在根据__proto__进行找到原型,发现sayHi方法。 19 20 // 实现了Child继承 21 child.sayHi();
但是这种继承方式存在一个问题,那就是引用类型属性共享。
1 // 父亲类 2 function Parent() { 3 this.color = ['pink', 'red']; 4 } 5 6 // 儿子类 7 function Child() { 8 9 } 10 Child.prototype = new Parent(); 11 12 var child1 = new Child(); 13 var child2 = new Child(); 14 // 先输出child1和child2种color的值 15 console.log(child1.color); // ["pink", "red"] 16 console.log(child2.color); // ["pink", "red"] 17 18 // 在child1的color数组添加white 19 child1.color.push('white'); 20 console.log(child1.color); // ["pink", "red", "white"] 21 // child1上的改动,child2也会受到影响 22 console.log(child2.color); // ["pink", "red", "white"]
它存在第二个问题,就是无法向父类种传参。
2. 借用构造函数
在这里,我们借用call函数可以改变函数作用域的特性,在子类中调用父类构造函数,复制父类的属性。此时没调用一次子类,复制一次。此时,每个实例都有自己的属性,不共享。同时我们可以通过call函数给父类传递参数。
2.1 解决引用类型共享问题
1 // 父亲类 2 function Parent(name) { 3 this.name = name; 4 this.color = ['pink', 'red']; 5 } 6 7 // 儿子类 8 function Child() { 9 Parent.call(this); 10 11 // 定义自己的属性 12 this.value = 'test'; 13 } 14 15 16 var child1 = new Child(); 17 var child2 = new Child(); 18 19 // 先输出child1和child2种color的值 20 console.log(child1.color); // ["pink", "red"] 21 console.log(child2.color); // ["pink", "red"] 22 23 // 在child1的color数组添加white 24 child1.color.push('white'); 25 console.log(child1.color); // ["pink", "red", "white"] 26 // child1上的改动,child2并没有受到影响 27 console.log(child2.color); // ["pink", "red"]
2.2 解决传参数问题
1 // 父亲类 2 function Parent(name) { 3 this.name = name; 4 this.color = ['pink', 'red']; 5 } 6 7 // 儿子类 8 function Child(name) { 9 Parent.call(this, name); 10 11 // 定义自己的属性 12 this.value = 'test'; 13 } 14 15 var child = new Child('qq'); 16 // 将qq传递给Parent 17 console.log(child.name); // qq
当时,上述方法也存在一个问题,共享的方法都在构造函数中定义,无法达到函数复用的效果。
3. 组合继承
根据上述两种方式,我们可以扬长避短,将需要共享的属性使用原型链继承的方法继承,将实例特有的属性,用借用构造函数的方式继承。
1 // 父亲类 2 function Parent() { 3 this.color = ['pink', 'red']; 4 } 5 Parent.prototype.sayHi = function() { 6 console.log('Hi'); 7 } 8 9 // 儿子类 10 function Child() { 11 // 借用构造函数继承 12 Parent.call(this); 13 14 // 下面可以自己定义需要的属性 15 } 16 // 原型链继承 17 Child.prototype = new Parent(); 18 19 var child1 = new Child(); 20 var child2 = new Child(); 21 22 // 每个实例特有的属性 23 // 先输出child1和child2种color的值 24 console.log(child1.color); // ["pink", "red"] 25 console.log(child2.color); // ["pink", "red"] 26 27 // 在child1的color数组添加white 28 child1.color.push('white'); 29 console.log(child1.color); // ["pink", "red", "white"] 30 // child1上的改动,child2并没有受到影响 31 console.log(child2.color); // ["pink", "red"] 32 33 // 每个实例共享的属性 34 child1.sayHi(); // Hi 35 child2.sayHi(); // Hi
上述方法,虽然综合了原型链和借用构造函数的优点,达到了我们想要的结果,但是它存在一个问题。就是创建一次实例时,两次调用了父类构造函数。
1 // 父亲类 2 function Parent() { 3 this.color = ['pink', 'red']; 4 } 5 Parent.prototype.sayHi = function() { 6 console.log('Hi'); 7 } 8 9 // 儿子类 10 function Child() { 11 Parent.call(this); // 第二次调用构造函数:在新对象上创建一个color属性 12 } 13 14 Child.prototype = new Parent(); // 第一次调用构造函数Child.prototype将会得到一个color属性,屏蔽了原型中的color属性。
因此,出现了寄生组合式继承。在了解之前,我们先了解一下什么是寄生式继承。
4. 寄生式继承
同工厂模式类似,将我们需要继承的函数进行封装,然后进行某种增强,在返回对象。
1 function Parent() { 2 this.color = ['pink', 'red']; 3 } 4 5 6 function createAnother(o) { 7 // 获得当前对象的一个克隆 8 var another = new Object(o); 9 // 增强对象 10 o.sayHi = function() { 11 console.log('Hi'); 12 } 13 // 返回对象 14 return another; 15 }
5. 寄生组合式继承
1 // 创建只继承原型对象的函数 2 function inheritPrototype(parent, child) { 3 // 创建一个原型对象副本 4 var prototype = new Object(parent.prototype); 5 // 设置constructor属性 6 prototype.constructor = child; 7 child.prototype = prototype; 8 } 9 10 // 父亲类 11 function Parent() { 12 this.color = ['pink', 'red']; 13 } 14 Parent.prototype.sayHi = function() { 15 console.log('Hi'); 16 } 17 18 // 儿子类 19 function Child() { 20 Parent.call(this); 21 } 22 23 inheritPrototype(Parent, Child);
6. 原型式继承
思想:基于已有的对象创建对象。
1 function createAnother(o) { 2 // 创建一个临时构造函数 3 function F() { 4 5 } 6 // 将传入的对象作为它的原型 7 F.prototype = o; 8 // 返回一个实例 9 return new F(); 10 }