>

JS作为面向对象的弱类型语言,)原型链继承

- 编辑:澳门博发娱乐官网 -

JS作为面向对象的弱类型语言,)原型链继承

从实质认知JavaScript的原型承接和类承继

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

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

JavaScript发展到今日,和其余语言差异样的五个风味是,有美妙绝伦的“继承模式”,或许某个正确一点的说法,叫做有丰富多彩的依照prototype的模拟类承接完成形式。

在ES6在此之前,JavaScript未有类承接的定义,因而使用者为了代码复用的指标,只好参谋其余语言的“传承”,然后用prototype来模拟出对应的完成,于是有了种种承继方式,举例《JavaScript高端程序设计》上说的 原型链,借用构造函数,组合承继,原型式承继,寄生式继承,寄生组合式继承 等等

那么多一而再形式,让第贰遍接触这一块的友人们心中有个别崩溃。然则,之所以有那么多再而三形式,其实照旧因为“模拟”二字,因为我们在说继续的时候不是在商讨prototype本身,而是在用prototype和JS天性来模拟其他语言的类承接。

我们以后放弃这个类别好些个的一而再格局,来看一下prototype的原形和我们怎么要模拟类继承。

JS创设对象的两种格局:工厂方式,构造函数格局,原型格局,混合格局,动态原型形式

雄起雌伏是面向对象中多个比较基本的概念。其余专门的学业面向对象语言都会用三种艺术达成一连:三个是接口达成,一个是一连。而ECMAScript只帮助承袭,不协助接口完结,而完毕再三再四的不二诀要依靠原型链达成。

)原型链承袭

function Box() {//Box构造

this.name = 'xiaoming';

}

function Desk() {//Desk构造

this.age = 100;

}

Desk.prototype = new Box();//Desc承袭了Box,通过原型,产生链条

var desk = new Desk();

alert(desk.age);                           //100

alert(desk.name);//xiaoming得到被三翻五次的天性

function Table() {//Table构造

this.level = 'AAAAA';

}

Table.prototype = new Desk();//继续原型链承接

var table = new Table();

alert(table.name);//继承了Box和Desk

图片 1

原型链承继流程图

假若要实例化table,那么Desk实例中有age=100,原型中追加同样的习性age=200,最终结果是有一点点吧?

Desk.prototype.age = 200;//实例和原型中均包括age

PS:以上原型链继承还贫乏一环,那就是Obejct,全部的构造函数都一连自Obejct。而承接Object是机关达成的,并无需技师手动承继。

因此持续后的实例,他们的隶属关系会怎么着呢?

alert(table instanceof Object);//true

alert(desk instanceof Table);//false,desk是table的超类

alert(table instanceof Desk);//true

alert(table instanceof Box);//true

在JavaScript里,被三翻五次的函数称为超类型(父类,基类也行,其余语言叫法),承袭的函数称为子类型(子类,派生类)。承袭也可能有以前难题,比如字面量重写原型会中断关系,使用引用类型的原型,何况子类型还不能给超类型传递参数。

面向对象的言语都有多个类的定义,通过类能够创立八个颇具同样格局和性质的指标,ES6从前并从未类的定义,在ES6中引进类class.

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

原型继承

“原型” 那些词本人源自激情学,指故事、宗教、梦境、幻想、管理学中持续重复出现的意象,它源自由民主族纪念和原有经验的国有无意识。

之所以,原型是一种浮泛,代表事物表象之下的沟通,用简短的话来讲,便是原型描述事物与事物之间的形似性.

设想二个娃儿怎么样认识那一个世界:

当小孩子没见过文虎的时候,大人也许会教他,沙虫妈呀,就像一头大猫。假如这一个孩子刚刚日常和近邻家的小猫玩耍,那么他不用去动物园见到真实的苏门答腊虎,就可以想象出老虎大致是长什么样体统。

图片 2

其一旧事有个更简便的发挥,叫做“庸庸碌碌反类犬”。假使大家用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 "嗷";
}

于是,原型能够经过汇报多个东西之间的形似关系来复用代码,大家可以把这种复用代码的形式称为原型承袭。

 

原型链承继的劣点:

ES5 面向对象

JS承接的达成方式

类继承

