javascript:深入理解javascript原型模式

本文详细介绍了JavaScript中构造函数和原型的概念,解释了如何利用这两种模式创建对象,并讨论了各自的优缺点。此外,还探讨了如何结合使用这两种模式来创建自定义类型。

 在javascript中,我们创建的每个函数都有一个 prototype(原型)属性,这个属性是一个指针,指向一个对象,这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。如果按照字面意思来理解,那么 prototype 就是通过调用构造函数而创建的那个对象实例的原型对象。使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。换句话说,不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中,如下面的例子所示。

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();
person1.sayName();    //"Nicholas"

var person2 = new Person();
person2.sayName();    //"Nicholas"

 只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个 prototype属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性包含一个指向 prototype 属性所在函数的指针。

 以之前使用的Person构造函数和Person.prototype创建的代码为例,如下图:


 在此,Person.prototype 指向了原型对象,而 Person.prototype.constructor 又指回了 Person。原型对象中除了包含 constructor 属性之外,还包括后来添加的其他属性。 Person 的每个实例——person1 和 person2 都包含一个内部属性,该属性仅仅指向了Person.prototype;换句话说,它们与构造函数没有直接的关系。虽然这两个实例都不包含属性和方法,但我们却可以调用 person1.sayName()。这是通过查找对象属性的过程来实现的。

  • isPrototypeOf()

在现实中无法访问到[[Prototype]],但是可以通过 isPrototypeOf()方法来确定对象之间是否存在调用关系。

alert(Person.prototype.isPrototypeOf(person1)); //true
alert(Person.prototype.isPrototypeOf(person2)); //true
  • Object.getPrototypeOf()

ECMAScript 5 增加了一个新方法Object.getPrototypeOf(),用来返回对象原型:

alert(Object.getPrototypeOf(person1) == Person.prototype); //true
alert(Object.getPrototypeOf(person1).name); //"Nicholas"
  • hasOwnProperty()

可以检测一个属性是存在于实例中,还是存在于原型中。

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  

原型与in操作符

有两种方式使用 in 操作符:单独使用和在 for-in 循环中使用:

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
alert("name" in person1); //true
person1.name = "Greg";
alert(person1.name); //"Greg" —— 来自实例
alert(person1.hasOwnProperty("name")); //true
alert("name" in person1); //true
alert(person2.name); //"Nicholas" —— 来自原型
alert(person2.hasOwnProperty("name")); //false
alert("name" in person2); //true
delete person1.name;
alert(person1.name); //"Nicholas" —— 来自原型
alert(person1.hasOwnProperty("name")); //false
alert("name" in person1); //true  

var o = {
  toString : function(){
    return "My Object";
  }
};
for (var prop in o){
  if (prop == "toString"){
    alert("Found toString"); //在 IE 中不会显示
  }
} 
  • Object.keys()

ECMAScript5定义的Object.keys()方法,这个党法接收一个对象作为参数,返回一个包含所有可枚举属性的字符串数组。

var keys = Object.keys(Person.prototype);
alert(keys); //"name,age,job,sayName"  
  • Object.getOwnPropertyNames()

无论它是否可枚举,都可以使用 Object.getOwnPropertyNames()方法。

var keys = Object.getOwnPropertyNames(Person.prototype);
alert(keys); //"constructor,name,age,job,sayName"

var keys = Object.getOwnPropertyNames(Person.prototype);
alert(keys); //"constructor,name,age,job,sayName"

简单的原型语法
function Person(){
}
Person.prototype = {
  name : "Nicholas",
  age : 29,
  job: "Software Engineer",
  sayName : function () {
    alert(this.name);
  }
};  

上面的写法等同于以对象字面量形式创建的新对象,但是constructor 属性不再指向 Person 了。constructor属性变成了新的对象的constructor属性(指向Object构造函数),不再指向Person函数,如下所示:

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值很重要,可以设置回恰当的值,但是不推荐这种写法,会隐式的修改javascript对象的默认属性

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

原型对象的缺点

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

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  

组合使用构造函数模式和原型模式

构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。

结果,每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存。

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

这种构造函数与原型混成的模式,是目前在 ECMAScript 中使用最广泛、认同度最高的一种创建自定义类型的方法。可以说,这是用来定义引用类型的一种默认模式

动态原型模式

动态原型模式把所有信息都封装在了构造函数中,而通过在构造函数中初始化原型(仅在必要的情况下),又保持了同时使用构造函数和原型的优点。换句话说,可以通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型。

function Person(name, age, job){
  //属性
  this.name = name;
  this.age = age;
  this.job = job;  
  //方法
  if (typeof this.sayName != "function"){
    Person.prototype.sayName = function(){
      alert(this.name);
    };
  }
}
var friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName();  

其中, if 语句检查的可以是初始化之后应该存在的任何属性或方法——不必用一大堆if 语句检查每个属性和每个方法;只要检查其中一个即可。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值