前言:上篇文章(发布在我的segmentfault上)介绍了js中通过构造函数来实例化对象的各种方法js构造函数,这篇文章主要介绍构造函数的继承(类的继承),同样包括 ES5 和 ES6 两部分的介绍,能力所限,文中难免有不合理或错误的地方,还望批评指正~
原型
首先简单介绍一下实例属性/方法 和 原型属性/方法,以便更好理解下文
1 2 3 4 5 6 7 8 9
| function Persion(name){ this.name = name; this.setName = function(nameName){ this.name = newName; } } Persion.prototype.sex = 'man';
var persion = new Persion('zhang');
|
通过 prototype 添加的属性将出现在实例对象的原型链中,
每个对象都会有一个内置 proto 对象,当在当前对象中找不到属性的时候就会在其原型链中查找(即原型链)
我们再来看下面的例子
注意:在构造函数中,一般很少有数组形式的引用属性,大部分情况都是:基本属性 + 方法。
1 2 3 4 5 6 7 8 9 10 11
| function Animal(n) { this.name = n; this.arr = []; this.say = function(){ return 'hello world'; } } Animal.prototype.sing = function() { return '吹呀吹呀,我的骄傲放纵~~'; } Animal.prototype.pArr = [];
|
接下来我们看一下实例属性/方法 和 原型属性/方法的区别
原型对象的用途是为每个实例对象存储共享的方法和属性,它仅仅是一个普通对象而已。并且所有的实例是共享同一个原型对象,因此有别于实例方法或属性,原型对象仅有一份。而实例有很多份,且实例属性和方法是独立的。
1 2 3 4 5 6 7 8 9 10
| var cat = new Animal('cat'); var dog = new Animal('dog');
cat.say === dog.say cat.sing === dog.sing
cat.arr.push('zz'); cat.pArr.push('xx'); console.log(dog.arr); console.log(dog.pArr);
|
当然,原型属性为基本数据类型,则不会被共享
在构造函数中:为了属性(实例基本属性)的私有性、以及方法(实例引用属性)的复用、共享。我们提倡:
1、将属性封装在构造函数中
2、将方法定义在原型对象上
ES5继承方式
首先,我们定义一个Animal父类
1 2 3 4 5 6 7 8 9 10 11
| function Animal(n) { this.name = n; this.arr = []; this.say = function(){ return 'hello world'; } } Animal.prototype.sing = function() { return '吹呀吹呀,我的骄傲放纵~~'; } Animal.prototype.pArr = [];
|
原型链继承
1 2 3 4 5 6 7 8 9 10
| function Cat(n) { this.cName = n; } Cat.prototype = new Animal();
var tom = new Cat('tom'); var black = new Cat('black');
tom.arr.push('Im tom'); console.log(black.arr);
|
优点: 实现了子对象对父对象的实例 方法/属性 和 原型方法/属性 的继承;
缺点: 子类实例共享了父类构造函数的引用数据类型属性。
借用构造函数
1 2 3 4 5 6 7 8 9 10 11
| function Cat(n) { this.cName = n; Animal.call(this, this.cName); }
var tom = new Cat('tom'); var black = new Cat('black');
tom.arr.push('Im tom'); console.log(black.arr); tom.sing();
|
优点:
1、实现了子对象对父对象的实例 方法/属性 的继承,每个子类实例所继承的父类实例方法和属性都是其私有的;
2、 创建子类实例,可以向父类构造函数传参数;
缺点: 子类实例不能继承父类的构造属性和方法;
组合继承
1 2 3 4 5 6 7 8 9 10
| function Cat(n) { this.cName = n; Animal.call(this, this.cName); } Cat.prototype = new Parent() Cat.prototype.constructor = Cat;
tom.arr.push('Im tom'); console.log(black.arr); tom.sing();
|
优点:
1、创建子类实例,可以向父类构造函数传参数;
2、父类的实例方法定义在父类的原型对象上,可以实现方法复用;
3、不共享父类的构造方法及属性;
缺点: 调用了2次父类的构造方法
寄生组合继承
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function Cat(n) { this.cName = n; Animal.call(this, this.cName); } Cat.prototype = Parent.prototype; Cat.prototype.constructor = Cat;
tom.arr.push('Im tom'); console.log(black.arr); tom.sing(); tom.pArr.push('publish'); console.log(black.pArr);
Cat.prototype.childrenProp = '我是子类的原型属性!'; var parent = new Animal('父类'); console.log(parent.childrenProp);
|
优点:
1、创建子类实例,可以向父类构造函数传参数;
2、子类的实例不共享父类的构造方法及属性;
3、只调用了1次父类的构造方法;
缺点: 父类和子类使用了同一个原型,导致子类的原型修改会影响父类;
寄生组合继承(简直完美)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function Cat(n) { this.cName = n; Animal.call(this, this.cName); } var F = function(){}; F.prototype = Parent.prototype; Cat.prototype = new F(); Cat.prototype.constructor = Cat; tom.arr.push('Im tom'); console.log(black.arr); tom.sing(); tom.pArr.push('publish'); console.log(black.pArr); Cat.prototype.childrenProp = '我是子类的原型属性!'; var parent = new Animal('父类'); console.log(parent.childrenProp);
|
优点: 完美实现继承;
缺点:实现相对复杂
附YUI库实现继承
1 2 3 4 5 6 7 8 9
| function extend(Child, Parent) { var F = function(){}; F.prototype = Parent.prototype; hild.prototype = new F(); Child.prototype.constructor = Child; Child.uber = Parent.prototype; }
extend(Cat,Animal);
|
Child.uber = Parent.prototype;
的意思是为子对象设一个uber属性,这个属性直接指向父对象的prototype
属性。(uber是一个德语词,意思是”向上”、”上一层”。)这等于在子对象上打开一条通道,可以直接调用父对象的方法。这一行放在这里,只是为了实现继承的完备性,纯属备用性质。
ES6继承方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class Animal{ constructor(name){ this.name=name; } eat(){ return 'hello world'; } } class Cat extends Animal{ constructor(name){ super(name); this.pName = name; } sing(){ return '吹呀吹呀,我的骄傲放纵~~'; } }
|
评论区 (请完善信息用于邮件接收评论的反馈)