ES5与ES6类的后续解析,从实质认识JavaScript的原型继承和类继承

从精神认识JavaScript的原型继承和类继承

2016/04/06 · JavaScript
· 1 评论 ·
继承

初稿出处:
十年踪迹(@十年踪迹)   

JavaScript发展到今天,和其他语言差其余一个特征是,有丰裕多采的“继承格局”,或者有些准确一点的传教,叫做有多种多样的基于prototype的模拟类继承完毕形式。

在ES6此前,JavaScript没有类继承的定义,由此使用者为了代码复用的目标,只好参考其余语言的“继承”,然后用prototype来模拟出对应的兑现,于是有了各样继承方式,比如《JavaScript高级程序设计》上说的 原型链,借用构造函数,组合继承,原型式继承,寄生式继承,寄生组合式继承 等等

这就是说多延续情势,让第三回接触这一块的伙伴们心中有些崩溃。不过,之所以有那么多连续格局,其实照旧因为“模拟”二字,因为我们在说后续的时候不是在探讨prototype本身,而是在用prototype和JS特性来模拟其余语言的类继承。

咱俩前几天摒弃这一个项目繁多的接轨格局,来看一下prototype的本色和咱们为啥要模拟类继承。

JS作为面向对象的弱类型语言,继承也是其非常强劲的特性之一。那么什么样在JS中落到实处持续呢?让大家拭目以待。

面向对象的语言都有一个类的定义,通过类可以创建多个具有同等方法和特性的对象,ES6以前并不曾类的概念,在ES6中引入类class.

JS继承的贯彻格局

原型继承

“原型”
这么些词本身源自心绪学,指传说、宗教、梦境、幻想、管理学中连连重复现身的意境,它源自民族纪念和原有经验的共用无意识。

就此,原型是一种浮泛,代表事物表象之下的互换,用简易的话来说,就是原型描述事物与事物之间的形似性.

设想一个女孩儿怎么样认知那一个世界:

当儿童没见过老虎的时候,大人可能会教他,老虎呀,似乎一只大猫。即便这些孩子刚刚日常和邻居家的猫咪玩耍,那么他不用去动物园见到真实的大虫,就能想象出老虎大致是长什么样子。

亚洲必赢官网 1

本条故事有个更简约的发挥,叫做“邯郸学步”。如若大家用JavaScript的原型来叙述它,就是:

JavaScript

function Tiger(){ //… } Tiger.prototype = new Cat();
//老虎的原型是一只猫

1
2
3
4
5
function Tiger(){
    //…
}
 
Tiger.prototype = new Cat(); //老虎的原型是一只猫

很显眼,“画虎类犬”(或者反过来“照虎画猫”,也能够,取决孩子于先认识老虎如故先认识猫)是一种认知方式,它令人类小孩子不需求在脑海里再一次完全构建一只猛虎的全部音讯,而可以透过她熟习的猫咪的“复用”得到老虎的绝大部分新闻,接下去她只须要去到动物园,去考察老虎和猫咪的不相同部分,就可以正确认知什么是老虎了。那段话用JavaScript可以描述如下:

JavaScript

function Cat(){ } //小猫喵喵叫 Cat.prototype.say = function(){ return
“喵”; } //小猫会爬树 Cat.prototype.climb = function(){ return
“我会爬树”; } function Tiger(){ } Tiger.prototype = new Cat();
//老虎的叫声和小猫不一样,但老虎也会爬树 Tiger.prototype.say = function(){
return “嗷”; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function Cat(){
 
}
//小猫喵喵叫
Cat.prototype.say = function(){    
  return "喵";
}
//小猫会爬树
Cat.prototype.climb = function(){
  return "我会爬树";
}
 
function Tiger(){
 
}
Tiger.prototype = new Cat();
 
//老虎的叫声和小猫不同,但老虎也会爬树
Tiger.prototype.say = function(){
  return "嗷";
}

据此,原型可以因而讲述四个东西之间的相似关系来复用代码,大家可以把那种复用代码的情势称为原型继承。

JS继承的落实形式

ES5 面向对象

既然要完结持续,那么首先大家得有一个父类,代码如下:

类继承

几年过后,当时的少儿长大了,随着她的知识结构不断丰硕,她认识世界的格局也发出了一部分变化,她学会了太多的动物,有喵喵叫的猫,百兽之王狮子,优雅的树林之王老虎,还有豺狼、大象之类。

