`

javascript之创建对象

    博客分类:
  • js
 
阅读更多
1.工厂模式
        我们可以把JavaScript中的对象看作成散列表,无非就是一组键值对,其中值可以是数据或函数。每个对象都是基于引用类型创建的。Object构造函数或对象字面量都可以用来创建单个对象,但是这些方式有个明显的缺点:使用同一个接口创建很多对象,会产生大量的重复代码。我们可以使用工厂模式,用函数来封装以特定接口创建对象的细节,如:
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("wuyu", 20, "enginneer");
var person1 = createPerson("zhangsan", 29, "tearcher");
 
    根据接受的参数通过createPerson()函数构建包含必要信息的Person对象,可以无数次的调用这个函数并且返回包含三个属性和一个方法的对象。
    工厂模式虽然很好的解决的创建多个相似对象的问题,但却没有解决对象识别的问题,即怎么知道一个对象的类型。这里我们可以用构造函数模式来解决。
2.构造函数模式
        ECMAScript中的构造函数可用来创建特定类型的对象。像Object和Array这样的原生构造函数,在运行时会自动出现在执行环境中。此外,也可以创建自定义的构造函数,从而定义自定义对象类型的属性和方法。如:
    function Person(name,  age, job) {
        this.name = name;
        this.age = age;
        this.job = job;
        this.sayName = function(){
            alert(this.name);
        }
    }
    var person1 = new Person("wuyu", 20, "enginneer");
    var person2 = new Person("zhangsan", 29, "tearcher");
 
    这段代码中,Person()方法(当作构造函数使用时,首字母通常大写)替代了createPerson()函数,除此之外,还存在以下不同之处:
    ●没有显式地创建对象
    ●直接将属性和方法赋给了this对象
    ●没有return 语句
要创建Person的新实例,必须使用new操作符。以这种方法调用构造函数实际上会经历以下4个步骤:
    ●创建一个新对象
    ●将构造构造函数的作用域赋给新对象(因此this就是指向了这个新对象)
    ●执行构造函数中的代码(为这个新对象添加属性)
    ●返回新对象
上面的示例,person1和person2分别保存着Person的一个不同的实例,这两个对象都有一个constructor(构造函数)属性,该属性指向Person,如:
alert(person1.constructor == Person);    //true
alert(person2.constructor == Person);    //true
 对象的consctructor属性最初是用来标识对象类型的,但是检测对象类型还是使用instanceof检测对象类型比较可靠些。这例子中创建的所有对象既是Object的实例,同时也是Person的实例.如下:
    alert(person1 instanceof Object);    //true
    alert(person1 instanceof Person);    //true
    alert(person2 instanceof Object);    //true
    alert(person2 instanceof Object);    //true
 
    创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型,而这就是构造函数模式胜过工厂模
式的地地方。
    构造函数也是函数,区别构造函数与其他普通函数,主要就在于调用它们的方式不同。任何函数,只要通过new操作符来调用,那它就可以作为构造函数;而任何函数,如果不通过new操作符来调用,那它跟普通函数也不会有什么两样。
    function Person(name,age,job){
        this.name=name;
        this.age=age;
        this.job=job;
        this.sayName=function(){
            alert(this.name);
        };
    }
 
    //当作构造函数使用
    var person = new Person("wuyan",20,"enginner");
    person.sayName(); //wuyan
 
    //作为普通函数使用
    Person("zhangsan",27,"Doctor");
    window.sayName(); //zhangsan
 
    //在另一个对象的作用域中调用(将Person()放入o作用域中调用)
    var o = new Object();
    Person.call(o,"Kristen",25,"Nuese");
    o.sayName(); //Kristen 
     构造函数模式虽然好用,但也并非没有缺点。最大的问题就是每个方法都要在每个实例上重新创建一遍。比如上面的person1和person2都有一个sayName()的方法,但那两个方法不是同一个Function的实例。ECMAScript中的函数是对象,因此每定义一个函数,也就是实例化了一个对象。创建两个实现的功能完全相同的Function实例真没必要,大可通过把函数定义转移到构造函数外部来解决这个问题。如下:
   function Person(name,age,job){
        this.name=name;
        this.age=age;
        this.job=job;
        this.sayName= sayName;
    }
    functin sayName = function(){
        alert(this.name);
    };
    var person1 = new Person("wuyu", 20, "enginneer");
    var person2 = new Person("zhangsan", 29, "tearcher");
 sayName转移到构造函数外部,而在构造函数内部我们将sayName属性设置成了全局的sayName函数。这样一来,sayName包含的是一个指向函数的指针,因此person1 和person2就共享了在全局作用域中的同一个sayName()函数。可是,对象需要定义很多方法,那么就要定义很多全局函数,于是我们这个自定义的引用类型就丝毫没有封装性可言了。这些问题我们可以使用原型模式来解决。
3.原型模式
3.1什么是原型
      我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对        象的用途是包含可以由特定类型的所有实例共享的属性和方法。prototype就是通过调用构造函数创建的那个    对象实例的原型对象。使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。如下:
    function Person() {
    }
    Person.prototype.name = "wuyan";
    Person.prototype.age = 20;
    Person.prototype.job = "engineer";
    Person.prototype.sayName = function(){
        alert(this.name);
    }
    var person1 = new Person();
    person.sayName();    //wuyan
    
    var person2 = new Person();
    person2.sayName();    //wuyan

    alert(person1.sayName == person2.sayName);    //true
 
我们将所有属性和sayName()方法直接添加到了Person的prototype属性中,在创建新对象时,这些属性和方法是由所有实例共享的。
 
3.2理解原型对象
    无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认情况下,所有原型都会自动获得一个constructor(构造函数)属性,这个属性一个指向prototype属性所在函数的指针。
    当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。这个内部属性叫做_proto_,可以在浏览调度中看见,但对于脚本来说则是完全不可见的。我们只要记住,就是这个连接存在于实例与构造函数的原型属性之间,而不是存在于实例与构造函数之间。
   
    Person.prototype指向了原型对象,而Person.prototype.constructor又指回了Person。Person.prototype的对象中除了包含constructor属性之外,还包括后来添加的其他属性。Person的每个实例person1和person2都包含一个内部属性,该属性仅仅指向了Person.prototype,它们与构造函数没有直接的关系。
    我们可以使用isPrototypeOf()方法来确定对象之间是否存在在这种关系,当对象的_proto_属性指向调用isPrototypeOf()方法的对象(Person.prototype),那么这个方法就会返回true。如:
    alert(Person.prototype.isPrototypeOf(person1));    //true
    alert(Person.prototype.isPrototypeOf(person2));    //true
 虽然可以通过对象实例访问保存在原型中的值,但去不能通过对象实例重写原型中的值。如下:
    function    Person() {
    }
    Person.prototype.name = "wuyan";
    Person.prototype.age = 20;
    Person.prototype.job = "engineer";
    Person.prototype.sayName = function(){
        alert(this.name);
    }
    var person1 = new Person();
    var person2 = new Person();
    person1.name = "poxiao";
    alert(person1.name);    //poxiao--->来自实例,在搜索name属性时,在对象实例本身就可以
//找到,就不 必再搜索原型了
    alert(person2.name);    //wuyan--->来自原型
  //当然,我们可以使用delete操作符删除实例属性,这样就可以重新访问到原型中的属性了。如下:
    function    Person() {
    }
    Person.prototype.name = "wuyan";
    Person.prototype.age = 20;
    Person.prototype.job = "engineer";
    Person.prototype.sayName = function(){
        alert(this.name);
    }
    var person1 = new Person();
    var person2 = new Person();
    person1.name = "poxiao";
    alert(person1.name);     //poxiao--->来自实例
    alert(person2.name);     //wuyan--->来自原型

    delete person1.name;
    alert(person1.name);    //wuyan--->来自原型
 使用hasOwnProperty()方法可以检测一个属性是存在于实例中,还是存在于原型中。这个方法(它是从Object继承来的)只在给定属性存在于对象实例中时,才会返回true。如下:
    function    Person() {
    }
    Person.prototype.name = "wuyan";
    Person.prototype.age = 20;
    Person.prototype.job = "engineer";
    Person.prototype.sayName = function(){
        alert(this.name);
    }
    var person1 = new Person();
    var person2 = new Person();
    
    alert(person1.hasOwnProperty("name"));    //false

    person1.name = "poxiao";
    alert(person1.name);    //poxiao-->来自实例
    alert(person1.hasOwnProperty("name"));    //true

    alert(person2.name);    //wuyan--->来自原型
    alert(person2.hasOwnProperty("name"));    //false
    
    delete person1.name;
    alert(person1.name);     //wuyan--->来自原型
    alert(person1.hasOwnProperty("name"));    //false
 
3.3原型与in操作符
    用两种方式使用in操作符:一、单独使用;二、在for-in中使用。
    单独使用时,in操作符会在通过对象能够访问属性时返回true,无论该属性存在于实例中还是原型中。如下:
    function Person(){}
 
    Person.prototype.name="wuyan";
    Person.prototype.age=20;
    Person.prototype.job="engineer";
    Person.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="poxiao";
 
    alert(person1.name); //poxiao来自实例
    alert(person1.hasOwnProperty("name")); //true
    alert("name" in person1); //true
 
    alert(person2.name); //wuyan来自原型
    alert(person2.hasOwnProperty("name")); //false
    alert("name" in person2); //true
 
    delete person1.name;
    alert(person1.name); //wuyan来自原型
    alert(person1.hasOwnProperty("name")); //false
    alert("name" in person1); //true
 
    alert(person1.hasOwnProperty("qqqq")); //false
    alert("qqqq" in person1); //false
 //同时使用hasOwnProperty()方法和in操作符,就可以确定该属性到底是存在于对象中,
//还是存在于原型中。如下:
    function hasPrototypeProperty(object, name){
        return !object.hasOwnProperty(name) && (name in object) ;
    }
 在使用for-in循环时,返回的是所有能够通过对象访问的、可枚举的(enumerated)属性,其中既包括存在于实例中的属性,也包括存在于原型中的属性。根据规定,所有开始人员定义的属性都是可枚举的(除ie8及更早版本)。如:
var o={
        toString:function(){
            return "My Object";
        }
}
 
for (var prop in o){
    if(prop == "toString"){
        alert("Found toString"); //在IE中不会显示
     }
}
 
3.4更简单的原型
    用一个包含所有属性和方法的对象字面量来重写整个原型对象。如下:
    function Person() {
    }
    Person.prototype = {
        name : "wuyan",
        age : 20,
        job : "engineer",
        sayName : function() {
            alert(this.name);
        }
    }
 
    结果与先前的相同,但有一个不相同的:constructor属性不再指向Person了。我们这样写,本质上完全重写了默认的prototype对象,因此constructor属性也就变成了新对象的constructor属性(指向Object构造函数),不再指向Person函数。
    当然,我们可以特意将它设置回适当的值。如下:
    function Person() {
    }
    Person.prototype = {
        constructor : Person,
        name : "wuyan",
        age : 20,
        job : "engineer",
        sayName : function() {
            alert(this.name);
        }
    }
 
3.5原型的动态性
    如果重写了整个原型对象,就相当于把原型修改为另外一个对象了(因为调用构造函数是会为实例添加一个指向最初原型的_proto_指针),就等于切断了构造函数与最初原型之间原联系。如下:
    function Person() {
    }
    var friend = new Person();
    
    Person.prototype = {
        constructor : Person,
        name : "wuyan",
        age : 20,
        job : "enginner",
        sayName : function() {
            alert(this.name);
        }
    };
    friend.sayName();    //error
 
3.6原生对象的原型
    原型模式的重要性不公体现在创建自定义类型方面,就连所有原生的引用类型,都是采用这种模式创建,如Object、Array、String等。
 
3.7原型对象的问题
    原型模式也是有缺点的。首先,它省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值。原型模式最大的问题是由其共享的本性所导致的,尤其对引用类型属性。如下:
   function Person() {
    }
    Person.prototype = {
        constructor : Person,
        name : "wuyan",
        age : 20,
        job : "engineer",
        friends : ["poxiao", "wyl"],
        sayName : function() {
            alert(this.name);
        }
    };
     
    var person1 = new Person();
    var person2 = new Person();
    
    person1.friends.push("wx");
    alert(person1.friends);    //poxiao,wyl,wx
    alert(person2.friends);    //poxiao,wyl,wx
    alert(person1.friends === person2.friends);     //true  问题出来了,person1结
//交了新朋友意味着person2也必须结交这个朋友
 
4.组合使用构造函数模式和原型模式
    创建自定义类型的最常见方式,就是组合使用构造函数模式与原型模式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。结果,每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内在。这种混合模式还支持向构造函数传递参数。如下:
    function Person(name ,age ,job){
        this.name = name;
        this.age = age;
        this.job = job;
        this.friends = ["poxiao", "wyl"];
    }
    Person.prototype = {
       constructor : Person,
        sayName: function() {
            this.name;
        }
    }

    var person1 = new Person("wuyan", 20, "engineer");
    var person2 = new Person("shaobo", 26, "teacher");
    
    person1.friends.push("wyl");
    alert(person1.friends);    //poxiao,wyl,wyl
    alert(person2.friends);    //poxiao,wyl
    alert(person1.friends === person2.friends);    //false
    alert(person1.sayName=== person2.sayName);    //true
 这种构造函数与原型混合的模式,是目前在ECMAScript中使用最广泛、认同度最高的一种创建自定义类型的方法。
5.动态原型模式
    在其他OO语言的开始人员在看到独立的构造函数和原型时,很可能会感到非常困惑。动态原型模式正是致力于解决这个问题的一个方案,把它所有信息都封装在了构造函数中,而通过在构造函数中初始化原型,又保持了同时使用构造函数和原型的优点。换句话说,可以通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型。如下:
   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 friends = new Person("wuyan", 20, "engineer");
    friends.sayName();
 
    使用动态原型模式时,不能使用对象字面量重写原型。如果在已经创建了实例的情况下重写原型,那么就会切断现有实例与新原型之间的联系。
 
6.寄生构造函数模式
    在前述的几种模式都不适用的情况下,可以使用寄生构造函数模式。这种模式的基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象。如下:
   function Person(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 friend = new Person("wuyan", 20, "engineer");
    friend.sayName();     //wuyan
 
除了使用new操作符并把使用的包装函数叫做构造函数外,这个模式跟工厂模式一模一样。
关于寄生构造函数模式,有一点要需要说明:首先,返回的对象与构造函数或者与构造函数的原型属性之间没有关系;也就是说,构造函数返回的对象与在构造函数外部创建的对象没有什么不同。为此,不能依赖instanceof操作符来确定对象类型。由于存在上述问题,我们建议在可以使用其他模式的情况下,不要使用这种模式。
 
7.稳妥构造函数模式
    所谓稳妥对象,批的是没有公共属性,而且其方法也不引用this的对象。稳妥对象最适合在一些安全的环境中(这些环境中会禁止使用this和new),或者在防止数据被其他应用程序改动时使用。稳妥构造函数与寄生构造函数有两点不同:一是新创建对象的实例方法不引用this;二是不使用new操作符调用构造函数。如下:
    function Person(name, age, job) {
        //创建要返回的对象
        var o = new Object();
        //可以在这定义私有变量和函数
        
        //添加方法
        o.sayName = function() {
            alert(name);
        }
        return o;
    }
    var friend = Person("wuyan", 20, "engineer");
    friend.sayName(); //wuyan
 
注意:在以这种模式创建的对象中,除了使用sayName()方法之外,没有其他办法访问name的值。这样,变量friend 中保存的就是一个稳妥对象。安全性高。
    和寄生构造函数模式一样,返回的对象与构造函数或者与构造函数的原型属性之间没一点关系。
 
 
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics