创建对象之原型模式-理解原型对象

原型模式

我们创建的每一个函数都有prototype(原型)属性,这个属性是一个指针,指向一个对象,这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。

如果按照字面理解,protoype就是通过调用构造函数而创建的那个对象实例的原型对象。使用原型对象的好处是可以让所有对象实例共享它所含的属性和方法。

换句话说,不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中。

例如:

function Person(){};
Person.prototype.name="Mary";
Person.prototype.age=18;
Person.prototype.job="teacher";
Person.prototype.sayName=function (){
    alert(this.name);
}
var person1=new Person(); 
person1.sayName();   //Mary-来自原型

var person2= new Person(); 
person2.sayName();   //Mary-来自原型

person1 和 person2 访问的都是同一组属性和同一个 sayName()函数。

理解原型对象

无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个
prototype 属性,这个属性指向函数的原型对象。

在默认情况下,所有原型对象都会自动获得一个 constructor (构造函数)属性,这个属性包含一个指向 prototype 属性所在函数的指针。

创建了自定义的构造函数之后,其原型对象默认只会取得 constructor 属性;至于其他方法,则都是从 Object 继承而来的。

当调用构造函数创建一个新实例后,该实例的内部将包含一个指针[[Prototype]](内部属性),指向构造函数的原型对象。

在这里插入图片描述
每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性。搜索首先从对象实例本身开始。如果在实例中找到了具有给定名字的属性,则返回该属性的值;如果没有找到, 则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性。如果在原型对象中找到了这个属性,则返回该属性的值。

例如:

function Person(){};
Person.prototype.name="Mary";
Person.prototype.age=18;
Person.prototype.job="teacher";
Person.prototype.sayName=function (){
    alert(this.name);
}
var person1=new Person();
person1.name="jack";
person1.sayName();   //jack-来自实例

var person2= new Person();
person2.sayName();   //Mary-来自原型

当为对象实例添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性;换句话说,添加这个属性只会阻止我们访问原型中的那个属性,但不会修改那个属性。

即使将这个属性设置为 null,也只会在实例中设置这个属性,而不会恢复其指向原型的连接。

不过,使用 delete 操作符则可以完全删除实例属性,从而让我们能够重新访问原型中的属性。

例如:

function Person(){};
Person.prototype.name="Mary";
Person.prototype.age=18;
Person.prototype.job="teacher";
Person.prototype.sayName=function (){
    alert(this.name);
}
var person1=new Person();
person1.name="jack";
delete person1.name;
person1.sayName();   //Mary-来自原型

var person2= new Person();
person2.sayName();   //Mary-来自原型

使用 hasOwnProperty()方法可以检测一个属性是存在于实例中,还是存在于原型中。这个方法(不要忘了它是从 Object 继承来的)只在给定属性存在于对象实例中时,才会返回 true。

例如:

function Person(){};
Person.prototype.name="Nicholas"; 
Person.prototype.age=29; 
Person.prototype.job= "Software Engineer"; 
Person.prototype.sayName=function (){
    alert(this.name);
};
var person1 = new Person(); 
var person2 = new Person(); 
 
alert(person1.hasOwnProperty("name"));  //false 
person1.name = "Greg"; 
alert(person1.name);     //"Greg"——来自实例 
alert(person1.hasOwnProperty("name"));  //true 
 
alert(person2.name);     //"Nicholas"——来自原型 
alert(person2.hasOwnProperty("name"));  //false 
 
delete person1.name; 
alert(person1.name);     //"Nicholas"——来自原型 
alert(person1.hasOwnProperty("name"));  //false 
 

在这里插入图片描述

更简单的原型语法

前面例子中每添加一个属性和方法就要敲一遍 Person.prototype。为减少不必要的输入,也为了从视觉上更好地封装原型的功能,更常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象。

function Person(){};
Person.prototype = {
    name: "Nicholas",
    age: 29,
    job: "Software Engineer",
    sayName:function (){
        alert(this.name);
    }
};

这样创建之后constructor 属性不再指向 Person 了,变成了新对象的 constructor 属性。此时,尽管 instanceof 操作符还能返回正确的结果,但通过 constructor 已经无法确定对象的类型了,如下所示。