此时,单纯的相似性的体会格局已经很少被拔取在这么丰硕的学识内容里,尤其小心的回味格局——分类,早先被更频仍使用。

亚洲必赢官网 2

那儿当年的小朋友会说,猫和狗都是动物,若是他刚刚学习的是正统的生物学,她或许还会说猫和狗都是脊索门哺乳纲,于是,相似性被“类”这一种更高水准的悬空表明取代,我们用JavaScript来叙述:

JavaScript

class Animal{ eat(){} say(){} climb(){} … } class Cat extends Animal{
say(){return “喵”} } class Dog extends Animal{ say(){return “汪”} }

1
2
3
4
5
6
7
8
9
10
11
12
class Animal{
    eat(){}
    say(){}
    climb(){}
    …
}
class Cat extends Animal{
    say(){return "喵"}
}
class Dog extends Animal{
    say(){return "汪"}
}

既然要完结一而再,那么首先我们得有一个父类,代码如下:

创制对象(四种情势简介,别的还有动态原型情势、寄生构造函数方式、稳妥构造函数格局等)

一、工厂形式


function createPerson (Name,Age,Job) {

      var man= new Object();

      man.name= Name;

      man.age= Age;

      man.job= Job;

      man.sayName= function () {

              alert(this.name)

    }

  return  man;

}

var personOne=  createPerson (“Erric”,26,”Engineer”);

var personTwo=  createPerson (“Lori”,26,”teacher”);

优点:焚薮而田了多个一般对象的开创问题

缺点: ①  对象识别问题无法解决(即怎么精晓一个目的的门类)

二、构造函数方式

function Person (Name,Age,Job) {

      this.name = Name;

      this.age = Age;

      this.job= Job;

      this.sayName= function () {

              alert(this.name)

      }

}

var personOne=  new Person(“Erric”,26,”Engineer”);

var personTwo=  new Person(“Lori”,26,”teacher”);

注一:
若不使用new操作符直接调用函数,那么其属性和方法都会被添加到window对象里面(因为在大局意义域调用一个主意时,this总是指向window对象)

如: Person(“Erric”,26,”Enginee”)

        window.sayName()  //  弹出 “Erric”

          window.name            //  “Erric”

          window.age              //  26

注二: new 操作符实际上进行了以下操作

          ① 创造一个新的靶子

          ② 将构造函数的功能域赋给新目标(this指向了那个新的对象)

          ③ 执行构造函数中的代码(为那个新目的添加属性)

          ④ 重返这么些新的对象

优点:① 不用显式的成立对象

            ② 将性能和艺术赋给了this对象

            ③ 没有return语句

缺点:① 
各类方法都要在各样实例上再也创制四遍(personOne和personTwo中的sayName方法不是同一个措施,每个函数都是一个对象,故每 
定义了一个函数就实例化了一个对象)。

           
此题材也得以通过将艺术单独抽出来解决(可是方法一多,都移到全局的话封装性就无从谈起),如下:

            function Person (Name,Age,Job) {

                    this.name = Name;

                      this.age = Age;

                      this.job= Job;

                      this.sayName= sayName

            }

            function sayName() {

                    alert(this.name)

              }

            var personOne=  new Person(“Erric”,26,”Engineer”);

            var personTwo=  new Person(“Lori”,26,”teacher”);

            ② 如果将公共的sayName方法移到全局,那么又从未封装性可言了。


三、原型格局

function Person () {

}

Person.prototype.name= “Erric”

Person.prototype.age= “28”

Person.prototype.job= “Job”

Person.prototype.sayName= function () {

        alert(this.sayName)

}

优点:①  化解了函数共用的问题,不用每个实例都成立五次方法。

缺点:①  不可能传参

            ②
即使实例中修改了原型中的属性(引用类型)或措施,那么这么些特性或艺术会被彻底的修改,而影响到任何实例。


四、构造函数+原型组合格局

function Person (Name,Age,Job) {

          this.name= Name

          this.age= Age

          this.job= Job

}

Person.prototype.sayName= function () {

          alert(this.name)

}

//
下边往原型上添加属性和方式的也可正如写,不过此时原型的constructor不指向Person构造函数,而是指向Object,因为Person.prototype就好像一个新的目的实例,它的__proto__指向Object原型。

//  Person.prototype= {

          constructor: Person,            //
