用javascript实现一个对象,一般最常用的方法是建立一个原型,单单依靠构造函数建立是不行的,比如:
function Person(name,age,college){
this.name = name;
this.age = age;
this.college = college;
this.sayName = function () {
alert(this.name);
}
}
var person1 = new Person("xuZhiWei",22,"Nanjing University of Science and Technology");
var person2 = new Person("zhaochao",23,"Nanjing University");
person1.sayName();//输出结果是xuZhiWei;
person2.sayName();//zhaochao;
alert(person1.age);//22;
alert(person2.age);//23;
这里用构造函数的方法创建了两个对象person1和person2,发现用构造函数它们各自都可以创建独立的属性,但是有一个小的缺点,每new一个Person,函数都要实例化一次(函数也是对象),比如判断person1.sayName==person2.sayName,结果是false,所以这证明了确实新建了一个实例。这样会增加内存的开销。
而另一种方法是利用prototype原型,创建一个构造函数,其就创建了一个原型对象,构造函数的prototype属性指向原型对象,同时原型对象的construction属性也指向了Person。示例如下:
function Person(){}
Person.prototype.name="xuZhiWei";
Person.prototype.age=22;
Person.prototype.sayName=function(){
return this.name;
};
alert(typeof Person.prototype.constructor =="function");//true
我们先声明了构造函数,然后给它的prototype赋值,最后判断construction的时候它的类型是function,这是因为它指向了构造函数,因为构造函数有prototype属性,所以实例应该也有prototype属性,但是不能直接访问,js提供了isPrototype方法来判断,如下例子可以证明所有实例都是指向这个原型:
function Person(){}
Person.prototype.name="xuZhiWei";
Person.prototype.age=22;
Person.prototype.sayName=function(){
return this.name;
};
var person1 = new Person();
var person2 = new Person();
alert(Person.prototype.isPrototypeOf(person1));//true;
alert(Person.prototype.isPrototypeOf(person2));//true;
alert(typeof Person.prototype.constructor =="function");//true
这说明了所有实例都是指向了构造函数的原型 ,那么它们的引用应该是一样的,包括函数,这时候执行person1.sayName==person2.sayname返回结果是true,说明引用相同。如果这时候想重写某些属性,只能给实例赋值,如下:
function Person(){}
Person.prototype.name="xuZhiWei";
Person.prototype.age=22;
Person.prototype.sayName=function(){
return this.name;
};
var person1 = new Person();
var person2 = new Person();
person1.name = "zhaochao";
alert(person1.name);//zhaochao
alert(person2.name);//xuZhiWei;
第一个显示的是zhaochao,第二个显示的是xuZhiWei,这说明给实例赋值,它会“屏蔽”原型的值,而不是修改原型的值,这说明了取值的查找顺序,先看看实例中有没有赋值,然后如果实例中没有找到再从原型中去找值。
使用原型函数等就可以共享了,但是对于引用类型的属性,它会存在一个问题,就是当一个引用类型的属性修改,所有的都会修改,比如数组,如下所示:
function Person(){}
Person.prototype.name="xuZhiWei";
Person.prototype.age=22;
Person.prototype.hobbies=["弹吉他","弹钢琴","看动漫"];
Person.prototype.sayName=function(){
return this.name;
};
var person1 = new Person();
var person2 = new Person();
person1.hobbies.push("看书");
alert(person1.hobbies);//弹钢琴,弹吉他,看动漫,看书;
alert(person2.hobbies);//弹钢琴,弹吉他,看动漫,看书;
发现这两个对象结果都返回了相同的值,这说明对于引用类型(类似还有日期,正则)每次赋值都是从原型上修改了结果。所以原型还是有一些局限。
联系构造函数,如果在构造函数中声明数组,自然不会出现这个问题。所以可以把这两个方法结合起来,如下所示:
function Person(name,age){
this.name = name;
this.age = age;
this.hobbies=["弹吉他","弹钢琴","看动漫"];
}
Person.prototype.sayName=function(){
return this.name;
};
var person1 = new Person();
var person2 = new Person();
person1.hobbies.push("看书");
alert(person1.hobbies);//弹钢琴,弹吉他,看动漫,看书;
alert(person2.hobbies);//弹钢琴,弹吉他,看动漫;
在这里可以看出,用构造函数创建引用每次实例化都会产生一个新数组,就不会产生之前的问题了,另外要提一点的是:prototype如果要用字面量定义,那么它的constructor就会指向Object而非构造函数了。所以最好不要用这个构造方法。
在用prototype实现原型链的继承的时候,可以这样做:
function Personchild(name){ this.name = name; } Personchild.prototype = new Person();这样Personchild的prototype对象也是指向Person的prototype的(Person的实例本来就指向Person.prototype),这样不仅能获得在构造函数里的属性,同时也能获得在Person的prototype的属性,这些都是Personchild的原型属性了。然后可以调用父类的属性方法了。如下所示:
function Person(name,age){
this.name = name;
this.age = age;
this.hobbies=["弹吉他","弹钢琴","看动漫"];
}
Person.prototype.sayName=function(){
return this.name;
};
function Personchild(name){
this.name = name;
}
Personchild.prototype = new Person();//这样Personchild的prototype就指向了Person的prototype.
var child = new Personchild("许志伟");
alert(child.hobbies);//弹钢琴,弹吉他,看动漫;
可以看到子类也成功调用了父类的属性。但是这又存在一个问题,如果先声明一个Person对象,然后修改hobbies的值,因为数组是引用类型,所以这个结果也会影响到子类,如下所示:
function Person(){
this.hobbies=["弹吉他","弹钢琴","看动漫"];
}
function Personchild(){}
Personchild.prototype = new Person();//这样Personchild的prototype就指向了Person的prototype.
var child1 = new Personchild();
child1.hobbies.push("看书");
var child2 = new Personchild();
alert(child2.hobbies);//弹钢琴,弹吉他,看动漫,看书;
因为每一个实例的指向的原型,都有继承来的hobbies属性,这个和构造函数的hobbies不一样,它是实例化后的新数组,但是在子类的prototype中,这样每个实例都共享了这个数组,当一个值改变,所有值都会发生改变。
为了解决这个问题,我们可以使用借用构造函数的技术,这个方法很简单,实例代码如下:
function Person(){
this.hobbies=["弹吉他","弹钢琴","看动漫"];
}
function Personchild(){
Person.call(this);//调用父类的构造方法;
}
var child1 = new Personchild();
child1.hobbies.push("看书");
alert(child1.hobbies);//弹钢琴,弹吉他,看动漫,看书;
var child2 = new Personchild();
alert(child2.hobbies);//弹钢琴,弹吉他,看动漫;
我们通过调用call()方法或者apply()方法,可以实现和Java的super()一样类似的效果,这样每个子类的实例都会有自己的hobbies副本。
如果要传递参数,可以在this后面加上参数即可,比如call(this,“zhaochao”)。
但是构造函数如果创建方法,仍然存在之前谈到的问题,所以一般还是要把借用构造函数和原型链结合在一起用,构成组合继承。
最后组合的代码如下:
function Person(name){
this.name = name;
this.hobbies=["弹吉他","弹钢琴","看动漫"];
}
Person.prototype.sayName = function () {
alert(this.name);
};
Person.prototype.sayHobbies = function () {
alert(this.hobbies);
};
function Personchild(name,age){
Person.call(this,name);//调用父类的构造方法;
this.age = age;
}
Personchild.prototype = new Person();
Personchild.prototype.sayAge = function () {
alert(this.age);
};
var instance1 = new Personchild("许志伟",22);
instance1.hobbies.push("看书");
instance1.sayName();//许志伟;
instance1.sayAge();//22;
instance1.sayHobbies();//"弹吉他","弹钢琴","看动漫","看书";
var instance2 = new Personchild("赵超",55);
instance2.sayName();//赵超
instance2.sayAge();//55
instance2.sayHobbies();//"弹吉他","弹钢琴","看动漫";
在这个例子中,构造函数里的属性是属于各个实例的,prototype原型里的方法是公用的,这样就结合了构造函数和原型链的优点。写的有点多,希望以后看到能瞬间秒懂
![难过](http://static.blog.csdn.net/xheditor/xheditor_emot/default/sad.gif)