几年现在,当时的孩儿长大了,随着他的学识结构不断丰硕,她认识世界的方法也时有产生了一部分变化,她学会了太多的动物,有喵喵叫的猫,百兽之王狮虎兽,优雅的黑蓝虎乌菟,还应该有豺狼、大象之类。

那时,单纯的相似性的体味格局已经相当少被应用在这么足够的知识内容里,尤其稳重的咀嚼方式——分类,开首被更频仍使用。

图片 3

此时当年的小孩子会说,猫和狗都以动物,若是他正要学习的是标准的生物学,她或者还或者会说猫和狗都以脊索门哺乳纲,于是,相似性被“类”这一种更加高水准的悬空表达替代,大家用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 "汪"}
}

1 在工厂形式中,在构造函数内部创制八个新对象,最终回来这几个目的。当实例化时,大家不必要用new关键字,就疑似调用方法同样就可以实例化。

1、 无法从子类中调用父类的构造函数, 那样就不曾艺术把子类中属性赋值给父类。

创制对象(三种格局简单介绍,其余还会有动态原型情势、寄生构造函数情势、安妥构造函数格局等)

一、工厂情势


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");


原型对象的领悟(主要)

1.第一得知道以下三点:

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

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

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

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

图片 4

2.万物皆对象,表达原型链的最开始点都以Object,所以任何七个引用类型的 instanceof Object都会回去true。


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

原型承接和类继承

从而,原型承继和类继承是二种认知格局,本质上都认为了架空(复用代码)。相对于类,原型更初级且更加灵活。因而当多少个连串内尚未太多关系的东西的时候,用原型显明比用类更加灵活便捷。

原型承接的便捷性表未来系统中指标相当少的时候,原型承袭不要求结构额外的抽象类和接口就足以兑现复用。(如系统里独有猫和狗三种动物来讲,没供给再为它们组织七个虚幻的“动物类”)

原型承接的灵活性还表以往复用情势更灵活。由于原型和类的方式不雷同,所以对复用的判断规范也就分裂,譬喻把二个革命皮球当做一个太阳的原型,当然是足以的(反过来也行),但肯定无法将“恒星类”当做太阳和红球的共用父类(倒是能够用“球体”那一个类作为它们的公共父类)。

既是原型本质上是一种认识格局能够用来复用代码,那大家为何还要模仿“类承接”呢?在那中间大家就得看看原型承接有怎样难点——

厂子情势的后天不足是轻易和日常函数混淆,只好通过命名来认同它是一个构造函数。不推荐使用这种格局。

2、 父类中属性是在子类的原型中的,那违背了我们近些日子所讲的包装的见识( 属性在指标中,方法在原型中), 会现身后边值的歪曲难题。

类的持续(两种办法)

一、原型链承接

        对于如何是原型链?

        逐个构造函数都有七个原型对象,原型对象的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


// 定义贰个动物类

原型承继的主题材料

是因为大家刚刚前边比方的猫和孟加拉虎的构造器未有参数,因而大家很恐怕没发掘标题,现在大家试验八个有参数构造器的原型承继:

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 再次来到值的不易,那是与任何模拟情势比较最大的实惠。

//factory pattern

2)对象冒充的点子承袭

为了化解引用分享和超类型无法传参的标题,大家选择一种叫借用构造函数的本事,或许成为目的冒充(伪造对象、杰出再而三)的技艺来消除那二种难题。

function Box(age) {

this.name = ['xiaoming', 'Jack', 'Hello']

this.age = age;

}

function Desk(age) {

Box.call(this, age);//对象冒充,给超类型传参

}

var desk = new Desk(200);

alert(desk.age);

alert(desk.name);

desk.name.push('AAA');//加多的新数据,只给desk

alert(desk.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

function Animal (name) {

模拟类承接

末段,大家应用这么些原理还足以达成相比较周全的类承继:

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);

function createPerson(name, age, job){
    var o = {};
    o.name = name;
    o.age = age;
    o.job = job;
    o.friends = ["Mike", "Sun"];
    o.sayName = function(){
        alert("factory pattern: " + this.name);
    }
    return o;
}

3)组合继承

借用构造函数即便减轻了刚刚三种难题,但尚无原型,复用则无从聊起。所以,大家需求原型链+借用构造函数的格局,这种形式改为构成承袭

