zoukankan      html  css  js  c++  java
  • 深入浅出理解Javascript原型概念以及继承机制

    在Javascript语言中,原型是一个经常被讨论到但是有非常让初学者不解的概念。那么,到底该怎么去给原型定义呢?不急,在了解是什么之前,我们不妨先来看下为什么。

    Javascript最开始是网景公司的死直男工程师Brendan Eich负责开发。起初设计的意愿非常简单,网景公司在1994年发布了Navigator浏览器0.9版(历史上第一个比较成熟的网络浏览器),这时候需要一个网页脚本语言,使得浏览器可以与网页互动。Brendan Eich认为这种语言无需复杂,尽量简单。然而Javascript里面都是对象,必须有一种机制,将所有对象联系起来,这就需要设计一个继承机制。为了维持Javascript的简单,Eich抛弃了传统面向对象语言中类的设计,而使用构造函数的方式去实现继承。比如下面一个构造函数:

    1 function Stark(name){
    2  this.name = name;
    3 }

    咳咳,为什么是Stark?因为博主是个骨灰级的冰与火之歌NC粉(●′艸`)ヾ 好好好言归正传,那么当我们要创建一个新的Stark对象时,仅需调用一个new命令:

    1 var branStark = new Stark('布兰');
    2 console.log(branStark .name); // 布兰

    然而这种继承方式有个最大的缺点,便是无法实现数据和方法的共享。比如

     1 function Stark(name){
     2  this.name = name;
     3  this.words = 'Winter is coming. ';
     4 }
     5 var branStark = new Stark('布兰');
     6 var aryaStark = new Stark('艾莉亚');
     7 Stark.words = 'Summer is coming.'
     8 
     9 console.log(branStark.words); //Winter is coming.
    10 console.log(aryaStark.words); //Winter is coming.

    可以看到,当Stark改变了家族族语words属性,布兰和艾莉亚的族语并没有改变。这是因为他们都有自己的一个Stark属性和方法副本。有时候这是好事,不会造成父子类数据上的混乱,但是有时候我们需要统一规划,使子类之间有数据的共享(大家一个家族的为毛不共用一个族语啊(╯‵□′)╯︵┻━┻ ),也节约资源上的开销。因此,Eich提出了一个概念:原型(prototype)。他为构造函数设置一个prototype属性。prototype属性包含一个对象,所有实例对象需要共享的属性和方法,都放在这个对象里面;那些不需要共享的属性和方法,就放在构造函数里面。实例对象一旦创建,将自动引用prototype对象的属性和方法。也就是说,实例对象的属性和方法,分成两种,一种是本地的,另一种是引用的。

     1 function Stark(name){
     2  this.name = name;
     3 }
     4 Stark.prototype.words = 'Winter is coming.';
     5 
     6 var branStark = new Stark('布兰');
     7 var aryaStark = new Stark('艾莉亚');
     8 
     9 console.log(branStark.words); //Winter is coming.
    10 console.log(aryaStark.words); //Winter is coming.
    11 
    12 Stark.prototype.words = 'Summer is coming.';
    13 console.log(branStark.words); //Summer is coming.
    14 console.log(aryaStark.words); //Summer is coming.

    可以看到,当Stark改变了words属性,布兰和艾莉亚的words属性也跟着改变了。这就实现了整齐划一~

    通过以上介绍,大家应该对Javascript中的prototype概念有了比较基础的了解。但是在实际应用中,从父类继承创建一个子类,还是没那么简单。子类有时候希望拥有自己的构造函数,这时候上面例子整齐划一的方法就不适用了。那么该如何保证子类既能继承父类的方法,又能保留自己的特色呢?别急,首先我们需要更深入一点,了解一下原型链(prototype chain)的概念。

    简单整理一下构造函数,原型和实例之间的关系:“每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针”。好,那我们看下以下代码:

     1 function Stark (){
     2   this.words = 'Winter is coming.';  
     3 }
     4 function BranStark(){
     5   this.pet = 'Summer';  
     6 }
     7 function Summer(){
     8   this.favor = 'meat';
     9 }
    10 BranStark.prototype = new Stark();
    11 Summer.prototype = new BranStark();
    12 
    13 summer = new Summer();
    14 
    15 console.log(summer.favor);  //meat
    16 console.log(summer.pet);  //Summer
    17 console.log(summer.words);  //Winter is coming.

    以上展示一个原型链继承的效果。我们让Summer的原型对象等于BranStark的实例,而BranStark的实例中包含一个指向BranStark原型对象的内部指针;而BranStark的原型对象又等于Stark的实例,Stark的实例中包含一个指向Stark原型对象的内部指针。这种关系可以层层递进,形成一条实例与原型的链条,这就是原型链。我在网上找了个图,大概是这样:

    在JS中实现继承,大概有两种思路:一个就是我们一开始所讲的使用构造函数;另一个就是上面例子所讲述的原型链继承。但两者各有利弊,构造函数继承会造成资源的浪费,方法和数据难以复用;原型链继承当有包含引用类型值的原型时,则容易造成数据上的混乱。请看下面例子:

     1 function Stark (){ }
     2 Stark.prototype.words =  ['Winter is coming'];
     3 function BranStark(){
     4  this.name = '布兰';
     5 }
     6 function AryaStark(){
     7  this.name = '艾莉亚';
     8 }
     9 
    10 BranStark.prototype = new Stark();
    11 AryaStark.prototype = new Stark();
    12 
    13 aryaStark = new AryaStark();
    14 console.log(aryaStark.words);  //['Winter is coming.']
    15 aryaStark.words.push('Needle is good.');
    16 console.log(aryaStark.words);  //['Winter is coming.', 'Needle is good.']
    17 
    18 branStark =  new BranStark();
    19 console.log(branStark.words);  //['Winter is coming.', 'Meat is good.']

     可以看到当原型内包含引用类型值时,子类实例aryaStark对words属性做出修改后,连其他子类也受到影响啦!Σ(  ̄д ̄;)艾莉亚你这个坑爹货!!咳,回顾下我们一开始所得到的结论:在实现继承的时候,所有实例对象需要共享的属性和方法,可以放在prototype对象里面;那些不需要共享的属性和方法,就放在构造函数里面。所以为了避免艾莉亚这种调皮孩子乱捣蛋,我们可以稍微做点改进!

     1 function Stark (){
     2   this.words = ['Winter is coming.'];  
     3 }
     4 function BranStark(){
     5  this.name = '布兰';
     6 }
     7 function AryaStark(){
     8  this.name = '艾莉亚';
     9 }
    10 
    11 BranStark.prototype = new Stark();
    12 BranStark.prototype.constructor = BranStark;
    13 AryaStark.prototype = new Stark();
    14 AryaStark.prototype.constructor = AryaStark;
    15 
    16 aryaStark = new AryaStark();
    17 console.log(aryaStark.words);  //['Winter is coming.']
    18 aryaStark.words.push('Needle is good.');
    19 console.log(aryaStark.words);  //['Winter is coming.', 'Needle is good.']
    20 
    21 branStark =  new BranStark();
    22 console.log(branStark.words);  //['Winter is coming.']

     _(:з」∠)_于是这样, 把需要被保护的数据放在构造函数就OK了,子类之间既能共享数据又能保证安全。这种继承方式也叫组合继承,是JS最常见的一种。注意到我增加了两行代码:

    1 BranStark.prototype.constructor = BranStark;
    2 AryaStark.prototype.constructor = AryaStark;

    解释下为什么:当我们把一个构造函数的prototype对象赋值给一个实例,我们相当于把该prototype对象完全删除,赋予一个新值。而每一个prototype对象都有一个constructor属性,指向它的构造函数。重新赋值后,constructor属性被改变,改为指向父类的构造函数。所以为了不会导致继承链的紊乱,我们需要手动把原型对象的构造函数指针重新指向自己。

    好了终于写完了,希望大家看得明白!( ̄▽ ̄)/

  • 相关阅读:
    CRL快速开发框架系列教程二(基于Lambda表达式查询)
    CRL快速开发框架系列教程一(Code First数据表不需再关心)
    非关系型数据库来了,CRL快速开发框架升级到版本4
    CRL快速开发框架开源完全转到Github
    火车订票系统的高并发解决方案
    用wordpress搭建个人博客
    centos/redhat安装mysql
    vue之nextTick全面解析
    微信公众号开发笔记3-sdk接入(nodejs)
    微信公众号开发笔记2(nodejs)
  • 原文地址:https://www.cnblogs.com/waitforwind/p/3674766.html
Copyright © 2011-2022 走看看