js面向对象
方法一:工厂模式
所谓工厂模式,简单点儿说就是按照工厂生产的过程创建对象,原材料->加工->返还产品
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("Nicole",12,"software engnieer");
var person2 = createPerson("Join",15,"writer");
优点很明显,解决了创建多个具有相似属性和方法的对象的问题,但是却没有解决对象识别的问题
比方说:
alert(person1 instance of Object);//true
alert(person1 instance of createPerson);//false
方法二:构造函数模式
function Person(name,age,job)
{
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
}
}
var person1 = new Person("Nicole",12,"software engnieer");
var person2 = new Person("Join",15,"writer");
从上面可以看出,构造函数模式,不在函数内部创建Object对象,而是用this代替,最后创建实例时使用new
其中:
var person1 = new Person("Nicole",12,"software engnieer");
经历了以下几个过程:
1.var o = new Object();
2.o = this(Person);
3.执行构造函数中的代码;
4.返回新对象
优点:1.使用new创建对象,更符合面向对象的创建习惯;
2.红宝书上说创建构造函数意味着将它的实例标识为一种特定的类型,我的理解是,就是创建了一种类型,你可以用它去创建实例。
alert(person1 instanceof Object);//true
alert(person1 instanceof Person);//true
为什么可以这样呢,因为你用Person实例化的对象,它都保存了一个constructor属性,指向Person。
alert(person1.constructor == Person);//true
alert(person2.constructor == Person);//true
缺点:首先,我们看一段代码:
this.sayName = function(){}
this.sayName = new Function("");
这两段代码在逻辑上是等价的,也就是说在ECMAscript中,函数是对象,每定义一个函数,就会实例化一个对象,因此,不同实例的不同函数是不相等的,用构造函数模式创建的对象,虽然看起来是大家“共用”了一个功能函数,但其实它们都是不同的对象,这就很没有必要。
方法三:原型模式
试想一下,能不能把sayName拿出来放在一个公共区域里,这样举例子可能有些不合适,因为并不是说你用sayName的时候我就不可以用,我们想要的是不管谁去取,多少对象同时使用都可以,但是sayName只有一个。这样的想法就诞生原型模式,这个公共区域就是prototype。
如图所示,无论什么时候,只要创建一个新函数,就会根据一组特定的规则为该函数。
创建一个prototype属性,该属性指向函数的原型对象。而原型对象会获取一个constructor属性,该属性指向对应的构造函数。而对象的实例中则含有一个[[prototype]]的属性,它指向原型对象。这个属性对脚本是完全不可见的,但是可以通过isPrototypeOf()方法来确定对象之间是否存在这种关系,如果说[[prototype]]指向调用isPrototype()方法的对象(obj.prototype),这个方法就返回true:
alert(Person.prototype.isPrototypeOf(person1));//true
ECMAscript 5中新增加一个方法Object.getPrototypeOf(),返回[[Prototype]]的值:
alert(Object.getPrototypeOf(person1) == Person.prototype);//true
alert(Object.getPrototypeOf(person1).name);//Nicole
原型创建代码:
function Person(){
}
Person.prototype.name = "Nicole";
Person.prototype.age = 12;
Person.prototype.job = "software engnieer";
Person.prototype.sayName = function(){
alert(this.name);
}
var person1 = new Person();
person1.sayName();//Nicole
var person2 = new Person();
person2.sayName();//Nicole
alert(person1.sayName == person2.sayName);//true
更简单的原型语法:
前面例子每添加一个属性和方法就要敲一遍Person.prototype,很麻烦,可以使用字面量重写整个原型对象:
function Person(){
}
Person.prototype = {
name:"Nicole",
age:12,
job:"software engnieer",
sayName:function(){
alert(this.name);
}
}
在这个例子中,我们将Person.prototype设置等于一个以字面量形式创建的新对象,最终结果相同,但是要注意的是,constructor不再指向Person,指向Object。
alert(Person.prototype.constructor == Person);//false
因此需要人工的配置constructor的指向:
Person.prototype = {
constructor:Person,
name:"Nicole",
age:12,
job:"software engnieer",
sayName:function(){
alert(this.name);
}
}
这样写依然有一个问题,就是重写prototype切断了,现有原型和重写前已经存在的实例的联系,它们引用的依然是最初的原型:
function Person(){
}
var person1 = new Person();
Person.prototype = function(){
constructor:Person,
name:"Nicole"
};
alert(person1.name);//error
原型对象的缺点:
共享是把双刃剑,它就带来了便利,也带来了麻烦,比方说:
function Person(){
};
Person.prototype = {
constructor:Person,
name:"Nicole",
age:12,
job:"software engnieer",
friends:['hah','hhe'],
sayName:function(){
alert(this.name);
}
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push('black');
alert(person2.friends);//'hah','hhe','black'
修改一个实例的原型方法或者属性,所有相关实例都会相应的变化。
方法四:组合使用构造函数模式和原型模式
为了解决原型模式的缺点,我们可以使用构造函数定义实例属性,用原型模式定义方法和共享的属性。
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.friends=["hello","world"];
}
Person.prototype = {
constructor:Person;
sayName:function(){
alert(this.name);
}
}
var person1 = new Person("Nicole",12,"software engnieer");
var person2 = new Person("blue",14,"writer");
person1.friends.push("!");
alert(person1.friends);//"hello","world","!"
alert(person2.friends);//"hello","world"