function Box(age) {

this.name = ['xiaoming', 'Jack', 'Hello']

this.age = age;

}

Box.prototype.run = function () {

return this.name + this.age;

};

function Desk(age) {

Box.call(this, age);//对象冒充

}

Desk.prototype = new Box();//原型链承接

var desk = new Desk(100);

alert(desk.run());

组合式承接是JavaScript最常用的接续格局;但,组合式承继也可能有一些小标题,正是超类型在运用进程中会被调用四次:三回是创立子类型的时候,另贰次是在子类型构造函数的里边。

function Box(name) {

this.name = name;

this.family = ['哥哥','妹妹','父母'];

}

Box.prototype.run = function () {

return this.name;

};

function Desk(name, age) {

博克斯.call(this, name);//第贰遍调用Box

this.age = age;

}

Desk.prototype = new Box();//第一遍调用Box

// 属性

总结

原型传承和类承继是三种区别的体味方式,原型继承在对象不是相当多的简易利用模型里比类承继越来越灵敏方便。但是JavaScript的原型承继在语法上有贰个结构器额外调用的主题素材,我们只要通过 createObjWithoutConstructor 来延迟构造器的调用,就可以化解那么些题目。

3 赞 8 收藏 1 评论

图片 5

var Abby = createPerson("Abby", "22", "Softwarre Engineer");
Abby.sayName();

**4)空对象承接**

以上代码是事先的组合承接,那么用空对象直接接轨prototype方法,化解了两遍调用的主题材料。

function extend(Child, Parent) {

var F = function(){};

F.prototype = Parent.prototype;

Child.prototype = new F();

Child.prototype.constructor = Child;

//Child.uber = Parent.prototype;

}

本条extend函数,正是YUI库如何促成持续的方法。

除此以外,说Bellamy(Bellamy)些,函数体最终一行

Child.uber = Parent.prototype;

趣味是为子对象设二个uber属性,那个本性直接指向父对象的prototype属性。(uber是一个法语词,意思是"向上"、"上一层"。)那等于在子对象上开垦一条通道,能够一向调用父对象的方法。这一行放在这里,只是为着落到实处持续的完备性,纯属备用性质。

function Box(name) {

this.name = name;

this.arr = ['哥哥','妹妹','父母'];

}

Box.prototype.run = function () {

return this.name;

};

function Desk(name, age) {

Box.call(this, name);

this.age = age;

}

extend( Desk,Box);//通过此处达成持续

var desk = new Desk('xiaoming',100);

desk.arr.push('姐姐');

alert(desk.arr);

alert(desk.run());//只分享了办法

var desk2 = new Desk('Jack', 200);

alert(desk2.arr);//引用难题消除

5)拷贝承接

上边是利用prototype对象,完成持续。大家也足以换一种思路,纯粹选用"拷贝"方法达成持续。轻松说,假设把父对象的有所属性和章程,拷贝进子对象,不也能够落实持续吗?那样大家就有了第三种艺术。

率先,依旧把Animal的保有不改变属性,都置于它的prototype对象上。

function Animal(){}

Animal.prototype.species = "动物";

然后,再写三个函数,实现属性拷贝的指标。

function extend2(Child, Parent) {

var p = Parent.prototype;

var c = Child.prototype;

for (var i in p) {

c[i] = p[i];

}

c.uber = p; }

那么些函数的效劳,就是将父对象的prototype对象中的属性,一一拷贝给Child对象的prototype对象。

this.name = name || 'Animal';

 

6)ECMA6 extends人命关天字 类的承继

先来讲一说,es6的class语法

JavaScript语言的历史观艺术是透过构造函数,定义并生成新对象。上边是二个例子。

functionPoint(x,y){

this.x=x;

this.y=y;

}

Point.prototype.toString=function(){

return'('+this.x+', '+this.y+')';

};

varp=newPoint(1,2);

上边这种写法跟古板的面向对象语言(比方C++和Java)差距非常的大,很轻巧让新学习那门语言的技术员认为可疑。

