javascript面向对象程序设计系列(一)---建立对象

javascript是一种基于对象的语言,但它没有类的概念,因此又和实际面向对象的语言有区别,面向对象是javascript中的难点之一。如今就我所理解的总结一下,便于之后复习:javascript

1、建立对象java

一、建立自定义对象最简单的方式就是建立Object的实例,并在为其添加属性和方法,以下所示:数组

var cat = new Object();   //以猫咪为对象,添加两个属性分别为猫咪姓名和猫咪花色,并添加一个方法,说出猫咪的姓名
cat.name = "cc";
cat.color = "white";
cat.say = function(){
     alert(this.name);  
}

近几年,对象字面量成为建立对象的首选方式,以下:函数

var cat = {
       name: "cc",
       color: "white",
      
       say: function(){
               alert(this.name);
       }
}    

以上两种方式均可以用来建立单个对象,但这些方式具备明显的缺点:在建立多个同一类型的对象的时候,会产生不少重复代码,所以,人们开始使用工厂模式的一种变体来解决这个问题。测试

二、工厂模式建立对象this

利用工厂模式的思想,用一个函数来封装建立对象的细节,并提供特定接口来实现建立对象,上面的例子能够写成以下:spa

function createCat(name,color){
    var o = new Object();
    o.name = name;
    o.color = color;
    o.say = function(){
             alert(this.name);
     }
    return o;
}

var cat1 = createCat("cc","white");
var cat2 = createCat("vv","black"); 

函数createCat()能够根据接受到的猫咪的姓名和花色来建立一个猫咪对象,咱们能够重复调用这个函数,每次调用都会返回一个包含两个属性和一个方法的对象,该对象表面了猫咪的姓名和花色,并能够说出猫咪的姓名。prototype

利用工厂模式建立对象能够很好的解决上面的定义大量重复代码的问题,但咱们却没法真正区分对象的类型,咱们只能知道建立的对象是Object,而没法识别cat1和cat2都是猫咪对象。为解决这个问题,javascript提出了一种构造函数模式。指针

三、构造函数模式code

咱们知道在ECMAScript中定义了不少原生的构造函数,好比说Array、Date,咱们能够用这些原生构造函数建立特定类型的对象。此外,javascript也容许咱们建立自定义的构造函数,定义对象类型的属性和方法,从而用于建立某一类的对象。咱们利用构造函数模式重写上面的例子以下:

function Cat(name,color){
       this.name = name;
       this.color = color;
        
       this.say = function(){
              alert (this.name)
       }
}

var cat1 = new Cat("cc","white");
var cat2 = new Cat("vv","black");

咱们能够用Cat()函数代替createCat()函数,相对于createCat()函数,Cat()函数存在如下不一样:

1>   没有显示的建立对象;

2>   直接将属性和方法付给了this对象;

3>   没有return语句;

同时,咱们还注意到Cat()函数的函数名首字母大写。按照书写规范,构造函数最好都以大写字母开头,其它函数都以小写字母开头,固然小写字母开头的函数也能够当成构造函数来建立对象。其实构造函数本质就是一个函数,只不过用它来建立对象。

当咱们使用Cat()构造函数建立一个实例对象时,必须使用new运算符,使用这种方式建立对象会通过如下4个步骤:

1>   建立一个新对象

2>   将构造函数中this指向新建立的对象

3>   执行构造函数中的语句(即为新对象添加属性和方法)

4>   返回这个新对象

在上面的例子中,分别建立了猫咪的两个不一样实例,这两个实例对象都有一个constructor(构造函数)属性,用于指向建立实例的构造函数,即Cat()

cat1.constructor == Cat      //=>  true
cat2.constructor == Cat      //=>  true

对象的constructor属性是用来标识对象类型的。但javascript提供了一个操做符instanceof来检测对象的类型,上面例子中建立的对象cat1和cat2便是Object的实例也是Cat的实例

cat1 instanceof Object   //=>   true
cat1 instanceof Cat        //=>   true
cat2 instanceof Object   //=>    true
cat2 instanceof Cat        //=>   true

以上可知,利用构造函数模式,咱们能够将建立的对象标识为一种特定的类型。上面咱们也提到过构造函数其实也是函数,它和普通函数的区别就在于调用方式的不一样。任何函数,只要经过new运算符来调用,就能够当作构造函数,而任何函数,不经过new运算符调用,就和普通的函数调用没有区别,看下面的实例:

//当作构造函数使用
var cat1 = new Cat("cc","white");
cat1.say();    //=>  cc

//当作普通函数调用
Cat("vv","block");   //普通函数运行,其this默认为window(ECMAScript3下)
window.say();   //=>   vv

//在另外一个对象做用域中调用
var o = new Object();
Cat.call(0,"ss","yellow");
o.say();   //=>   yellow

以上三种使用方式,注意this的值

构造函数虽然解决了上面存在的一些问题,但它也有本身的缺点,就是每当建立一个对象时,其内部的全部属性和方法都会从新建立一次,都会占有必定的内存,上面实例中建立的两个猫咪对象cat1和cat2,它们的say()方法虽然相同,但却不是同一个实例,占有不一样的内存。

cat1.say == cat2.say;    //=>  false

但在实际使用中,每一个对象中的方法实现的是一样的功能,咱们彻底没有必要建立多个Function实例,所以咱们能够经过将函数定义到构造函数外面来解决这个问题。

function Cat(name,color){
      this.name  = name;
      this.color = color;
      this.say  =  sayName;
}

function sayName(){
      alert(this.name);
}

var cat1 = new Cat("cc","white");
var cat2 = new Cat("vv","black");

上例中,咱们将say函数移到了外面,这样cat1和cat2就能够共享在全局中定义的sayName函数了,但这样作又存在一个新问题,就是定义在全局中的函数其实只是被某一类对象所调用,又使得全局做用域过于混乱,而对象也没有封装性可言了。这时,咱们可使用原型模式来解决。

四、原型模式

javascript中,咱们建立的每一个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象中的全部属性和方法都会被构造函数建立的实例对象所继承,即每一个函数的prototype都是经过调用该构造函数而建立的实例对象的原型对象。咱们看一个例子

function Person(){
}

Person.prototype.name = "cc";
Person.prototype.age = "2";
Person.prototype.job = "software engineer";
Person.prototype.sayName = function(){
     alert(this.name);
}

var person1 = new Person();
person1.sayName();     //=> "cc"

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

person1.sayName == person2.sayName;   //=> true

咱们将Person的属性都放在其原型对象中,其建立的实例对象person1和person2都包含一个内部属性,该属性指向Person的原型函数Person.prototype。咱们用图示表示(在这里偷懒,直接上高程的图了):

 

上图中展现了Person构造函数,Person的原型属性以及Person的两个实例对象之间的关系。其中,Person具备一个prototype属性指向其原型对象,而Person的原型对象中又有一个constructor函数指向Person。其两个实例对象的内部属性[[Prototype]](目前尚未标准的方式访问)都指向了Person.prototype。从上图咱们能够看出,其实建立的实例对象和构造函数并无直接关系。

上面的例子中,虽然person1和person2都不具备属性和方法,但却能够调用sayName()方法。每当读取对象的某个属性时,都会执行一次搜索,首先搜索实例对象自己,若是没有找到则继续搜索对象指向的原型对象,若是有则返回,若是没有则返回undefined。

上面的对象实例能够访问原型中的属性和方法,但却不能重写原型中的值。若是咱们在实例中重写一个与原型中相同名字的属性,就会在实例中建立该属性,并屏蔽原型中的同名属性,但不会修改原型中的属性。以下所示:

function Person(){
}

Person.prototype.name = "cc";
Person.prototype.age = "2";
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
     alert(this.name);
}

var person1 = new Person();
var person2 = new Person();

person1.name = "vv";
person1.name;   //=>   vv
person2.name;  //=>   cc
Person.prototype.name;  //=>  cc

前面实例中每次给原型添加属性和方法都要输入Person.prototype。所以咱们可使用对象字面量来重写原型对象,以下所示:

function Person(){

}

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

上面的代码定义的Person.prototype与上面定义的原型对象几乎相同,但又一个例外,使用对象字面量从新定义原型对象(至关于从新定义了一个对象),其constructor属性就不在指向Person了,而是指向Object构造函数。此时使用instanceof操做符还能够返回正确结果,但使用constructor属性则没法肯定对象的类型了。

var newPerson = new Person();

newPerson instanceof Object;   //=>  true
newPerson instanceof Person;  //=>  true
newPerson.constructor == Object;  //=> true
newPerson.constructor == Person;  //=> false

