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     }