var friend = new Person(); 
 
alert(friend instanceof Object);       //true 
alert(friend instanceof Person);       //true 
alert(friend.constructor == Person);    //false 
alert(friend.constructor == Object);   //true 

如果 constructor 的值真的很重要,可以像下面这样特意将它设置回适当的值。

 function Person(){ } 
 Person.prototype = {
      constructor : Person,
      name : "Nicholas",
      age : 29,
      job: "Software Engineer",
      sayName : function () {
               alert(this.name);     
               } 
      }; 

以上代码特意包含了一个 constructor 属性,并将它的值设置为 Person,从而确保了通过该属性能够访问到适当的值。
注意,以这种方式重设 constructor 属性会导致它的[[Enumerable]]特性被设置为 true。

原型的动态性

由于在原型中查找值的过程是一次搜索,因此我们对原型对象所做的任何修改都能够立即从实例上反映出来——即使是先创建了实例后修改原型也照样如此。

var friend = new Person(); 
 
Person.prototype.sayHi = function(){     alert("hi"); }; 
 
friend.sayHi();   //"hi"(没有问题!) 

当我们调用 person.sayHi() 时,首先会在实例中搜索名为 sayHi 的属性,在没找到的情况下,会继续搜索原型。因为实例与原型之间的连接只不过是一个指针,而非一个副本,因此就可以在原型中找到新的 sayHi 属性并返回保存 在那里的函数。

调用构造函数时会为实例添加一个指向初原型的 [[Prototype]]指针,而把原型修改为另外一个对象就等于切断了构造函数与初原型之间的联系。 请记住:实例中的指针仅指向原型,而不指向构造函数。

function Person(){ } 
 
var friend = new Person();          
Person.prototype = {
     constructor: Person,     
     name : "Nicholas",     
     age : 29,     
     job : "Software Engineer",     
     sayName : function () {
              alert(this.name);     
              } 
  }; 
 
friend.sayName();   //error 

我们先创建了 Person 的一个实例,然后又重写了其原型对象。然后在调用 friend.sayName()时发生了错误,因为 friend 指向的原型中不包含以该名字命名的属性。

在这里插入图片描述
重写原型对象切断了现有原型与任何之前已经存在的对象实例之间的联系;它 们引用的仍然是初的原型。

原生对象的原型

原型模式的重要性不仅体现在创建自定义类型方面,就连所有原生的引用类型,都是采用这种模式创建的。所有原生引用类型(Object、Array、String,等等)都在其构造函数的原型上定义了方法。

例如,在 Array.prototype 中可以找到 sort()方法,而在 String.prototype 中可以找到 substring()方法。

通过原生对象的原型,不仅可以取得所有默认方法的引用,而且也可以定义新方法。可以像修改自定义对象的原型一样修改原生对象的原型,因此可以随时添加方法。

例如:String 添加了一个名为 startsWith()的方法。

String.prototype.startsWith = function (text) {
     return this.indexOf(text) == 0; 
     }; 
 var msg = "Hello world!"; 
alert(msg.startsWith("Hello"));   //true 

原型对象的问题

原型中所有属性是被很多实例共享的,这种共享对于函数非常合适。对于那些包含基本值的属性倒也说得过去,毕竟(如前面的例子所示),通过在实例上添加一个同名属性,可以隐藏原型中的对应属 性。然而,对于包含引用类型值的属性来说,问题就比较突出了。
例如:

function Person(){ } 
 
Person.prototype = {
     constructor: Person,
     name : "Nicholas",
     age : 29, 
     job : "Software Engineer",
     friends : ["Shelby", "Court"],     
     sayName : function () {
              alert(this.name);    
               } 
}; 
 
var person1 = new Person(); 
var person2 = new Person(); 
 
person1.friends.push("Van"); 

alert(person1.friends);    //"Shelby,Court,Van" 
alert(person2.friends);    //"Shelby,Court,Van" 
alert(person1.friends === person2.friends);  //true 

由于 friends 数组存在于 Person.prototype 而非 person1 中,所以刚刚提到的修改也会通过 person2.friends(与 person1.friends 指向同一个数组)反映出来

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值