重新再实例中定义constructor的指向,覆盖Object原型中的constructor指向

          sayName: function () {

                  alert(this.name)

          }

}

var personOne=  new Person(“Erric”,26,”Engineer”);

var personTwo=  new Person(“Lori”,26,”teacher”);


ES5与ES6类的后续解析,从实质认识JavaScript的原型继承和类继承。原型对象的明白(紧要)

1.先是得了解以下三点:

① 每个函数(含构造函数)都有一个prototype属性,指向Person原型

② 每个实例都有一个__proto__属性,也指向Person原型

③ 每个原型都有一个constructor属性,指向其对应的构造函数

构造函数、实例、原型三者关系如下图:

亚洲必赢官网 3

2.万物皆目的,表明原型链的最开首点都是Object,所以任何一个引用类型的
instanceof Object都会回去true。


// 定义一个动物类

原型继承和类继承

为此,原型继承和类继承是二种认知形式,本质上都是为着架空(复用代码)。相对于类,原型更初级且更灵敏。因而当一个种类内尚未太多涉及的事物的时候,用原型显然比用类更灵敏便捷。

原型继承的便捷性表现在系统中目的较少的时候,原型继承不须求协会额外的抽象类和接口就可以已毕复用。(如系统里只有猫和狗二种动物来说,没要求再为它们协会一个虚无的“动物类”)

原型继承的灵活性还显示在复用方式尤其灵敏。由于原型和类的情势不平等,所以对复用的判定标准也就不均等,例如把一个红色皮球当做一个阳光的原型,当然是可以的(反过来也行),但显然不能将“恒星类”当做太阳和红球的公家父类(倒是可以用“球体”那一个类作为它们的公物父类)。

既是原型本质上是一种认知格局可以用来复用代码,那我们为啥还要模仿“类继承”呢?在那一个中大家就得看看原型继承有哪些问题——

// 定义一个动物类

类的接续(二种方式)

一、原型链继承

        对于怎么样是原型链?

       
每个构造函数都有一个原型对象,原型对象的constructor指向这些构造函数本身,而实例的__proto__特性又针对原型对象。那一个只要一个实例的__proto__内部指针指向其原型,而它的原型又是另一个档次的实例,那么它的原型又将对准另一个原型,另一个原型也含有一个针对它的构造函数的指针,倘使另一个原型又是另一个品类的实例,那样少见递进,就组成了实例与原型的链子,那就是原型链的基本概念。

兑现原型链的继续方式为主如下:

function Father () {

      this.appearance = “beautiful”

}

Father.prototype.sayHappy = function () {

        alert(“快乐”)

}

function Child () {

          this.name= “Jhon”

}

Child.prototype= new Father()        //  继承了父类的不二法门和总体性

Child.prototype.addArr= [1,2,3,4,5]

var child= new Child()
child.sayHappy()          //  弹出“快乐”
child.appearance        //  “beautiful”

child.addArr                      //  [1,2,3,4,5]

原型链继承的缺点:①  不可能传参  ②
若原型上的点猪时引用类型的话,不小心被修改了的话会影响其它实例。


二、借助构造函数继承(利用calll和apply改变this指针)

基本思路:在子类型构造函数的里边调用超类型的构造函数。

function Father (Hobby){

      this.hobby= Hobby

}

Father.prototype.sayHappy = function () {

      alert(“快乐”)

}

function Child () {

      this.name= “Jhon”

      Father.call(this,”Play Games”)          // 
或者Father.apply(this,[“Play Games”]),继承了Father的属性和章程

}

var child =  new Child()
child.sayHappy                //
从不反应,原型上的主意和特性不会持续
child.hobby                      //  “Play Games”

依傍构造函数继承的瑕疵:① 
措施都在构造函数中定义,函数的复用无从谈起    ② 
超类中的方法对子类不可知。


三、组合继承(也叫经典两次三番,将原型链和依赖构造函数继承相结合)

思路:1.原型链完结对原型属性和方法的后续;

            2.构造函数落成对实例属性的继续,且调用基类的构造函数;

function Father(Hobby) {

          this.hobby= Hobby;

          this.exGF = [‘cuihua’, ‘erya’]

}

Father.prototype.sayHappy = function () {

          alert(“快乐”)

}

function Child () {

          this.name= “Jhon”

          Father.call(this,”Play Games”)          // 
或者Father.apply(this,[“Play Games”]),继承了Father的性质和方法

}