ES6提供了更就好像古板语言的写法,引进了Class(类)这几个定义,作为靶子的模版。通过class关键字,能够定义类。基本上,ES6的class能够当作只是多个语法糖,它的绝大多数功能,ES5都足以做到,新的class写法只是让对象原型的写法越发清晰、更像面向对象编制程序的语法而已。上面的代码用ES6的“类”改写,就是底下那样

//定义类

classPoint{

constructor(x,y){

this.x=x;

this.y=y;

}

toString(){

return'('+this.x+', '+this.y+')';

}  }

地点代码定义了四个“类”,能够看来里面有七个constructor方法,那正是构造方法,而this关键字则表示实例对象。相当于说,ES5的构造函数Point,对应ES6的Point类的构造方法。

Point类除了构造方法,还定义了二个toString方法。注意,定义“类”的方法的时候,前面无需加上function那个尤为重要字,间接把函数定义放进去了就能够了。别的,方法之间无需逗号分隔,加了会报错。

ES6的类,完全能够看成构造函数的另一种写法。

classPoint{

// ...

}

typeofPoint// "function"

Point===Point.prototype.constructor// true

地方代码申明,类的数据类型正是函数,类自个儿就针对构造函数。

应用的时候,也是一贯对类使用new命令,跟构造函数的用法完全一致。

classBar{

doStuff(){

console.log('stuff');

}

}

varb=newBar();

b.doStuff()// "stuff"

构造函数的prototype属性,在ES6的“类”上边继续存在。事实上,类的装有办法都定义在类的prototype属性上边。

classPoint{

constructor(){

// ...

}

toString(){

// ...

}

toValue(){

// ...

}

}

//等同于

Point.prototype={

toString(){},

toValue(){}

};

在类的实例下面调用方法,其实就是调用原型上的法子

classB{}

letb=newB();

b.constructor===B.prototype.constructor// true

地点代码中,b是B类的实例,它的constructor方法便是B类原型的constructor方法。

出于类的艺术都定义在prototype对象方面,所以类的新格局能够增添在prototype对象方面。Object.assign方法能够很有利地壹次向类增添七个主意。

classPoint{

constructor(){

// ...

}

}

Object.assign(Point.prototype,{

toString(){},

toValue(){}

});

prototype对象的constructor属性,直接针对“类”的自家,那与ES5的一颦一笑是一致的。

Point.prototype.constructor===Point// true

类的实例对象须求注意:

生成类的实例对象的写法,与ES5截然一致,也是应用new命令。要是忘记加上new,像函数那样调用Class,将会报错。

/报错

varpoint=Point(2,3);

//正确

varpoint=newPoint(2,3);

不真实变量升高

Class官样文章变量进步(hoist),那或多或少与ES5一心两样。

newFoo();// ReferenceError

class Foo{}

地点代码中,Foo类使用在前,定义在后,那样会报错,因为ES6不会把类的扬言提高到代码底部。这种规定的始末与下文要涉及的三番陆次有关,必得确定保障子类在父类之后定义。

// 实例方法

2 构造函数方式,用new关键字来实例化对象。与工厂方式对待,使用构造函数情势创造对象,无需在函数内部重新创制对象,而采纳this指代,而且函数无需显明return。不推荐使用这种情势。

Class的继承

Class之间能够通过extends关键字贯彻持续,那比ES5的经过改变原型链完结三回九转,要清楚和有利广大。

class ColorPoint extends Point{}

上边代码定义了多少个ColorPoint类,该类通过extends关键字,承袭了Point类的具有属性和方法。不过由于尚未配置任何代码,所以那四个类完全同样,等于复制了多少个Point类。下边,大家在ColorPoint内部加上代码。

class ColorPoint extends Point{

constructor(x,y,color){

super(x,y);//调用父类的constructor(x, y)

this.color=color;

}

toString(){

returnthis.color+' '+super.toString();//调用父类的toString()

}    }

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

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

class Point{/* ... */}

class ColorPoint extends Point{

constructor(){

}

}

letcp=newColorPoint();// ReferenceError

上边代码中,ColorPoint承袭了父类Point,不过它的构造函数未有调用super方法,导致新建实例时报错。

ES5的接续,实质是先创设子类的实例对象this,然后再将父类的主意增加到this上边(Parent.apply(this))。ES6的持续机制完全分裂,实质是先创设父类的实例对象this(所以必得先调用super方法),然后再用子类的构造函数修改this。

