• javascript的原型和继承(1)


          原型与继承是javascript中基础,重要而相对比较晦涩难解的内容。在图灵的网上看到一篇翻译过的文章,有参考了一些知名博客。我自己总结了几篇。通过这次的总结,感觉自己对原型和继承的认识又增加了很多,究其原因,主要这次,是从语言设计者的角度,理解了当初的原型和继承为什么这么设计,

        转换角度后,理解起来就简单多了。

        先从历史介绍一下,看起来啰嗦,其实还挺有必要的。

        1994年,网景公司(Netscape)发布了Navigator浏览器0.9版。是历史上第一个比较成熟的网络浏览器,轰动一时。但是,这个版本的浏览器只能用来浏览,不具备与访问者互动的能力。比如,如果网页上有一栏"用户名"要求填写,浏览器就无法判断访问者是否真的填写了,只有让服务器端判断。如果没有填写,服务器端就返回错误,要求用户重新填写,这太浪费时间和服务器资源了。

         网景公司急需一种网页脚本语言,使得浏览器可以与网页互动。工程师brendan Eich负责开发这种新语言。他觉得,没必要设计的很复杂,这种语言只要能够完成一些简单的操作就够了,比如,判断用户有没有填写表单。

        1994年正式面向对象编程最兴盛的时期。C++是当时最流行的语言。而java语言也即将推出。Brendan无疑受到了影响,Javascript里面所有的数据类型都是对象(Object)。这一点和java很相似。随即他就遇到了一个难题。到底要不要设计“继承”机制呢?

         如果真的是简易的语言,是不需要有继承机制的。但因为javascript里面都是对象,为了将所有的对象联系起来,Brendan最后还是设计了继承。

        但他并没有打算引入“类”的概念,引入类,Javascript就变成彻底的面向对象了。这好像太正式了,而且增加了初学者的难度。综合了c++和java,他也把new命令引入了javascript,用来从原型对象生成一个实例对象。

        下一个问题,Javascript没有“类”,怎么来表示原型对象呢?

        他想到,C++和java是用new命令时,都会调用“类”的构造函数(Constructor)。他就做了一个简化的设计,在javascript语言中,New命令后面跟的不是类,而是一个构造函数。

        -----------------看到这里,是不是有一种一步步接近真相的感觉呢?   

        举例来说,现在有一个叫做dog的构造函数,表示狗对象的原型。

    function Dog(name){
        this.name = name;
    }

         对这个构造函数使用new,就会生成一个狗对象的实例。 

    var dogA = new Dog(“金毛”);
    alert(dogA.name); //金毛

    new运算符的缺点

    用构造函数生成实例对象,有一个缺点。那就是无法共享属性和方法。 

    原博客这里介绍的不够仔细,我这边再着重说一下:

    function DOG(name){
        this.name = name;
        this.dinner = ["meat","water"];  //看这里看这里
    }
    var dogA = new DOG(“大毛”);
    
    var dogB = new DOG(“二毛”);
    
    alert(dogA.dinner); //[“meat”,”water”]
    
    alert(dogB.dinner); //[“meat”,”water”]

    看起来好像是他们共享了dinner这个属性,其实则不然,实例继承的属性都是互相独立的。

    console.log(dogA.dinner == dogB.dinner); //false

    看见了没,这也意味着,在创建实例的时候其实是这样的一个过程;

    dogA.dinner = new Array(“meat”,”water”);
    dogB.dinner
    = new Array(“meat”,”water”);

    每一个实例都有自己的属性和方法的副本。这样的话,无法做到数据共享,肯定是极大的资源浪费。

    prototype属性的引入

    考虑到这一点,Brendan决定为构造函数设置一个prototype属性。这个属性包含一个对象,我个人的理解,这个属性就是一个对象。里面包含有所有实例可以共享的属性和方法。而那些不需要共享的属性方法,则放在构造函数里。

    function DOG(name){
        this.name = name;
    }
    DOG.prototype.dinner = [“meat”,”water”];
    var dogA = new DOG(“大毛”); var dogB = new DOG(“二毛”); console.log(dogA.dinner); // [“meat”,”water”] console.log(dogB.dinner); //[“meat”,”water”] console.log(dogA.dinner == dogB.dinner); //true;

          实例对象一旦创建,将自动引用prototype对象的属性和方法。也就是说,实例对象的属性方法分成两种,一种是本地的,一种是引用的,其实也就是继承的。由于所有的实例对象共享同一个prototype对象,那么从外界看起来。prototype对象好像就是实例对象的原型。而实例对象,也就继承了prototype对象。

    Constructor属性的引入

          构造函数和prototype对象之间也需要一个联系的桥梁。这个对象就是constructor。因此,prototype对象的constructor属性,指向prototype所属的构造函数。(听着好像有点别扭,其实也很好理解。)因为prototype对象中所有的属性和方法都可以由实例来共享。因此,实例的constructor属性也指向了实例对应的构造函数。这里真想感慨,看起来那么简单,又是多么神奇的prototype和constructor。

        当然prototype属性也并非完美,下一节的时候会提到prototype的缺点以及解决方案。

    prototype的缺点

    所有的实例在默认的情况下共享属性和方法。这对共享函数非常合适。但是,对于包含引用类型的值来说,问题就比较突出了。

    function DOG(name){
    
    this.name = name;
    
    }
    
    DOG.prototype.dinner = [“meat”,”water”];
    
    var dogA = new DOG(“大毛”);
    
    var dogB = new DOG(“二毛”);
    
    console.log(dogA.dinner); // [“meat”,”water”]
    
    console.log(dogB.dinner); //[“meat”,”water”]
    
    console.log(dogA.dinner == dogB.dinner); //true;
    
    dogA.dinner.push(“milk”);
    
    console.log(dogB.dinner); //[“meat”,”water”,”milk”]

     

    因此,最常见的模式就是将构造函数模式和原型模式进行结合使用。实例的属性放在构造函数中。共享的属性和方法放在原型对象中。(应该还有其他的方法,先不扩展这里。)

  • 相关阅读:
    uip源码剖析【三】——【网络层】ICMP解读
    uip源码剖析【五】——【传输层】TCP解读
    WebGame方案汇总
    终于,我生命中第一次编译并运行了手机程序
    使用R7版NDK搭建Android开发环境[不使用Cgywin]
    拷问Unity:开发U3D游戏要思考的问题
    浏览器缓存导致FLASH资源更新问题的解决方案
    山寨版的《KingdomRush(皇城突袭)》
    在Unity3D的网络游戏中实现资源动态加载
    Unity3d之无缝地形场景切换–解决方法和代码
  • 原文地址:https://www.cnblogs.com/lxin/p/3587790.html
Copyright © 2020-2023  润新知