Child.prototype= new Father()

Student.prototype.sayName= function () {

          alert(this.name);

}

var liHua= new Child()

liHua.sayHappy()

liHua.sayName()


检测对象属性的二种方法:

object.hasOwnProperty(属性名),那个点子检测的是目的实例的习性(借使再次回到true),无法检测原型上的特性。

in操作符,检测对象拥有的习性,包涵原型和实例上的额,有的话就回去true.


判断一个原型是不是在某个实例的原型链上:

Person.prototype.isPropotypeOf(personOne)    //  true

Object.prototype.isPropotypeOf(personOne)      //  true

看清一个构造函数是或不是在实例的原型链中出现过:

personOne instanceof Person                //  true

personOne instanceof Object                //  true


function Animal (name) {

原型继承的问题

鉴于大家刚刚前边举例的猫和老虎的构造器没有参数,因而大家很可能没察觉问题,现在咱们试验一个有参数构造器的原型继承:

JavaScript

function Vector2D(x, y){ this.x = x; this.y = y; }
Vector2D.prototype.length = function(){ return Math.sqrt(this.x *
this.x + this.y * this.y); } function Vector3D(x, y, z){
Vector2D.call(this, x, y); this.z = z; } Vector3D.prototype = new
Vector2D(); Vector3D.prototype.length = function(){ return
Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); } var
p = new Vector3D(1, 2, 3); console.log(p.x, p.y, p.z, p.length(), p
instanceof Vector2D);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Vector2D(x, y){
  this.x = x;
  this.y = y;
}
Vector2D.prototype.length = function(){
  return Math.sqrt(this.x * this.x + this.y * this.y);
}
 
function Vector3D(x, y, z){
  Vector2D.call(this, x, y);
  this.z = z;
}
Vector3D.prototype = new Vector2D();
 
Vector3D.prototype.length = function(){
  return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
}
 
var p = new Vector3D(1, 2, 3);
console.log(p.x, p.y, p.z, p.length(), p instanceof Vector2D);

上边那段代码里面大家见到大家用 Vector2D 的实例作为 Vector3D 的原型,在
Vector3D 的构造器里面大家还是能调用 Vector2D 的布局器来伊始化 x、y。

不过,若是认真探讨方面的代码,会意识一个小题目,在中等描述原型继承的时候:

JavaScript

Vector3D.prototype = new Vector2D();

1
Vector3D.prototype = new Vector2D();

咱俩实际上无参数地调用了一遍 Vector2D 的构造器!

这次调用是不必要的,而且,因为我们的 Vector2D
的构造器丰裕不难并且没有副功效,所以我们本次无谓的调用除了稍稍消耗了性能之外,并不会带动太严重的题材。

但在实际项目中,大家有些组件的构造器相比复杂,或者操作DOM,那么那种景观下无谓多调用三回构造器,显著是有可能引致惨重问题的。

于是,我们得想艺术克服这一回剩余的构造器调用,而拨云见日,大家发现我们得以不要求本次剩余的调用:

JavaScript

function createObjWithoutConstructor(Class){ function T(){}; T.prototype
= Class.prototype; return new T(); }

1
2
3
4
5
function createObjWithoutConstructor(Class){
    function T(){};
    T.prototype = Class.prototype;
    return new T();    
}

上面的代码中,大家经过成立一个空的协会器T,引用父类Class的prototype,然后回来new
T(
),来都行地避开Class构造器的履行。那样,大家实在可以绕开父类构造器的调用,并将它的调用时机延迟到子类实例化的时候(本来也理应那样才合理)。

JavaScript

function Vector2D(x, y){ this.x = x; this.y = y; }
Vector2D.prototype.length = function(){ return Math.sqrt(this.x *
this.x + this.y * this.y); } function Vector3D(x, y, z){
Vector2D.call(this, x, y); this.z = z; } Vector3D.prototype =
createObjWithoutConstructor(Vector2D); Vector3D.prototype.length =
function(){ return Math.sqrt(this.x * this.x + this.y * this.y +
this.z * this.z); } var p = new Vector3D(1, 2, 3); console.log(p.x,
p.y, p.z, p.length(), p instanceof Vector2D);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Vector2D(x, y){
  this.x = x;
  this.y = y;
}
Vector2D.prototype.length = function(){
  return Math.sqrt(this.x * this.x + this.y * this.y);
}
 
function Vector3D(x, y, z){
  Vector2D.call(this, x, y);
  this.z = z;
}
Vector3D.prototype = createObjWithoutConstructor(Vector2D);
 
Vector3D.prototype.length = function(){
  return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
}
 
var p = new Vector3D(1, 2, 3);
console.log(p.x, p.y, p.z, p.length(), p instanceof Vector2D);

诸如此类,大家缓解了父类构造器延迟构造的题材未来,原型继承就相比较适用了,并且那样简单处理以后,使用起来还不会潜移默化
instanceof 重返值的科学,那是与其它模拟方式比较最大的补益。

function Animal (name) {

ES6 面向对象

ES6中引入了Class(类)那几个概念,通过首要字class可以创设一个类。类的数据类型就是函数,类的有着办法都定义在prototype属性上。

class Person () {
        constructor (x,y) {
              this.name= x
              this.age= y
        }
        sayName () {
                alert(“快乐”)
        }
}
var liHua= new Person(“张俊泽”,26)

注:
可以领略为constuctor中的属性和办法为ES5中的构造函数部分,和constructor同级的是ES5中原型上的措施和性质。


ES6的连续通过extends关键字贯彻

class Father(){}
class Child extends Father {
        constructor(x,y,color){
                  super(x,y)
                  this.color= color
        }
        toString() {
                retunr “世界和平!”
        }
}

上边代码中,constructor方法和toString方法之中,都冒出了super关键字,它在此间代表父类的构造函数,用来新建父类的this对象。

子类必须在constructor方法中调用super方法,否则新建实例时会报错。那是因为子类没有协调的this对象,而是继续父类的this对象,然后对其开展加工。如果不调用super方法,子类就得不到this对象。


类的prototype和__proto__属性

Class作为构造函数的语法唐,同时有prototype和__proto__性能,因而存在两条继承链:

①  子类的__proto__,表示构造函数的持续,总是指向父类

② 
子类的prototype属性的__proto__属性,表示方法的接轨,总是指向父类的prototype属性。

class Father {

}

class Child extends Father{

          constructor () {

                  super()

          }

}

var childOne= new Child()

Child.__proto__ ==  Father        //  true

childOne.__proto__ ==  Child.prototype        //  true

Child.prototype.__proto__ ==  Fahter.prototype            //  true

// 属性

模拟类继承

末段,大家利用这一个规律仍是可以够兑现比较健全的类继承:

JavaScript

(function(global){“use strict” Function.prototype.extend =
function(props){ var Super = this; //父类构造函数 //父类原型 var TmpCls
= function(){ } TmpCls.prototype = Super.prototype; var superProto = new
TmpCls(); //父类构造器wrapper var _super = function(){ return
Super.apply(this, arguments); } var Cls = function(){
if(props.constructor){ //执行构造函数 props.constructor.apply(this,
arguments); } //绑定 this._super 的方法 for(var i in Super.prototype){
_super[i] = Super.prototype[i].bind(this); } } Cls.prototype =
superProto; Cls.prototype._super = _super; //复制属性 for(var i in
props){ if(i !== “constructor”){ Cls.prototype[i] = props[i]; } }
return Cls; } function Animal(name){ this.name = name; }
Animal.prototype.sayName = function(){ console.log(“My name is
“+this.name); } var Programmer = Animal.extend({ constructor:
function(name){ this._super(name); }, sayName: function(){
this._super.sayName(name); }, program: function(){ console.log(“I\”m
coding…”); } }); //测试大家的类 var animal = new Animal(“dummy”),
akira = new Programmer(“akira”); animal.sayName();//输出 ‘My name is
dummy’ akira.sayName();//输出 ‘My name is akira’ akira.program();//输出
‘I”m coding…’ })(this);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
(function(global){"use strict"
 
  Function.prototype.extend = function(props){
    var Super = this; //父类构造函数
 
    //父类原型
    var TmpCls = function(){
 
    }
    TmpCls.prototype = Super.prototype;
 
    var superProto = new TmpCls();
 
    //父类构造器wrapper
    var _super = function(){
      return Super.apply(this, arguments);
    }
 
    var Cls = function(){
      if(props.constructor){
        //执行构造函数
        props.constructor.apply(this, arguments);
      }
      //绑定 this._super 的方法
      for(var i in Super.prototype){
        _super[i] = Super.prototype[i].bind(this);
      }
    }
    Cls.prototype = superProto;
    Cls.prototype._super = _super;
 
    //复制属性
    for(var i in props){
      if(i !== "constructor"){
        Cls.prototype[i] = props[i];
      }
    }  
 
    return Cls;
  }
 
  function Animal(name){
    this.name = name;
  }
 
  Animal.prototype.sayName = function(){
    console.log("My name is "+this.name);
  }
 
  var Programmer = Animal.extend({
    constructor: function(name){
      this._super(name);
    },
    sayName: function(){
      this._super.sayName(name);
    },
    program: function(){
      console.log("I\"m coding…");
    }
  });
  //测试我们的类
  var animal = new Animal("dummy"),
      akira = new Programmer("akira");
  animal.sayName();//输出 ‘My name is dummy’
  akira.sayName();//输出 ‘My name is akira’
  akira.program();//输出 ‘I"m coding…’
 
})(this);

