首页 未命名正文

linux编程_深入明白JavaScript中建立工具模式的演变(原型)

云返利网 未命名 2020-05-26 09:06:02 14 0

确立工具的模式多种多样,然则种种模式又有怎样的利弊呢?有没有一种最为完善的模式呢?下面我迁就以下几个方面来剖析确立工具的几种模式:

  • Object组织函数和工具字面量方式
  • 工厂模式
  • 自界说组织函数模式
  • 原型模式
  • 组合使用自界说组织函数模式和原型模式
  • 动态原型模式、寄生组织函数模式、稳妥组织函数模式

第一部门:Object组织函数和工具字面量方式

  我之前在博文《JavaScript中工具字面量的明白 http://www.linuxidc.com/Linux/2016-11/136666.htm》中讲到过这两种方式,若何人人不熟悉,可以点进去看一看回首一下。它们的优点是用来确立单个的工具异常利便。然则这种方式有一个显著的瑕玷:行使统一接口确立许多工具是,会发生大量的重复代码。这句话怎么明白呢?让我们看一下下面的代码:

1 2 3 4 var person1={     <strong>name</strong>: "zzw" ,     <strong>age</strong>: "21" ,     <strong>school</strong>: "xjtu" ,<br>         <strong> sayName</strong>:<strong> function (){</strong><br><strong>                console.log( this .name);</strong><br><strong>                };</strong>
 
1 2 3 4 5     } var person2={     <strong>name</strong>: "ht" ,     <strong>age</strong>: "18" ,     <strong>school</strong>: "tjut" ,<br>         <strong> sayName: function (){</strong><br><strong>            console.log( this .name);</strong><br><strong>          };</strong><br><br>        }

   可以看出,当我们确立了两个类似的工具时,我们重复写了name age school 以及工具的方式这些代码,随着类似工具的增多,显然,代码会凸显出庞大、重复的感受。为解决这一问题,工厂模式应运而生。

第二部门:工厂模式

  刚刚我们提到:为解决确立多个工具发生大量重复代码的问题,由此发生了工厂模式。那么,事实什么是工厂模式?它是若何解决这一问题的呢?首先,我们可以想一想何谓工厂? 就我小我私家明白:在工厂可以生产出一个模具,通过这个模具大量生产产物,最终我们可以加以修饰(好比喷涂以差异颜色,包装差异的外壳)。这样就不用一个一个地做产物,由此可以大大地提高效率。

  同样地,对于确立工具也是这样的思绪:它会通过一个函数封装确立工具的细节。最后直接将差异的参数通报到这个函数中去,以解决发生大量重复代码的问题。考察以下代码:

1 2 3 4 5 6 7 8 9 10 11 12 13         function createPerson(name,age,school){     var o= new Object();     o.name=name;     o.age=age;     o.school=school;     o.sayName= function (){         console.log( this .name);     };     return o; } var person1=createPerson( "zzw" , "21" , "xjtu" ); var person2=createPerson( "ht" , "18" , "tjut" );

  看似这里的代码也不少啊!可是,若是在多确立2个工具呢,10个呢,100个呢?效果可想而知,于是工厂模式乐成地解决了Object组织函数或工具字面量确立单个工具而造成大量代码重复的问题!工厂模式有以下特点:

  • 在函数内部显式地确立了工具。
  • 函数末端一定要返回这个新确立的工具。

  然则,我们仔细考察,可以发现工厂模式确立的工具,例如这里确立的person1和person2,我们无法直接识别工具是什么类型。为了解决这个问题,自界说的组织函数模式泛起了。

第三部门:自界说组织函数模式

  刚刚说到,自界说组织函数模式是为了解决无法直接识别工具的类型才泛起的。那么显然自界说组织函数模式至少需要解决两个问题。其一:可以直接识别确立的工具的类型。其二:解决工厂模式解决的确立大量相似工具时发生的代码重复的问题。

  那么,我为什么说是自界说组织函数模式呢?这是由于,第一部门中,我们使用的Object组织函数是原生组织函数,显然它是���决不了问题的。只有通过确立自界说的组织函数,从而界说自界说工具类型的属性和方式。代码如下:

1 2 3 4 5 6 7 8 9 10 function Person(name,age,school){     this .name=name;     this .age=age;     this .school=school;     this .sayName= function (){         console.log( this .name);     }; } var person1= new Person( "zzw" , "21" , "xjtu" ); var person2= new Person( "ht" , "18" , "tjut" );

  首先我们验证这种自界说的组织模式是否解决了第一个问题。在上述代码之后追加下面的代码:

1 2 console.log(person1 <strong> instanceof </strong> Person); //true console.log(person1 <strong> instanceof </strong> Object); //true

  结构都获得了true,对于Object固然没有问题,由于一切工具都是继续自Object的,而对于Person,我们在确立工具的时刻用的是Person组织函数,那么获得person1是Person类型的也就没问题了。

  对于第二个问题,谜底是显而易见的。很显著,确立大量的工具不会造成代码的重复。于是,自界说组织函数乐成解决所有问题。

  A 下面我们对比以下自界说组织函数与工厂模式的差异之处:

  • 自界说组织函数没有用 var o = new Object()那样显式地确立工具
  • 与o.name等差异,它直接将属性和方式赋给了this工具,this最终会指向新确立的工具。(this工具的更多细节可以在我的另一篇博文《JavaScript函数之美~》中查看)。
  • 由于没有确立工具,以是最终没有return一个工具(注重:组织函数在不返回值的情形下,会默认返回一个新工具实例)。

  B 对于组织函数,我们还应当注重:

  • 组织函数的函数名需要大写,用以区分与通俗函数。
  • 组织函数也是函数,只是它的作用之一是确立工具。
  • 组织函数在确立新工具时,必须使用new操作符。
  • 确立的两个工具person1和person2的constructor(组织函数)属性都指向用于确立它们的Person组织函数。

  C 若何明白组织函数也是函数?

  只要证实组织函数也可以像通俗函数一样的挪用,那么就可以明白组织函数也是函数了。

1 2 3 4 5 6 7 8 9 10 function Person(name,age,school){     this .name=name;     this .age=age;     this .school=school;     this .sayName= function (){         console.log( this .name);     }; } <strong>Person( "zzw" , "21" , "xjtu" );</strong> sayName(); //zzw

  可以看出,我直接使用了Person("zzw","21","xjtu");来像通俗函数一样的挪用这个组织函数,由于我们把它当作了通俗函数,那么函数中的this就不会指向之前所说的工具(这里亦没有工具),而是指向了window。于是,函数一经挪用,内部的变量便会放到全局环境中去,同样,对于其中的函数也会在挪用之后到全局环境,只是这个内部的函数是函数表达式并未被挪用。只有挪用即sayName();才气准确输出

  由此,我们证实了组织函数也是函数。

  D 那么这种自界说组织函数就没有任何问题吗?

  组织函数的问题是在每次确立一个实例时,组织函数的方式都需要再实例上确立一遍。由于在JavaScript中,我们以为所有的函数(方式)都是工具,以是每当确立一个实例工具,都市同时在工具的内部确立一个新的工具(这部门内容同样可以在我的博文《JavaScript函数之美~》中找到)。即我们之前确立的自界说组织函数模式相当于下列代码:

1 2 3 4 5 6         function Person(name,age,school){     this .name=name;     this .age=age;     this .school=school;     this .sayName= new Function( "console.log(this.name)" ); }
?
1 2         var person1= new Person( "zzw" , "21" , "xjtu" ); var person2= new Person( "ht" , "18" , "tjut" );

  

  即我们在确立person1和person2的时刻,同时确立了两个sayName为工具指针的工具,我们可以通过下面这个语句做出判断:

1 console.log(person1.sayName==person2.sayName); //false

  这就证实了若是确立两个工具同时也在每个工具中又各自确立了一个函数工具,然则确立两个完成同样义务的Function实例简直没有必要(况且内部有this工具,只要确立一个工具,this便会指向它)。这就造成了内部方式的重复造成资源虚耗。

  E 解决方式。

  若是我们将组织函数内部的方式放到组织函数的外部,那么这个方式便会被person1和person2共享了,于是,在每次确立新工具时就不会同时确立这个方式工具了。如下:

1 2 3 4 5 6 7 8 9 10 11     function Person(name,age,school){     this .name=name;     this .age=age;     this .school=school;                 this .sayName=sayName; } <strong> function sayName(){     console.log( this .name); }</strong> var person1= new Person( "zzw" , "21" , "xjtu" ); var person2= new Person( "ht" , "18" , "tjut" );

                            person1.sayName();//zzw

  应当注重:this.sayName=sayName;中这里等式右边的sayName是一个指针,以是在确立新工具的时刻只是确立了一个指向配合对像谁人的指针而已,并不会确立一个方式工具。这样便解决了问题。   而外面的sayName函数在最后一句中是被工具挪用的,以是其中的this同样是指向了工具。

  

    F新的问题

    若是这个组织函数中需要的方式许多,那么为了保证能够解决E中的问题,我们需要把所有的方式都写在组织函数之外,可是若是这样:

  1.     在全局作用域中界说的函数从未在全局环境中挪用,而只会被某个工具挪用,这样就让全局作用域有点名存实亡。
  2.     若是把所有组织函数中的方式都放在组织函数之外,这样就没有封装性可言了。       

   由此,为了解决F中的问题,接下来不得不提到JavaScript语言中的焦点原型模式了。

