首页 > 技术文章 > 原型链与继承

helloCindy 2020-02-13 21:30 原文

创建对象的四种方式:

1.字面量方式

2.new Object()

3.构造函数创建

4.Object.create()

 1 // 1.字面量创建
 2 var o1 = {
 3     name: 'o1'
 4 }
 5 
 6 // 2.通过new Object()创建
 7 var o2 = new Object({
 8     name: 'o2'
 9 })
10 
11 // 3.通过构造函数创建
12 var M = function() {
13     this.name = 'o3'
14 }
15 var o3 = new M();
16 
17 // 4.通过Object.create()创建
18 var P = {
19     name: 'o4'
20 }
21 var o4 = Object.create(P);

构造函数,new在执行时会做四件事:

1.在内存中创建一个新的空对象

2.让this指向这个新对象

3.执行构造函数里的代码,给这个新对象添加属性和方法

4.返回这个新对象(所以构造函数里不需要return)

 

原型链:从一个实例对象网上找构造这个实例的相关联的对象,然后这个关联的对象再网上找,它又有创造它的上一级的原型对象,以此类推,一直到Object.prototype原型对象终止,那么这个链条就断了。也就是说,Object.prototype属性是整个原型链的顶端,到这儿就截止啦!(原型的作用就是共享方法)

 

 

 instanceof原理

判断实例对象的_proto_属性与构造函数的prototype是不是同一个引用,如果不是,它会沿着对象的_proto_向上查找,直到顶端Object。也即判断对象实例与构造函数是否在一个原型链上。

 

继承的三种方法

1.借助构造函数实现继承

 1 // 借助构造函数实现继承
 2 function Parent1() {
 3     this.name = 'Parent1';
 4 }
 5 Parent1.prototype.say = function() {};
 6 
 7 function Child1() {
 8     Parent1.call(this);
 9     this.type = 'Child1';
10 }
11 console.log(new Parent1());
12 console.log(new Child1()); // 没有参数的时候加不加括号都可以

 

 

原理: 将子类的this使用父类的构造函数跑一遍 
缺点: Parent原型链上的属性和方法并不会被子类继承

这里通过 Parent1.call(this)将父级的构造函数上的this指向子构造函数的实例上去。Parent1原型链上的东西没有被Child1继承,例如这里的say方法就没有被继承。

如果父类上的属性都在构造函数里面,那么完全可以实现继承。但如果父类的原型对象上还有其他方法的话,子类则继承不了。

 

2.借助原型链实现继承

 1 // 借助构造函数实现继承
 2 function Parent2() {
 3     this.name = 'Parent2';
 4     this.play = [1, 2, 3];
 5 }
 6 
 7 function Child2() {
 8     this.type = 'Child2';
 9 }
10 Child2.prototype = new Parent2();
11 var s1 = new Child2();
12 var s2 = new Child2();
13 s1.play.push(4);
14 console.log(new Child2());
15 console.log(s1.play, s2.play);
16 s1.name = 'js';
17 console.log(s1.name, s2.name);

 

 

原理:把子类的prototype(原型对象)直接设置为父类的实例
缺点:因为子类只进行一次原型更改,所以子类的所有实例保存的是同一个父类的值。
当子类对象上进行值修改时,如果是修改的原始类型的值,那么会在实例上新建这样一个值;
但如果是引用类型的话,他就会去修改子类上唯一一个父类实例里面的这个引用类型,这会影响所有子类实例

 

3.组合方式实现继承(使用call继承和原型链继承)

 1 // 组合方式实现继承
 2 function Parent3() {
 3     this.name = 'Parent3';
 4     this.play = [1, 2, 3];
 5 }
 6 
 7 function Child3() {
 8     Parent3.call(this);
 9     this.type = 'Child3';
10 }
11 Child3.prototype = new Parent3();
12 var s3 = new Child3();
13 var s4 = new Child3();
14 s3.play.push(4);
15 console.log(new Child3());
16 console.log(s3.play, s4.play);
 
原理: 子类构造函数中使用Parent.call(this)的方式可以继承写在父类构造函数中this上绑定的各属性和方法; 
使用Child.prototype = new Parent()的方式可以继承挂在在父类原型上的各属性和方法
缺点:  父类构造函数在子类构造函数中执行了一次,在子类绑定原型时又执行了一次
 
3.1组合继承的优化1
 1 // 组合继承的优化1
 2 function Parent4() {
 3     this.name = 'Parent4';
 4     this.play = [1, 2, 3];
 5 }
 6 
 7 function Child4() {
 8     Parent4.call(this);
 9     this.type = 'Child4';
10 }
11 Child4.prototype = Parent4.prototype;
12 var s5 = new Child4();
13 var s6 = new Child4();
14 Child4.prototype.say = function() {};
15 console.log(new Child4(), new Parent4);
16 console.log(s5.play, s6.play);
17 console.log(s5 instanceof Child4, s5 instanceof Parent4);
18 console.log(s5.constructor);

构造函数体内通过两个构造函数组合能拿到所有构造函数体内的属性和方法,现在是想继承父类的原型对象,所以直接赋值给子类父类的原型对象就行了。

缺点: 因为原型上有一个属性为constructor,此时直接使用父类的prototype的话那么会导致 实例的constructor为Parent,即不能区分这个实例对象是Child的实例还是父类的实例对象。子类不可直接在prototype上添加属性和方法,因为会影响父类的原型,这里给子类添加一个say方法,可以看到父类上也连带有了say方法。
注意:这个时候instanseof是可以判断出实例为Child的实例的,因为instanceof的原理是沿着对象的__proto__判断是否有一个原型是等于该构造函数的原型的。这里把Child的原型直接设置为了父类的原型,那么: 实例.__proto__ === Child.prototype === Parent.prototype
 
3.2组合继承的优化2
 1 // 组合继承的优化2
 2 function Parent5() {
 3     this.name = 'Parent5';
 4     this.play = [1, 2, 3];
 5 }
 6 
 7 function Child5() {
 8     Parent5.call(this);
 9     this.type = 'Child5';
10 }
11 Child5.prototype = Object.create(Parent5.prototype);
12 Child5.prototype.constructor = Child5;
13 var s7 = new Child5();
14 console.log(s7 instanceof Child5, s7 instanceof Parent5);
15 console.log(s7.constructor);

Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。通过创建中间对象的方法就把两个原型对象区分开,这个中间对象还具备的一个特性就是它的原型对象是父类的原型对象。该方法解决了无法区分实例是由子类还是父类创建的问题。

 

 

 

推荐阅读