zoukankan      html  css  js  c++  java
  • 深入探究js的原型与原型链

    最近在看《javascript高级程序设计》,看完之后,觉得感触,然后我今天又看到了一篇文章,说的很搞笑。就想整理下自己所学的。

    首先,如果我们把ECMAScript的对象想象为散列表,即一组名值对,其中值可以是数据或函数。

    那究竟对象、原型对象、构造函数、继承、原型链、 原型属性的共享、原型的动态性、原型的整体重写呢,来一组简单粗暴的描述哈,还是挺搞笑的。

    1)人是人他妈生的,妖是妖他妈生的。人和妖都是对象实例,而人他妈和妖他妈就是原型。原型也是对象,叫原型对象。

    2)人他妈和人他爸啪啪啪能生出一堆人宝宝、妖他妈和妖他爸啪啪啪能生出一堆妖宝宝,啪啪啪就是构造函数,俗称造人。

    3)人他妈会记录啪啪啪的信息,所以可以通过人他妈找到啪啪啪的信息,也就是说能通过原型对象找到构造函数。

    4)人他妈可以生很多宝宝,但这些宝宝只有一个妈妈,这就是原型的唯一性。

    5)人他妈也是由人他妈他妈生的,通过人他妈找到人他妈他妈,再通过人他妈他妈找到人他妈他妈……,这个关系叫做原型链。

    6)原型链并不是无限的,当你通过人他妈一直往上找,最后发现你会发现人他妈他妈他妈……的他妈都不是人,也就是原型链最终指向null。

    7)人他妈生的人会有人的样子,妖他妈生的妖会有妖的丑陋,这叫继承。

    8)你继承了你妈的肤色,你妈继承了你妈他妈的肤色,你妈他妈……,这就是原型链的继承。

    9)你谈对象了,她妈让你带上房产证去提货,你若没有,那她妈会问你妈有没有,你妈没有那她妈会问你妈她妈有没有……这就是原型链的向上搜索。

    10)你会继承你妈的样子,但是你也可以去染发洗剪吹,就是说对象的属性可以自定义,会覆盖继承得到的属性。

    11)虽然你洗剪吹了染成黄毛了,但你不能改变你妈的样子,你妈生的弟弟妹妹跟你的黄毛洗剪吹没一点关系,就是说对象实例不能改动原型的属性。

    12)但是你家被你玩火烧了的话,那就是说你家你妈家你弟们家都被烧了,这就是原型属性的共享。

    13)你妈外号阿珍,邻居大娘都叫你阿珍儿,但你妈头发从飘柔做成了金毛狮王后,隔壁大婶都改口叫你包租仔,这叫原型的动态性。

    14)你妈爱美,又跑到韩国整形,整到你妈他妈都认不出来,即使你妈头发换回飘柔了,但隔壁邻居还是叫你金毛狮王子。因为没人认出你妈,整形后的你妈已经回炉再造了,这就是原型的整体重写。

     哈哈,是不是简单粗暴,还特别容易理解。

    ECMAScript中有两种属性:数据属性和访问器属性
    (1)数据属性:

    [[Configurable]] 表示能否通过delete删除属性从而重新定义属性,能否修改属性,或者能否把属性修改为访问器属性
    [[Enumerable]] 表示能够通过for-in循环返回属性
    [[Writable]]表示能否修改属性的值
    [[Value]]包含这个属性的数据值,
    修改默认的属性使用 Object.defineProperty() 这个方法

    (2)访问器属性:包含getter和setter函数。getter:负责返回有效的值,setter函数传入新值,访问器属性的4个特性:
    [[Configurable]]表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,能否把属性修改为数据属性。默认为true.
    [[Enumerable]]表示能否通过for-in循环返回属性。
    [[Get]]在读取属性时调用的函数,默认值为undefined
    [[Set]]在写入属性时调用的函数,默认值为undefined

    访问器的属性用Object.defineProperty()来定义

    书上给了一个例子,来挺好理解的:

    var book = {
      _year:2004,
      //_year前面的下划线是一种常用的记号,**用来表示只能通过对象方法访问的属性**
      edition:1
    };
    Object.defineProperty(book,"year",{
      get:function(){
        return this._year;
      }
      set:function(newYear){
        if(newValue >2004){
          this._year = newValue;
          this.edition += newValue -2004;
        }
      }
    });
    //定义访问器的旧有方法
    book.__defineGetter__("year",function(){
      return this._year;
    })
    book.__defineSetter__("year",function(newValue){
      if(newValue >2014){
        this._year = newValue;
        this.edition += newValue -2004;
      }
    })

    book.year = 2015;
    alert(book.edition); //2
    读取属性的特性:Object.getOwnPropertyDesciptor()取得给定属性的描述符。

    最后说回原型链:原型链继承的主要问题在于属性的共享,很多时候我们只想共享方法而并不想要共享属性,理想中每个实例应该有独立的属性。因此,原型继承就有了下面的两种改良方式:

    1)组合继承

    function Mother(age){

      this.age = age;

      this.bobby = ['running','football']

    }

    Mother.prototype.showAge = function(){

      console.log(this.age);

    };

    function Person(name,age){

      Mother.call(this.age);         //第二次执行

      this.name = name;

    }

    Person.prototype = new Mother();    //第一次执行

    Person.prototype.constructor = Person;

    Person.prototype.showName = function(){

      console.log(this.name);

    }

    var p1 = new Person('Jack',20);

    p1.hobby.push('basketball'); //p1:'Jack';_proto_:20,['running','football']

    var p2 = new Person('Mark',18); //p2:'Mark';_proto_:18,['running','football']

    (2)寄生组合式继承

    function object(o){

      function F(){}

      F.prototype = o;

      return new F();

    }

    function inheritPrototype(Person,Mother){

      var prototype = object(Mother.prototype);

      prototype.constructor = Person;

      Person.prototype = prototype;

    }

    function Mother(age){

      this.age = age;

      this.hobby = ['running','football']

    }

    Mother.prototype.showAge = function(){

      console.log(this.age);

    };

    function Person(name,age){

      Mother.call(this,age);

      this.name = name;

    }

    inheritPrototype(Person,Mother);

    Person.prototype.showName = function(){

      console.log(this.name);

    }

    var p1 = new Person('Jack',20);

    p1.hobby.push('backetball');    //p1:'Jack';_proto_:20,['running','football']

    var p2 = new Person('Mark',18); //p2:'Mark';_proto_:18,['running','football']

    js创建对象的各种方法

    (1)原型模式

    //1原型模式:对象字面量方式

    var person = {

      name : 'Jack',

      age:18,

      sayName:function(){alert(this.name);}

    };

    //2原型模式:Object构造函数方式

    var person = new Object();

    person.name = 'Jack';

    person.age = 18;

    person.sayName = function(){

       alert(this.name); 

    };

    (2)工厂模式

    //工厂模式,定义一个函数创建对象

    function creatPerson(name,age){

      var person = new Object();

      person.name = name;

      person.age = age;

      person.sayName = function(){

        alert(this.name);

      };

      return person;

    }

    工厂模式就是批量化生产,简单调用就可以进入造人模式(啪啪啪。。。)指定姓名年龄就可以造一堆小宝宝啦,解放双手。但是由于是工厂暗箱操作的,所以你不能识别这个对象到底是什么类型、是人还是狗傻傻分不清(instanceof 测试为 Object),另外每次造人时都要创建一个独立的temp对象,代码臃肿

    (3)构造函数

    //3构造函数模式,为对象定义一个构造函数

    function Person(name,age){

      this.name = name;

      this.age = age;

      this.sayName = function(){

        alert(this.name);

      };

    }

    var p1 = new Person('Jack',18);    //创建一个p1对象

    Person('Jack',18);

    构造函数与C++、JAVA中类的构造函数类似,易于理解,另外Person可以作为类型识别(instanceof 测试为 Person 、Object)。但是所有实例依然是独立的,不同实例的方法其实是不同的函数。这里把函数两个字忘了吧,把sayName当做一个对象就好理解了,就是说张三的 sayName 和李四的 sayName是不同的存在,但显然我们期望的是共用一个 sayName 以节省内存。

    (4)原型模式

    //原型模式,直接定义prototype属性

    function Person(){}

    Person.prototype.name = 'Jack';

    Person.prototype.age = 18;

    Person.prototype.sayName = function(){alert(this.name);};

    //原型模式。字面量定义方式

    function Person(){}

    Person.prototype = {

      name:'Jack',

      age:18,

      sayName:function(){alert(this.name);}

    };

    var p1 = new Person();  //name = 'Jack'

    var p2 = new Person(); //name = 'Jack'

    这里需要注意的是原型属性和方法的共享,即所有实例中都只是引用原型中的属性方法,任何一个地方产生的改动会引起其他实例的变化。

     (5)混合模式

    //原型构造函数组合模式

    function Person(name,age){

      this.name = name;

      this.age = age;

    }

    Person.prototype = {

      hobby:['running','football'];

      sayName:function(){alert(this.name);},

      sayAge:function(){alert(this.age);}

    }

    var p1 = new Person('Jack',20);

    var p2 = new Person('Mark',18);

    做法是将需要独立的属性方法放入构造函数中,而可以共享的部分则放入原型中,这样做可以最大限度节省内存而又保留对象实例的独立性。

  • 相关阅读:
    ubuntu安装mysql并修改编码为utf-8
    ubuntu16.04安装jdk1.8
    解决 Can't Connect to MySQL Server on IPAddress (10061) 的问题
    使用开源数据库客户端DBeaver连接DB2数据库
    Windows下使用console线连接思科交换机
    win7安装JDK6
    Python将excel文件从xls转换为xlsx
    Windows使用Gitblit搭建Git服务器
    B树、B-树、B+树、B*树
    必须熟悉的vim快捷键操作
  • 原文地址:https://www.cnblogs.com/meetup/p/5510726.html
Copyright © 2011-2022 走看看