第四部门:原型模式

        为什么会泛起原型模式呢?这个模式在上面讲了是为了解决自界说组织函数需要将方式放在组织函数之外造成封装性较差的问题。固然它又要解决组织函数能够解决的问题,以是,最终它需要解决以下几个问题。其一:可以直接识别确立的工具的类型。其二:解决工厂模式解决的确立大量相似工具时发生的代码重复的问题。其三:解决组织函数发生的封装性欠好的问题。由于这个问题比较庞大,以是我会分为几点循序渐进的做出说明。

A 明白原型工具

   首先,我们应当知道:无论什么时刻,只要确立了一个新函数(函数即工具),就会凭据一组特定的规则确立一个函数(工具)的prototype属性明白为指针,这个属性会指向函数的原型工具(原型工具也是一个工具),然则由于我们不能通过这个新函数接见prototype属性,以是写为[[prototype]]。同时,对于确立这个工具的组织函数也将获得一个prototype属性(明白为指针,同时指向它所确立的函数(工具)所指向的原型工具,这个组织函数是可以直接接见prototype属性的,以是我们可以通过接见它将界说工具实例的信息直接添加到原型工具中。这时原型工具拥有一个constructor属性(明白为指针)指向确立这个工具的组织函数(注重:这个constructor指针不会指向除了组织函数之外的函数)。

   你可能会问?所有的函数都是由组织函数确立的吗?谜底是一定的。函数即工具,我在博文《JavaScript函数之美~》中做了详尽先容。对与函数声明和函数表达式这样确立函数的方式本质上也是由组织函数确立的。

    

   上面的说法可能过于抽象,我们先写出一个例子(这个例子还不是我们最终想要的原型模式,只是为了让人人先明白原型这个观点),再凭据代码作出说明:

1 2 3 4 5 6 7 8 9 10 11 12 <strong>        function Person(){}</strong>         Person.prototype.name= "zzw" ;         Person.prototype.age=21;         Person.prototype.school= "xjtu" ;         Person.prototype.sayName= function (){             console.log( this .name);         };         var person1= new Person();         var person2= new Person();         person1.sayName(); //zzw         person2.sayName(); //zzw         console.log(person1.sayName==person2.sayName); //true

  在这个例子中,我们首先确立了一个内容为空的组织函数,由于刚刚讲了我们可以通过接见组织函数的prototype属性来为原型工具中添加属性和方式。于是在下面几行代码中,我们便通过接见组织函数的prototype属性向原型工具中添加了属性和方式。接着,确立了两个工具实例person1和person2,并挪用了原型工具中sayName()方式,获得了原型工具中的name值。这说明:组织函数确立的每一个工具和实例都拥有或者说是继续了原型工具的属性和方式。(由于无论是确立的工具实例照样缔造函数的prototype属性都是指向原型工具的) 换句话说,原型工具中的属性和方式会被组织函数所确立的工具实例所共享,这也是原型工具的一个利益。

  下面我会画一张图来继续论述这个问题:

从这张图中我们可以看出以下几点:

  1. 组织函数和由组织函数确立的工具的prototype指针都指向原型工具。即原型工具既是组织函数的原型工具,又是组织函数确立的工具的原型工具。
  2. 原型工具有一个constructor指针指向组织函数,却不会指向组织函数确立的实例。
  3. 组织函数的实例的[[prototype]]属性被实例接见来添加或修改原型工具的属性和方式的,而组织函数的prototype属性可以被用来接见以修改原型工具的属性和方式。
  4. person1和person2与他们的组织函数之间没有直接的关系,只是他们的prototype属性同时指向了统一个原型工具而已。 
  5. Person.prototype指向了原型工具,而Person.prototype.constructor又指回了Person。
  6. 虽然这两个实例都不包罗属性和方式,但我们却可以挪用person1.name,这是通过查找工具属性的历程来实现的。

B.有关于原型工具中的方式以及实例中的属性和原型工具中的属性

为了加深对原型的明白,我在这里先先容两种方式确定组织函数确立的实例工具与原型工具之间的关系。

  第一种方式:isPrototypeOf()方式,通过原型工具挪用,确定原型工具是否是某个实例的原型工具。在之前的代码后面追加下面两句代码:

1 2 console.log(Person.prototype.isPrototypeOf(person1)); //true console.log(Person.prototype.isPrototypeOf(person2)); //true

  效果不出意外地均为true,也就是说person1实例和person2实例的原型工具都是Person.prototype。

  第二种方式:Object.getPrototypeOf()方式,通过此方式获得某个工具实例的原型。在之前的代码后面追加下面三句代码:

1 2 console.log(Object.getPrototypeOf(person1)); console.log(Object.getPrototypeOf(person1)==Person.prototype);<br>          console.log(Object.getPrototypeOf(person1).name); //zzw

  其中第一句代码在控制台中可以直接获得person1的原型工具,如下图所示:

       其中第二句代码获得布尔值:true。第三句代码获得了原型工具中的name属性值。

然则,当实例自己自己有和原型中相同的属性名,而属性值差异,在代码获取某个工具的属性时,该从那里获取呢?

  规则是:在代码读取某个工具而某个属性是,都市执行一次搜索,目的是具有给定名字的属性。搜索首先从实例自己��始,若是在实例中找到了给定名字的属性,则返回该属性的值;若是没有找到,则继续搜索指针指向的原型工具。考察下面的例子。

1 2 3 4 5 6 7 8 9 10 11 12 13 function Person(){} Person.prototype.name= "zzw" ; Person.prototype.age=21; Person.prototype.school= "xjtu" ; Person.prototype.sayName= function (){     console.log( this .name); }; var person1= new Person(); var person2= new Person(); console.log(person1.name); //zzw <strong>person1.name= "htt" ;</strong> <strong>console.log(person1.name); //htt</strong> console.log(person2.name); //zzw<br>        <strong>  delete</strong> person1.name;<br>        <strong>      console.log(person1.name);//zzw</strong><br>
  •  首先,我们把person1实例的name属性设置为"htt" ,当我们直接获取person1的name属性时,会现在person1自己找该属性(明白为就近原则),找不到,继续向原型工具中寻找。
  •  当给person1工具添加了自身的属性name时,这次获得的时person1自身的属性,即该属性屏障了原型中的同名属性。
  •  通过倒数第三句代码再次获得了zzw,这说明我们对person1设定了与原型工具相同的属性名,但却没有重写原型工具中的同名属性。
  •  最后,我们可以通过delete删除实例中的属性,而原型中的属性不会被删除。 

  第三种方式:hasOwnProperty()方式

   该方式可以检测一个属性是存在于实例中照样存在于原型中。只有给定属性存在于工具实例中时,才会返回true,否则返回false。举例如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18         function Person(){}         Person.prototype.name= "zzw" ;         Person.prototype.age=21;         Person.prototype.school= "xjtu" ;         Person.prototype.sayName= function (){             console.log( this .name);         };         var person1= new Person();         var person2= new Person();         console.log(person1.name); //zzw     <strong>    console.log(person1.hasOwnProperty( "name" )); //false  由于zzw是搜索于原型工具的</strong>         person1.name= "htt" ;         console.log(person1.name); //htt     <strong>    console.log(person1.hasOwnProperty( "name" )); //true 在上上一句,我添加了person1实例的属性,它不是属于原型工具的属性</strong>             delete person1.name;                 console.log(person1.name); //zzw <strong>                console.log(person1.hasOwnProperty( "name" )); //false  由于使用delete删除了实例中的name属性,以是为false </strong>

 C.in操作符的使用以及若何编写函数判断属性存在于工具实例中

  in操作符会在通过工具能够接见给定属性时,返回true,无论该属性存在于事例中照样原型中。考察下面的例子:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21         function Person(){}         Person.prototype.name= "zzw" ;         Person.prototype.age=21;         Person.prototype.school= "xjtu" ;         Person.prototype.sayName= function (){             console.log( this .name);         };         var person1= new Person();         var person2= new Person();         console.log(person1.name); //zzw         console.log(person1.hasOwnProperty( "name" )); //false         <strong>console.log( "name" in person1); //true</strong>         person1.name= "htt" ;         console.log(person1.name); //htt         console.log(person1.hasOwnProperty( "name" )); //true         <strong>console.log( "name" in person1); //true</strong>             delete person1.name;                 console.log(person1.name); //zzw                 console.log(person1.hasOwnProperty( "name" )); //false               <strong>  console.log( "name" in person1); //true </strong>

  可以看到,确实,无论属性在实例工具自己照样在实例工具的原型工具都市返回true。

  有了in操作符以及hasOwnProperty()方式我们就可以判断一个属性是否存在于原型工具了(而不是存在于工具实例或者是基本就不存在)。编写hasPrototypeProperty()函数并磨练:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21                 function Person(){}     <strong>    function hasPrototypeProperty(Object,name){             return !Object.hasOwnProperty(name)&&(name in Object);                 }</strong>         Person.prototype.name= "zzw" ;         Person.prototype.age=21;         Person.prototype.school= "xjtu" ;         Person.prototype.sayName= function (){             console.log( this .name);         };         var person1= new Person();         var person2= new Person();         console.log(person1.name); //zzw         <strong>console.log(hasPrototypeProperty(person1, "name" )); //true</strong>         person1.name= "htt" ;         console.log(person1.name); //htt <strong>        console.log(hasPrototypeProperty(person1, "name" )); //true</strong>             delete person1.name;                 console.log(person1.name); //zzw     <strong>    console.log(hasPrototypeProperty(person1, "name" )); //true </strong>

  其中hasPrototypeProperty()函数的判断方式是:in操作符返回true而hasOwnProperty()方式返回false,那么若是最终获得true则说明属性一定存在于原型工具中。(注重:逻辑非运算符!的优先级要远远高于逻辑与&&运算符的优先级

D.for-in循环和Object.keys()方式在原型中的使用

  在通过for-in循环时,它返回的是所有能够通过工具接见的、可枚举的属性,其中既包罗存在于实例中的属性,也包罗存在于原型中的属性。且对于屏障了原型中不可枚举的属性(即将[[Enumerable]]标记为false的属性)也会在for-in中循环中返回。(注:IE早期版本中存在一个bug,即屏障不可枚举属性的实例属性不会泛起在for-in循环中,这里不做详细先容)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function Person(){}<br>          Person.prototype.name= "zzw" ; Person.prototype.age=21; Person.prototype.school= "xjtu" ; Person.prototype.sayName= function (){     console.log( this .name); }; var person1= new Person(); var person2= new Person(); console.log(person1.name); //zzw person1.name= "htt" ; console.log(person1.name); //htt     delete person1.name;         console.log(person1.name); //zzw for ( var propName in person1){     console.log(propName); //name age school sayName }

  通过for-in循环,我们可以枚举初name age school sayName这几个属性。由于person1中的[[prototype]]属性不可被接见,因此,我们不能行使for-in循环枚举出它。

  Object.keys()方式吸收一个参数,这个参数可以是原型工具,也可以是由组织函数确立的实例工具,返回一个包罗所有可枚举属性的字符串数组。如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17             function Person(){}         Person.prototype.name= "zzw" ;         Person.prototype.age=21;         Person.prototype.school= "xjtu" ;         Person.prototype.sayName= function (){             console.log( this .name);         };         var person1= new Person();         var person2= new Person();         console.log(person1.name); //zzw         person1.name= "htt" ;         console.log(person1.name); //htt         person1.age= "18" ;     <strong>    console.log(Object.keys(Person.prototype)); //["name", "age", "school", "sayName"]         console.log(Object.keys(person1)); //["name", "age"]         console.log(Object.keys(person2)); //[] </strong>

  

  我们可以从上面的例子中看到,Object.keys()方式返回的是其自身的属性。如原型工具只返回原型工具中的属性,工具实例也只返回工具实例自己确立的属性,而不返回继续自原型工具的实例。

E 更简朴的原型语法

  在之前的例子中,我们在组织函数的原型工具中添加属性和方式时,每次都要在前面敲一遍Person.prototype,若是属性多了,这样的方式会显得更为繁琐,那么下面我将先容给人人一种简朴的方式。

  我们知道,原型工具说到底它照样个工具,只要是个工具,我们就可以使用工具字面量方式来确立,方式如下:

1 2 3 4 5 6 7 8 9         function Person(){} Person.prototype={     name: "zzw" ,     age:21,     school: "xjtu" ,     sayName: function (){         console.log( this .name);     } }; //原来行使Person.prototype.name="zzw"知识工具中的属性,对于工具并没有任何影响,而这里确立了新的工具<br>          

  同样,最最先,我们确立一个空的Person组织函数(人人发现了没有,实在每次我们确立的都是空的组织函数),然后用工具字面量的方式来向原型工具中添加属性。这样既减少了不必要的输入,也从视觉上更好地封装了原型。 然则,这时原型工具的constructor就不会指向Person组织函数而是指向Object组织函数了。

    为什么会这样?我们知道,当我们确立Person组织函数时,就会同时自动确立这个Person组织函数的原型(prototype)工具,这个原型工具也自动获取了一个constructor属性并指向Person组织函数,这个之前的图示中可以清楚地看出来。之前我们使用的较为贫苦的方式(e.g. Person.prototype.name="zzw")只是简朴地向原型工具添加属性,并没有其他本质的改变。然而,上述这种封装性较好的方式纵然用工具字面量的方式,实际上是使用Object组织函数确立了一个新的原型工具(工具字面量本质即行使Object组织函数确立新工具),注重:此时Person组织函数的原型工具不再是之前的原型工具(而之前的原型工具的constructor属性仍然指向Person组织函数),而和Object组织函数的原型工具一样均为这个新的原型工具。这个原型工具和确立Person组织函数时自动天生的原型工具风马牛不相及。理所应当的是,工具字面量确立的原型工具的constructor属性此时指向了Object组织函数。

      我们可以通过下面几句代码来验证:

1 2 3 4 5 6 7 8 9 10 11 12         function Person(){} Person.prototype={     name: "zzw" ,     age:21,     school: "xjtu" ,     sayName: function (){         console.log( this .name);     } }; var person1= new Person(); console.log(Person.prototype.constructor==Person); //false console.log(Person.prototype.constructor==Object); //true

  通过最后两行代码我们可以看出Person组织函数的原型工具的constructor属性此时不再指向Person组织函数,而是指向了Object组织函数。然则这并被影响我们正常使用,下面几行代码便可以清楚地看出:

1 2 3 4 5 6 7 8 9 10 11 12 13 14     function Person(){} Person.prototype={     name: "zzw" ,     age:21,     school: "xjtu" ,     sayName: function (){         console.log( this .name);     } }; var person1= new Person(); console.log(person1.name); //zzw console.log(person1.age); //21 console.log(person1.school); //xjtu person1.sayName(); //zzw

  下面我将以小我私家的明白用图示示意(若是有问题,请指出):

    第一步:确立一个空的组织函数。function Person(){}。此时组织函数的prototype属性指向原型工具,而原型工具的constructor属性指向Person组织函数。

  第二步:行使工具字面量的方式确立一个Person组织函数的新原型工具。

1 2 3 4 5 6 7 8   Person.prototype={     name: "zzw" ,     age:21,     school: "xjtu" ,     sayName: function (){         console.log( this .name);     } };

      此时,由于确立了Person组织函数的一个新原型工具,以是Person组织函数的prototype属性不再指向原来的原型工具,而是指向了Object组织函数确立的原型工具(这是工具字面量方式的本质)。然则原来的原型工具的constructor属性仍指向Person组织函数。

   第三步:由Person组织函数确立一个实例工具。

这个工具实例的constructor指针同组织它的组织函数一样指向新的原型工具。

  总结:从上面的这个例子可以看出,虽然新确立的实例工具仍可以共享添加在原型工具内里的属性,然则这个新的原型工具却不再指向Person组织函数而指向Object组织函数,若是constructor的值真的异常重要的时刻,我们可以像下面的代码这样重新设置会适当的值:

1 2 3 4 5 6 7 8 9 10 function Person(){} Person.prototype={     constructor:Person,     name: "zzw" ,     age:21,     school: "xjtu" ,     sayName: function (){         console.log( this .name);     } };

  这样,constructor指针就指回了Person组织函数。即如下图所示:

  值得注重的是:这种方式重设constructor属性会导致它的[[Enumerable]]特征设置位true,而默认情形下,原生的constructor属性是不可枚举的。然则我们可以试用Object.defineProperty()将之修改为不可枚举的(这一部门可以参见我的另一篇博文:《深入明白JavaScript中的属性和特征》)。

F.原生工具的原型

  原型的重要性不仅体现在自界说类型方面,就连所有原生的引用类型,都是使用这种模式确立的。所有原生引用类型(Object、Array、String,等等)都在其组织函数的原型上界说了方式。例如在Array.prototype中可以找到sort()方式,而在String.prototype中就可以找到substring()方式。

1 2 console.log( typeof Array.prototype.sort); //function console.log( typeof String.prototype.substring); //function

  于是,实际上我们是可以通过原生工具的原型来修改它。好比:   

1 2 3 4 5 String.prototype.output= function (){     alert( "This is a string" ); } var message= "zzw" ; message.output();

   这是,便在窗口中弹出了“This is a string”。只管可以这样做,然则我们不推荐在产物化的程序中修改原生工具的原型。这样做有可能导致命名冲突等问题。

G.原型模式存在的问题

  实际上,从上面临原型的解说来看,原型模式照样有许多问题的,它并没有很好地解决我在第四部门初提出的若干问题:“其一:可以直接识别确立的工具的类型。其二:解决工厂模式解决的确立大量相似工具时发生的代码重复的问题。其三:解决组织函数发生的封装性欠好的问题。”其中第一个问题解决的不错,通过组织函数便可以直接看出来类型。第二个问题却解决的欠好,由于它省略了为组织函数通报初始化参数这一环节,效果所有的实例在默认情形下都将取得相同的默认值,我们只能通过在实例上添加同名属性来屏障原型中的属性,这无疑也会造成代码重复的问题。第三个问题,封装性也还说的已往。因此原型模式算是委曲解决了上述问题。

  然则这种方式还由于自己发生了分外的问题。看下面的例子:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16         function Person(){} Person.prototype={     constructor:Person,     name: "zzw" ,     age:21,     school: "xjtu" ,     friends:[ "pengnian" , "zhangqi" ],     sayName: function (){         console.log( this .name);     } }; var person1= new Person(); var person2= new Person(); person1.friends.push( "feilong" ); console.log(person1.friends); //["pengnian","zhangqi","feilong"] console.log(person2.friends); //["pengnian","zhangqi","feilong"]

  这里我在新建的原型工具中增加了一个数组,于是这个数组会被后面确立的实例所共享,然则person1.friends.push("feilong");这句代码我的意思是添加为person1的同伙而不是person2的同伙,然则在效果中我们可以看到person2的同伙也有了feilong,这就不是我们所希望的了。这也是对于包罗引用类型的属性的最大问题。

  也正是这个问题和刚刚提到的第二个问题(即它省略了为组织函数通报初始化参数这一环节,效果所有的实例在默认情形下都将取得相同的默认值,我们只能通过在实例上添加同名属性来屏障原型中的属性,这无疑也会造成代码重复的问题),很少有人会单单使用原型模式。

第五部门:组合使用自界说组织函数模式和原型模式

  刚刚我们说到的原型模式存在的两个最大的问题。问题一:由于没有在为组织函数确立工具实例时通报初始化参数,所有的实例在默认情形下获取了相同的默认值。问题二:对于原型工具中包罗引用类型的属性,在某一个实例中修改引用类型的值,会牵涉到其他的实例,这不是我们所希望的。而组合使用自界说组织函数模式和原型模式纵然组织函数应用于界说实例属性,而原型模式用于界说方式和共享的属性。它能否解决问题呢?下面我们来一探事实!

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function Person(name,age,school){     this .name=name;     this .age=age;     this .school=school;     this .friends=[ "pengnian" , "zhangqi" ]; } Person.prototype={     constructor:Person,     sayName: function (){         console.log( this .name);     } } var person1= new Person( "zzw" ,21, "xjtu" ); var person2= new Person( "ht" ,18, "tjut" ); person1.friends.push( "feilong" ); console.log(person1.friends); //["pengnian", "zhangqi", "feilong"] console.log(person2.friends); //["pengnian", "zhangqi"] console.log(person1.sayName==person2.sayName); //true

  OK!我们来看看组合使用组织函数模式和原型模式解决的问题:

  1. 解决了Object组织函数和工具字面量方式在确立大量工具时造成的代码重复问题(由于只要在确立工具时向组织函数通报参数即可)。
  2. 解决了工厂模式发生的无法识别工具类型的问题(由于这里通过组织函数即可获知工具类型)。
  3. 解决了自界说组织函数模式封装性较差的问题(这里所有都被封装)。
  4. 解决了原型模式的两个问题:所有实例共享相同的属性以及包罗引用类型的数组在实例中修改时会影响原型工具中的数组。

  综上所述,组合使用组织函数模式和原型模式可以说是异常完善了。

第六部门:动态原型模式、寄生组织函数模式、稳妥组织函数模式

  实际上,组合使用组织函数模式和原型模式确实已经异常完善了,这里将要讲的几种模式都是在特定的情形下使用的,以是我以为第六部门相对于第五部门并没有进一步的提高。仅仅是多学习几种模式可以解决更多的问题。

A 动态原型模式

  这里的动态原型模式相对于第五部门的组合使用自界说组织函数模式和原型模式本质上是没有什么差异的,只是由于对于有其他OO(Object Oriented,面向工具)语言履历的开发人员看到这种模式会以为新鲜,因此我们可以将所有信息都封装在组织函数中。本质上是通过检测某个应该存在的方式是否存在或有用,来决议是否要初始化原型。如下例所示:

1 2 3 4 5 6 7 8 9 10 11 12 13 function Person(name,age,school){     this .name=name;     this .age=age;     this .school=school;     if ( typeof this .sayName != "function" ){         Person.prototype.sayName= function (){             console.log( this .name);         };     } }     var person= new Person( "zzw" ,21, "xjtu" ); //使用new挪用组织函数并确立一个实例工具     person.sayName(); //zzw     console.log(person.school); //xjtu

  这里先声明晰一个组织函数,然后当使用new操作符挪用组织函数确立实例工具时进入了组织函数的函数执行环境,最先检测工具的sayName是否存在或是否是一个函数,若是不是,就使用原型修改的方式向原型中添加sayName函数。且由于原型的动态性,这里所做的修改可以在所有实例中立刻获得反映。值得注重的是在使用动态原型模式时,不能使用工具字面量重写原型,否则,在确立了实例的情形下重写原型会导致切断实例和新原型的联系。

B 寄生组织函数模式

  寄生组织函数模式是在前面几种模式都不适用的情形下使用的。看以下例子,再做出说明:

1 2 3 4 5 6 7 8 9 10 11 12 function Person(name,age,school){     var o = new Object();     o.name=name;     o.age=age;     o.school=school;     o.sayName= function (){         console.log( this .name);     };     return o; } var person = new Person( "zzw" ,21, "xjtu" ); person.sayName(); //zzw

  寄生组织函数的特点如下:

  • 声明一个组织函数,在组织函数内部确立工具,最后返回该工具,因此这个函数的作用仅仅是封装确立工具的代码。
  • 可以看出,这种方式除了在确立工具的时刻使用了组织函数的模式(函数名大写,用new关键字挪用)以外与工厂模式一模一样。
  • 组织函数在不返回值的情形下,默认会返回新工具实例,而通过组织函数的末尾添加一个return语句,可以重写挪用组织函数时返回的值。

  这个模式可以在特殊的情形下来为工具确立组织函数。假设我们想要确立一个具有分外方式的特殊数组,通过改变Array组织函数的原型工具是可以实现的,然则我在第四部门F中提到过,这种方式可能会导致后续的命名冲突等一系列问题,我们是不推荐的。而寄生组织函数就能很好的解决这一问题。如下所示:

1 2 3 4 5 6 7 8 9 10         function SpecialArray(){     var values= new Array();     values.push.apply(values,arguments);     values.toPipedString= function (){         return this .join( "|" );     };     return values; } var colors= new SpecialArray( "red" , "blue" , "green" ); console.log(colors.toPipedString()); //red|blue|green

  或者如下所示:

1 2 3 4 5 6 7 8 9 10 function SpecialArray(string1,string2,string3){     var values= new Array();     values.push.call(values,string1,string2,string3);     values.toPipedString= function (){         return this .join( "|" );     };     return values; } var colors= new SpecialArray( "red" , "blue" , "green" ); console.log(colors.toPipedString()); //red|blue|green

  这两个例子实际上是一样的,唯一差异在于call()方式和apply()方式的应用差异。(这部门内容详见《JavaScript函数之美~》)

  这样就既没有改变Array组织函数的原型工具,又完成了添加Array方式的目的。

  关于寄生组织函数模式,需要说明的是:返回的工具与组织函数或组织函数的原型属性之间没有任何关系;也就是说,组织函数返回的工具在与组织函数外部确立的工具没有什么差异。故不能依赖instanceof来确定工具类型。于是,我们建议在可以使用其他模式确立工具的情形下不使用寄生组织函数模式。

C.稳妥组织函数模式

  稳妥工具是指这没有公共属性,而且方式也不引用this的工具。稳妥工具适合在平安的环境中使用,或者在防止数据被其他应用程序改动时使用。举例如下:

1 2 3 4 5 6 7 8 9 function Person(name,age,school){     var o= new Object();     o.sayName= function (){         console.log(name);     };     return o; } var person=Person( "zzw" ,21, "xjtu" ); person.sayName(); //zzw

  可以看出来,这种模式和寄生组织函数模式异常相似,只是:

  1.新确立工具的实例方式不用this。

  2.不用new操作符挪用组织函数(由函数名的首字母大写可以看出它简直是一个组织函数)。

  注重:变量person中保留的是一个稳妥工具,除了挪用sayName()方式外没有其余方式可以接见其数据成员。例如在上述代码下添加:

1 2 console.log(person.name); //undefined console.log(person.age); //uncefined

  因此,稳妥组织函数模式提供的这种平安性,使得它异常适合在某些平安执行环境提供的环境下使用。

第七部门:总结

  在这篇博文中,在确立大量相似工具的前提下,我以剖析种种方式利弊的思绪下向人人循序渐进地先容了Object组织函数和工具字面量方式、工厂模式、自界说组织函数模式、原型模式、组合使用自界说组织函数模式和原型模式、动态原型模式、寄生组织函数模式、稳妥组织函数模式这几种模式,其中我以为组合使用自界说组织函数模式和原型模式以及动态原型模式都是异常不错的模式。而对于确立工具数目不多的情形下,工具字面量方式、自界说组织函数模式也都是不错的选择。

  这一部门内容属于JavaScript中的重难点,希望人人多读几遍,信赖一定会有很大的收获! 

【关于云返利网】

云返利网是阿里云、腾讯云、华为云产品推广返利平台,在各个品牌云产品官网优惠活动之外,云返利网还提供返利。您可以无门槛获得阿里云、华为云、腾讯云所有产品返利,在官网下单后就可以领取,无论是自己用、公司用还是帮客户采购,您个人都可以获得返利。云返利网的目标是让返利更多、更快、更简单!详情咨询13121395187(微信同号)