在 js 中,对象由特性(attribute)构成,特性可以是原始值,也可以是引用值。如果特性存放的是函数,它将被看作对象的方法(method),否则该特性被看作对象的属性(property)。在js中创建对象一般有以下几种方法:
一.工厂模式
1 function person(name,age){ 2 var o={}; 3 o.name=name; 4 o.age=age; 5 o.sayHello=function(){ 6 alert('hi,I am '+this.name) 7 }; 8 return o; 9 } 10 var liLei=person('liLei',25); 11 var hanHei=person('hanHei',22); 12 liLei.sayHello(); //hi,I am liLei 13 hanHei.sayHello(); //hi,I am hanHei
console.log(liLei.sayHello==hanHei.sayHello) //false
我们创建了一个person函数,并传递了name和age两个参数,最后返回一个object。每次只需调用person函数我们便可以生成一个具有name,age属性及sayHello方法的对象,这种方法有个问题,就是每次通过调用person方法创建对象的时候都会新创建一个sayHello方法,而事实上,我们所创建的实例都共享一个方法,所以我们可以修改如下,将sayHello方法定义在函数外面,这样每次创建对象所引用的sayHello都为同一个方法;
// JavaScript Document
function sayHello(){
alert('hi,I am '+this.name)
}
function person(name,age){
var o={};
o.name=name;
o.age=age;
o.sayHello=sayHello;
return o;
}
var liLei=person('liLei',25);
var hanHei=person('hanHei',22);
liLei.sayHello(); //hi,I am liLei
hanHei.sayHello(); //hi,I am hanHei
console.log(liLei.sayHello==hanHei.sayHello) //true
console.log(liLei.prototype==hanHei.prototype) //false
工厂模式有一个问题,就是创建的实例之间没有联系,于是又有了构造函数模式。
二.构造函数模式
1 function Person(name,age){ 2 this.name=name; 3 this.age=age; 4 this.sayHello=function(){ 5 alert('hi,I am'+this.name) 6 }; 7 } 8 var liLei=new Person('liLei',25); 9 var hanHei=new Person('hanHei',22); 10 liLei.sayHello(); //hi,I amliLei 11 hanHei.sayHello(); //hi,I amhanHei 12 console.log(liLei.constructor==hanHei.constructor) //true
我们习惯将构造函数的首字母大写,当然我们也可以像上面那样,将sayHello方法定义到外面。在构造函数模式调用时,必须加new,否则就会发生错误,因为如果不调用new的话,函数内的this将指向window,为了避免这种问题,我们可以改进一下上述方法。
function Person(name,age){ if(this instanceof Person){ this.name=name; this.age=age; this.sayHello=function(){ alert('hi,I am'+this.name) }; }else{ return new Person(name,age) } } var liLei=Person('liLei',25); var hanHei=new Person('hanHei',22); liLei.sayHello(); //hi,I amliLei hanHei.sayHello(); //hi,I amhanHei
在函数内部我们新增了一个判断,如果调用函数时没加new,则当前this值为window,并不是Person的实例,因此我们返回一个新的实例。
当然构造函数除了像上面将方法定义在外面并没有解决实例方法重复的问题。以此就有了原型。
三.原型对象
关于原型对象;
在js中,在我们创建一个函数时,就会有一个默认的prototype指针指向一个对象,我们称之为原型对象,该对象的所有属性和方法都可以被实例所继承。
function Person(){ } Person.prototype.name='liLei'; Person.prototype.age=25 Person.prototype.sayHello=function(){ alert(1) } var liLei=new Person(); var hanHei=new Person(); liLei.sayHello(); hanHei.sayHello() alert(liLei.name+'---'+hanHei.name)
我们看到,实例对象都继承了原型对象的属性和方法,当然我们也可以为实例添加与原型对象相同名称的属性,这样我们访问该属性的时候就会返回实例属性而非原型属性。实例属性会屏蔽掉原型对象,但是不能通过实例属性修改原型属性。访问属性顺序大概为 实例属性---》原型属性,如下:
1 function Person(){ 2 3 } 4 Person.prototype.name='liLei'; 5 Person.prototype.age=25 6 Person.prototype.sayHello=function(){ 7 alert(1) 8 } 9 var liLei=new Person(); 10 alert(liLei.name); //liLei 11 liLei.name='name'; 12 alert(liLei.name); //name 13 delete liLei.name; 14 alert(liLei.name) //liLei
我们经常见到这样一种写法
function Person(){ } Person.prototype={ name:'lilei', age:25, sayHello:function(){ } } Person.prototype.constructor=Person
在对象字面量的写法中,我们在下面紧接着加了一句,原型对象的constructor属性指向Person。这是因为在js中,我们创建函数即Person时,就会有一个prototype属性指向该函数的原型对象,当我们使用对象字面量为原型对象赋值时,本质上等于完全重写了默认的prototype对象,因此constructor属性也就变成了新对象的constructor属性(指向object构造函数),因此为了指向的正确,我们一般都会在后面通过手动设置constructor属性来使指向正确。
通过原型对象创建的实例都有共同的属性prototype,可以通过isPrototype()方法来验证,但是原型对象也有一个问题,当遇到属性值为引用类型值时,会出现共享
1 function Person(){ 2 3 } 4 Person.prototype.color=['red','blue']; 5 Person.prototype.age=25 6 Person.prototype.sayHello=function(){ 7 alert(1) 8 } 9 var person1=new Person(); 10 var person2=new Person(); 11 person1.color.push('green'); 12 alert(person1.color); //red,blue,green 13 alert(person2.color); //red,blue,green
上面的代码中,属性 color为原型属性,是引用类型,当person1对color push一个新值时,结果会反映到person2中,这显然不是我们想要的结果。
四.组合模式
最常用的方法时结合构造函数及原型模式:
1 function Person(name,age,color){ 2 this.name=name; 3 this.age=age; 4 this.color=['red','blue'] 5 } 6 Person.prototype.sayHello=function(){ 7 alert(1) 8 } 9 var person1=new Person('li',25); 10 var person2=new Person('han',22); 11 person1.color.push('green'); 12 alert(person1.color); //red,blue,green 13 alert(person2.color) //red,blue
所有的非函数属性都在构造函数中创建,意味着又能够用构造函数的参数赋予属性默认值了。修改一个实例对象中的引用类型不会影响到另一个实例对象,所有实例对象都引用原型对象的方法。
五.动态原型模式
1 function Person(name,age,color){ 2 this.name=name; 3 this.age=age; 4 this.color=['red','blue']; 5 if(!typeof this.sayHello=='function'){ 6 Person.prototype.sayHello=function(){ 7 alert(1) 8 } 9 } 10 } 11 var person1=new Person('li',25); 12 var person2=new Person('han',22); 13 person1.color.push('green'); 14 alert(person1.color); //red,blue,green 15 alert(person2.color) //red,blue