亚洲必赢官网,可以相比一下ES6的类继承:

JavaScript

(function(global){“use strict” //类的定义 class Animal {
//ES6中最新协会器 constructor(name) { this.name = name; } //实例方法
sayName() { console.log(“My name is “+this.name); } } //类的接轨 class
Programmer extends Animal { constructor(name) {
//直接调用父类构造器进行初阶化 super(name); } sayName(){
super.sayName(); } program() { console.log(“I\”m coding…”); } }
//测试大家的类 var animal = new Animal(“dummy”), akira = new
Programmer(“akira”); animal.sayName();//输出 ‘My name is dummy’
akira.sayName();//输出 ‘My name is akira’ akira.program();//输出 ‘I”m
coding…’ })(this);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
(function(global){"use strict"
 
  //类的定义
  class Animal {
    //ES6中新型构造器
      constructor(name) {
          this.name = name;
      }
      //实例方法
      sayName() {
          console.log("My name is "+this.name);
      }
  }
 
  //类的继承
  class Programmer extends Animal {
      constructor(name) {
        //直接调用父类构造器进行初始化
          super(name);
      }
      sayName(){
          super.sayName();
      }
      program() {
          console.log("I\"m coding…");
      }
  }
  //测试我们的类
  var animal = new Animal("dummy"),
      akira = new Programmer("akira");
  animal.sayName();//输出 ‘My name is dummy’
  akira.sayName();//输出 ‘My name is akira’
  akira.program();//输出 ‘I"m coding…’
 
})(this);

// 属性

this.name = name || ‘Animal’;

总结

原型继承和类继承是三种不一致的咀嚼形式,原型继承在目的不是成百上千的简便利用模型里比类继承更加灵敏方便。不过JavaScript的原型继承在语法上有一个结构器额外调用的题材,大家借使经过
createObjWithoutConstructor 来延迟构造器的调用,就能一举成功那么些问题。

3 赞 8 收藏 1
评论

亚洲必赢官网 4

this.name = name || ‘Animal’;

// 实例方法

// 实例方法

this.sleep = function(){

this.sleep = function(){

console.log(this.name + ‘正在睡觉!’);

console.log(this.name + ‘正在睡觉!’);

}

}

}

}

// 原型方法

// 原型方法

Animal.prototype.eat = function(food) {

Animal.prototype.eat = function(food) {

console.log(this.name + ‘正在吃:’ + food);

console.log(this.name + ‘正在吃:’ + food);

};

};

1、原型链继承

1、原型链继承

核心:将父类的实例作为子类的原型

核心:将父类的实例作为子类的原型

function Cat(){

function Cat(){

}

}

Cat.prototype = new Animal();

Cat.prototype = new Animal();

Cat.prototype.name = ‘cat’;

Cat.prototype.name = ‘cat’;

// Test Code

// Test Code

var cat = new Cat();

var cat = new Cat();

console.log(cat.name);

console.log(cat.name);

console.log(cat.eat(‘fish’));

console.log(cat.eat(‘fish’));

console.log(cat.sleep());

console.log(cat.sleep());

console.log(cat instanceof Animal); //true

console.log(cat instanceof Animal); //true

console.log(cat instanceof Cat); //true

console.log(cat instanceof Cat); //true

特点:

特点:

非常纯粹的延续关系,实例是子类的实例,也是父类的实例

那一个纯粹的继承关系,实例是子类的实例,也是父类的实例