万一子类没有定义constructor方法,这些艺术会被私下认可添加,代码如下。约等于说,不管有未有显式定义,任何二个子类都有constructor方法。

constructor(...args){

super(...args);

}

另一个必要细心的地方是,在子类的构造函数中,唯有调用super之后,才足以动用this关键字,不然会报错。那是因为子类实例的构建,是遵照对父类实例加工,只有super方法技术重临父类实例。

class Point{

constructor(x,y){

this.x=x;

this.y=y;

}

}

class ColorPoint extends Point{

constructor(x,y,color){

this.color=color;// ReferenceError

super(x,y);

this.color=color;//正确

}

}

上边代码中,子类的constructor方法未有调用super此前,就采纳this关键字,结果报错,而坐落super方法之后就是不错的。

上面是生成子类实例的代码。

letcp=newColorPoint(25,8,'green');

cpinstanceofColorPoint// true

cpinstanceofPoint// true

上边代码中,实例对象cp同期是ColorPoint和Point多个类的实例,那与ES5的一言一行完全一致。

this.sleep = function(){

构造函数的顽固的疾病是任何时间任何地点的正片,每new一次就造出三个别本,每一个方法都要在各样实例上再次创制一遍,明显那样是极度的,大家想要的是一种多少措施分享全部,有此方法私有,于是埃里克发明了原型链。

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

//constructor pattern
function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function(){
        alert("constructor pattern: " + this.name);
    }
}

}

var Abby = new Person("Abby", "22", "Software Engineer");
Abby.sayName();

}

 

// 原型方法

 

Animal.prototype.eat = function(food) {

3 原型方式,这里就要聊到prototype。大家创立的各类函数都有有贰个prototype(原型)属性,它也是三个对象,它的用处是含有有一定项目标具备实例的性质和艺术。不推荐使用这种格局。

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

上面例子,我们把持有办法三个个加上到prototype上,但出于prototype上情势属于一种分享,这一个格局有个别外人用的到,有个别外人根本用不到,有些外人想用的从未有过的法子还要再度往prototype上加上。那样就倒霉了,所以最常用的方式其实是混合型的。

};

//prototype pattern
function Abby(){}

1、原型链承接

Abby.prototype.name = "Abby";
Abby.prototype.age = "22";
Abby.prototype.sayName = function(){
    alert("prototype pattern: " + this.name);
}

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

var person1 = new Abby();
person1.sayName();

function Cat(){

 

}

 

Cat.prototype = new Animal();

4 构造函数格局和原型格局的犬牙相错类型。将具备属性不是措施的属性定义在函数中(构造函数情势),将享有属性值为格局的品质利用prototype在函数之外定义(原型方式)。 推荐使用这种方法创造对象。

Cat.prototype.name = 'cat';

//hybrid constructor & prototype pattern
function Student(name, sno){
  this.name = name;
  this.sno = sno;
  this.sayName = function(){
    alert("hybrid constructor & prototype pattern: " + this.name);
  }
}

// Test Code

Student.prototype = {
  constructor : Student,
  teacher : ["mike", "abby"],
  sayTeacher : function(){
    alert("hybrid constructor & prototype pattern(teacher): " + this.teacher);
  }
}

var cat = new Cat();

var zhangsan = new Student("zhangsan", "22");
var lisi = new Student("lisi", "23");
zhangsan.sayName();
lisi.sayName();
zhangsan.sayTeacher();
lisi.sayTeacher();

console.log(cat.name);

 

console.log(cat.eat('fish'));

 

console.log(cat.sleep());

5 动态原型情势

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

动态原型方式得以知道为混合形式的三个特例。该情势中,属性为方式的质量直接在函数中开展了定义,不过因为if从句进而确认保障开创该目的的实例时,属性的秘籍不会被重新创设。推荐使用这种情势。

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

//dynamic prototype pattern
function Person(){
  this.name = "Mike";
  this.age = 22;
}
if (typeof Person._lev == "undefined"){
   Person.prototype.lev = function(){
     return this.name;
   }
   Person._lev = true;
}

特点:

本文由胜博发-前端发布,转载请注明来源:JS作为面向对象的弱类型语言,)原型链继承