若是咱们须要constructor属性,能够手动设置其值。

注意:咱们能够随时添加原型的属性和方法,并能够在实例中当即反应过来,但咱们必定要注意重写原型对象的位置。调用构造函数建立对象会添加一个指向原型的指针,若是咱们重写对象的原型函数,就切断了已建立实例和构造函数的关系。

function Person1(){

}

Person1.prototype.name = "cc";
Person1.prototype.say = function(){
        alert(this.name);
}

var friend1 = new Person1();
friend.say();  //=>  cc

function Person(){
}

var friend = new Person();

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

friend.sayName();  //=> error  

 

原型模式的缺点:

1>  没法经过构造函数传递初始化参数

2>  引用类型的实例共享

咱们看一个实例:

function Person(){

}

Person.prototype = {
        constructor : Person,
        name : "cc",
        age : 2,
        job : "Software Engineer",
        friends : ["vv","dd"],
        sayName : function(){
               alert(this.name);
        }  
}

var person1 = new Person();
var person2 = new Person();

person1.friends.push("aa");

person1.friends;   //=>  vv,dd,aa
person2.friends;  //=>   vv,dd,aa
person1.friends == person2.friends;  //=>  true

以上代码,当咱们修改了person1.friends时,相应的person2.friends也会改变,由于它们指向同一个数组。但在实际中,咱们一般但愿实例拥有本身的独立的属性,所以提出了一个组合使用构造函数模式和原型模式的方法,这种混合模式,是目前使用最普遍、认同度最高的一种建立自定义类型的方法。

function Cat(name,color){
     this.name = name;
     this.color = color;
     this.firends = ["aa","bb"];
}

Cat.prototype = {
     constructor : Cat,
     sayName : function(){
           alert(this.name);
     }
}

var cat1 = new Cat("cc","white");
var cat2 = new Cat("vv","block");

cat1.firends.push("dd");
cat1.firends;  // =>   aa,bb,dd
cat2.firends;  //=>   aa,bb
cat1.firends == cat2.firends;  //=>  false
cat1.sayName == cat2.sayName;   //=> true

从以上实例可知,将咱们但愿实例对象独立拥有的属性放到构造函数中,将咱们但愿实力对象共享的属性都放到原型中,有效的解决了上面存在的问题。

五、检测对象类型的几个方法

1>  isPrototypeOf() 方法

目前咱们在全部实现中都没法访问到[[prototype]],但咱们能够经过isPrototypeOf()方法来肯定对象之间是否存在这种关系,若是一个对象的[[prototype]]属性指向调用isPrototypeOf()方法的对象,就返回true,不然返回false,以下:

Person.prototype.isPrototypeOf(person1);   //=> true
Person.prototype.isPrototyoeOf(person2);   //=> true

以上,咱们用Person的原型对象测试person1和person2,都返回true。

2>  Object.getPrototypeOf() 方法 (ECMAScript5新定义)

这个方法返回[[prototype]]的值,如:

Object.getPrototypeOf(person1) == Person.prototype;  //=>  true
Object.getPrototypeOf(person1).name;  //=>  "cc"

3>  hasOwnProperty() 方法

该方法用于检测一个属性是否存在于一个实例中,而不是存在于原型中,如:

person1.name;  //=> cc   来自于原型
person1.hasOwnProperty("name");  //=>  false

person1.name = "vv";  //=>  来自于实例
person1.hasOwnProperty("name");  //=>  true

上面例子中,当person1的name属性来自于原型时,hasOwnProperty()返回false,给person1重写name属性后,则返回true。

4>  in运算符

有两种方式使用in操做符:单独使用或者再for-in循环中使用。

单独使用时,in运算符能够用来检测给定属性是否可以被对象所访问,无论该属性是存在于实例中仍是原型中

person1.name;   //=>  cc  //=>来自原型
"name" in person1;  //=>  true

person1.name = "vv";  //=>来自实例中
"name" in person1; //=>  true

上面例子中可见,不管对象的属性来自原型仍是来自实例,只要能被person1对象访问就返回true

在for-in循环中,返回全部可以被对象访问的、可枚举的属性,其中即包括实例中的属性,也包括原型中的属性。屏蔽了原型中不可枚举的属性的实例属性也会在for-in循环中返回,由于全部开发人员定义的属性都是可枚举的(IE8及更早版本下不会返回)。

相关文章
相关标签/搜索