父类新增原型方法/原型属性,子类都能访问到

父类新增原型方法/原型属性,子类都能访问到

几乎,易于落到实处

简简单单,易于落到实处

缺点:

缺点:

要想为子类新增属性和措施,必须求在new
Animal()那样的言辞之后执行,不可能松手构造器中

要想为子类新增属性和办法,必须要在new
Animal()那样的语句之后执行,不可能放手构造器中

没辙兑现多连续

手足无措达成多连续

来自原型对象的引用属性是有着实例共享的(详细请看附录代码:示例1;))

源点原型对象的引用属性是兼具实例共享的(详细请看附录代码:示例1;))

创办子类实例时,无法向父类构造函数传参

创立子类实例时,不能向父类构造函数传参

推荐指数:★★(3、4两差不多命缺陷)

推介指数:★★(3、4两大约命缺点)

2、构造继承

2、构造继承

核心:动用父类的构造函数来增长子类实例,等于是复制父类的实例属性给子类(没用到原型)

核心:应用父类的构造函数来升高子类实例,等于是复制父类的实例属性给子类(没用到原型)

function Cat(name){

function Cat(name){

Animal.call(this);

Animal.call(this);

this.name = name || ‘Tom’;

this.name = name || ‘Tom’;

}

}

// Test Code

// Test Code

var cat = new Cat();

var cat = new Cat();

console.log(cat.name);

console.log(cat.name);

console.log(cat.sleep());

console.log(cat.sleep());

console.log(cat instanceof Animal); // false

console.log(cat instanceof Animal); // false

console.log(cat instanceof Cat); // true

console.log(cat instanceof Cat); // true

特点:

特点:

解决了1中,子类实例共享父类引用属性的题材

缓解了1中,子类实例共享父类引用属性的题材

成立子类实例时,能够向父类传递参数

创设子类实例时,可以向父类传递参数

能够已毕多三番四回(call三个父类对象)

可以完结多再而三(call多少个父类对象)

缺点:

缺点:

实例并不是父类的实例,只是子类的实例

实例并不是父类的实例,只是子类的实例

不得不一连父类的实例属性和措施,不可以持续原型属性/方法

只可以继续父类的实例属性和方法,不可能再三再四原型属性/方法

不可以落成函数复用,每个子类都有父类实例函数的副本,影响属性

不可能已毕函数复用,每个子类都有父类实例函数的副本,影响属性

推荐指数:★★(缺点3)

推介指数:★★(缺点3)

3、实例继承

3、实例继承

核心:为父类实例添加新特性,作为子类实例重临

核心:为父类实例添加新特征,作为子类实例再次回到

function Cat(name){

function Cat(name){

var instance = new Animal();

var instance = new Animal();

instance.name = name || ‘Tom’;

instance.name = name || ‘Tom’;

return instance;

return instance;

}

}

// Test Code

// Test Code

var cat = new Cat();

var cat = new Cat();

console.log(cat.name);

console.log(cat.name);

console.log(cat.sleep());

console.log(cat.sleep());

console.log(cat instanceof Animal); // true

console.log(cat instanceof Animal); // true

console.log(cat instanceof Cat); // false

console.log(cat instanceof Cat); // false

特点:

特点:

不限定调用情势,不管是new 子类()如故子类(),再次回到的目标拥有同样的效能

不限制调用方式,不管是new 子类()如故子类(),再次来到的靶子拥有同等的职能

缺点:

缺点:

实例是父类的实例,不是子类的实例

实例是父类的实例,不是子类的实例

不帮衬多一而再

不辅助多一连

推介指数:★★

推荐指数:★★

4、拷贝继承

4、拷贝继承

function Cat(name){

function Cat(name){

var animal = new Animal();

var animal = new Animal();

for(var p in animal){

for(var p in animal){

Cat.prototype[p] = animal[p];

Cat.prototype[p] = animal[p];

}

}

Cat.prototype.name = name || ‘Tom’;

Cat.prototype.name = name || ‘Tom’;

}

}

// Test Code

// Test Code

var cat = new Cat();

var cat = new Cat();

console.log(cat.name);

console.log(cat.name);

console.log(cat.sleep());

console.log(cat.sleep());

console.log(cat instanceof Animal); // false

console.log(cat instanceof Animal); // false

console.log(cat instanceof Cat); // true

console.log(cat instanceof Cat); // true

特点:

特点:

支撑多三番五次

支撑多接二连三

缺点:

缺点:

频率较低,内存占用高(因为要拷贝父类的性能)

频率较低,内存占用高(因为要拷贝父类的特性)

不可以获取父类不可胜数的章程(成千上万方法,无法应用for in 访问到)

不可能得到父类不可计数的不二法门(成千成万方法,无法运用for in 访问到)

推荐指数:★(缺点1)

引进指数:★(缺点1)

5、组合继承

5、组合继承

核心:透过调用父类构造,继承父类的特性并保留传参的独到之处,然后经过将父类实例作为子类原型,完成函数复用

核心:透过调用父类构造,继承父类的习性并保留传参的长处,然后通过将父类实例作为子类原型,完成函数复用

function Cat(name){

function Cat(name){

Animal.call(this);

Animal.call(this);

this.name = name || ‘Tom’;

this.name = name || ‘Tom’;

}

}

Cat.prototype = new Animal();

Cat.prototype = new Animal();

// Test Code

// Test Code

var cat = new Cat();

var cat = new Cat();

console.log(cat.name);

console.log(cat.name);

console.log(cat.sleep());

console.log(cat.sleep());

console.log(cat instanceof Animal); // true

console.log(cat instanceof Animal); // true

console.log(cat instanceof Cat); // true

console.log(cat instanceof Cat); // true

特点:

特点:

弥补了法子2的缺陷,可以继续实例属性/方法,也得以接二连三原型属性/方法

弥补了章程2的通病,可以持续实例属性/方法,也足以继续原型属性/方法

既是子类的实例,也是父类的实例

既是子类的实例,也是父类的实例

不存在引用属性共享问题

不设有引用属性共享问题

可传参

可传参

函数可复用

函数可复用

缺点:

缺点:

调用了五次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)

调用了一遍父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)

推荐指数:★★★★(仅仅多损耗了好几内存)

推介指数:★★★★(仅仅多消耗了少数内存)

6、寄生组合继承

6、寄生组合继承

核心:因此寄生方式,砍掉父类的实例属性,这样,在调用五次父类的构造的时候,就不会起初化两回实例方法/属性,幸免的咬合继承的败笔

核心:经过寄生方式,砍掉父类的实例属性,这样,在调用三遍父类的布局的时候,就不会初叶化三遍实例方法/属性,防止的构成继承的瑕疵

function Cat(name){

function Cat(name){

Animal.call(this);

Animal.call(this);

this.name = name || ‘Tom’;

this.name = name || ‘Tom’;

}

}

(function(){

(function(){

// 创制一个从未有过实例方法的类

// 创制一个并未实例方法的类

var Super = function(){};

var Super = function(){};

Super.prototype = Animal.prototype;

Super.prototype = Animal.prototype;

//将实例作为子类的原型

//将实例作为子类的原型

Cat.prototype = new Super();

Cat.prototype = new Super();

})();

})();

// Test Code

// Test Code

var cat = new Cat();

var cat = new Cat();

console.log(cat.name);

console.log(cat.name);

console.log(cat.sleep());

console.log(cat.sleep());

console.log(cat instanceof Animal); // true

console.log(cat instanceof Animal); // true

console.log(cat instanceof Cat); //true

console.log(cat instanceof Cat); //true

特点:

堪称完美

缺点:

兑现相比复杂

推介指数:★★★★(已毕复杂,扣掉一颗星)

附录代码:

示例一:

function Animal (name) {

// 属性

this.name = name || ‘Animal’;

// 实例方法

this.sleep = function(){

console.log(this.name + ‘正在睡觉!’);

}

//实例引用属性

this.features = [];

}

function Cat(name){

}

Cat.prototype = new Animal();

var tom = new Cat(‘Tom’);

var kissy = new Cat(‘Kissy’);

console.log(tom.name); // “Animal”

console.log(kissy.name); // “Animal”

console.log(tom.features); // []

console.log(kissy.features); // []

tom.name = ‘Tom-New Name’;

tom.features.push(‘eat’);

//针对父类实例值类型成员的转移,不影响

console.log(tom.name); // “Tom-New Name”

console.log(kissy.name); // “Animal”

//针对父类实例引用类型成员的更改,会通过影响其余子类实例

console.log(tom.features); // [‘eat’]

console.log(kissy.features); // [‘eat’]

网站地图xml地图