Js原型模式

function Person(){
}
Person.prototype.name = "xd";
Person.prototype.age = 26;
Person.prototype.sayName = function(){
    alert(this.name);     
};
var person1 = new Person();
person1.sayName();//"xd"
var person2 = new Person();
person2.sayName();//"xd"
alert(person1.sayName==person2.sayName);//true

 代码讲解:

第一个函数,可以称其为构造函数,在js中函数就是对象,因此每定义一个函数就相当于创建了一个对象,这里我们相当于创建了一个名为Person的对象。

2-3行中有一个prototype(原型)属性,这个属性是一个指针,指向要被“new”的对象,即原型对象。将对象的属性都添加到原型对象中。

  为什么要用到原型模式来创建对象那?

先来介绍比较常见的两种创建对象的方法:

第一种,工厂模式

示例代码如下:

function createPerson(name,age,job)
{
     var o = new Object();
     o.name = name;
     o.age = age;
     o.job = job;
     o.sayName = function(){
        alert(this.name);
     }   
     return o;
}
var person1 = createPerson("a",26,"aa");
var person2 = createPerson("b",26,"bb");

工厂模式创建对象的方法是,在方法中创建一个Object类,然后对这个实例的属性赋值,最后返回这个Object实例。通过调用这个方法,就可以获得一个期望的对象。但是这个方法有一个缺陷,就是无法知道你所创建对象的类型。

第二种,构造函数模式

示例代码如下:

function Person(name,age,job)
{
  this.name = name;
  this.age = age;
  this.job = job;
  this.sayName = function(){
    alert(this.name);
  };
}
var person1 = new Person("a",26,"aa");
var person2 = new Person("b",26),"bb";
alert(person1.constructor==Person);//true
alert(person1 instanceof Object);//true
alert(person1 instanceof Person);//true

"this"指向的是你"new"的对象。

person1和person2分都有一个constructor(构造函数)属性,该属性指向Person。

 任何函数,只要通过new操作来调用,那它就可当做构造函数,如果不用new来调用,那它和普通函数没有区别。

例如:

//当做构造函数来使用
var person = new Person("a",26,"aa");
person.sayName();
//当做普通函数来调用
Person("a",26,"aa");//这里this指向了window对象,即以window对象作为原型。
window.sayName();
//在另一个对象的作用域中来用。
var o = new Object();
Person.call(o,"a",26,"aa");//这里把o对象作为原型,this指向了对象o。(这里的call也可以换为apply)
o.sayName();

构造函数创建实例的缺点:

每个构造函数在用于创建实例时,其中的方法都要重新创建一遍。person1和person2中的sayName()方法不是同一个Function实例。

解决方法如下:

function Person(name,age,job)
{
    this.name = name;
    ...
    this.sayName = sayName;
}
function sayName()
{
    alert(this.name);
}
...

这里sayName属性保存的是指向函数sayName()的指针,解决了上述问题。
新问题:这里定义的在全局域定义的函数只能被某个对象调用,作为全局作用域有点...

其次,如果构造函数中有很多方法,就要定义很多全局函数,就没有丝毫封装性可言。

所以最终我们选用了原型模式。

   原型对象的问:

问题1:它省略了构造函数初始化参数这一环节,结果所有的实例在默认的情况下都将取得相同的属性值。

问题2:原型中所有的属性都是共享的,这种共享对于函数很合适,但对于包含引用类型的属性来说,就会出现问题。

例如:

function Person()
{}
Person.prototype = {
    constructor:Person,
    name:"a",
    job:"b",
    friends:["c","d"],
    sayName(){
        alert(this.name);
    }
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push("e");
alert(person1.friends);//"c","d","e"
alert(person2.friends);//"c","d","e"
alert(person1.friends===person2.friends);//true

从上边的代码可以看出本来person1与person2中的friends是不应该相同的,结果由于将属性friends属性放在了原型对象中,导致期所有的实例的所有属性都相同了。

  解决办法就是组合使用构造函数模式和原型模式。

构造函数模式用于定义实例属性。

原型模式用于定义方法和共享属性。

最终每个实例都有自己的一份实例属性的副本,但同时又共享了对方法的引用。

例如:

function Person(name,age,job)
{
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ["a","b"];
}
Person.prototype = {
    constructor:Person,
    sayName = function(){
        alert(this.name);
    }
}

   动态原型模式:

如果大家觉得将一个对象分成两个方法来写有点别扭,那么可以用动态原型模式来书写。

代码如下:

function Person(name,age,job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ["a","b"];
    if(typeof this.sayName!="function")
    {
        Person.prototype.sayName = function(){
            alert(this.name);
        }
    }
}

var person1 = new Person("a","b","c");
person1.sayName();
var person2 = new Person("e","f","g");
person2.sayName();

上边代码只有在第一次实例化时才初始化原型属性,而且判定条件只需要原型中的一个属性即可。
注意:不可以在动态原型模式中使用对象字面量来重写原型。

 

posted @ 2013-06-18 16:23  云端天籁  阅读(214)  评论(0)    收藏  举报