JS创建对象的几种方法:工厂模式,构造函数模式,原型模式,混合模式,动态原型模式
1 在工厂模式中,在构造函数内部创建一个新对象,最后返回这个对象。当实例化时,我们不需要用new关键字,就像调用方法一样就可以实例化。
工厂模式的缺点是容易和普通函数混淆,只能通过命名来确认它是一个 构造函数。不推荐使用这种模式。
//factory pattern
function createPerson(name, age, job){
var o = {};
o.name = name;
o.age = age;
o.job = job;
o.friends = ["Mike", "Sun"];
o.sayName = function(){
alert("factory pattern: " + this.name);
}
return o;
}
var Abby = createPerson("Abby", "22", "Softwarre Engineer");
Abby.sayName();
2 构造函数模式,用new关键字来实例化对象。与工厂方式相比,使用构造函数方式创建对象,无需在函数内部重新创建对象,而使用this指代,并且函数无需明确return。不推荐使用这种模式。
构造函数的缺点是不断的拷贝,每new一次就造出一个副本,每个方法都要在每个实例上重新创建一遍,显然这样是不行的,我们想要的是一种有些方法共享所有,有此方法私有,于是Eric发明了原型链。
//constructor pattern
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert("constructor pattern: " + this.name);
}
}
var Abby = new Person("Abby", "22", "Software Engineer");
Abby.sayName();
3 原型模式,这里就要说到prototype。我们创建的每个函数都有有一个 prototype(原型)属性,它也是一个对象,它的用途是包含有特定类型的所有实例的属性和方法。不推荐使用这种模式。
下面例子,我们把所有方法一个个添加到prototype上,但由于prototype上方法属于一种共享,这些方法有些别人用的到,有些别人根本用不到,有些别人想用的没有的方法还要再次往prototype上添加。这样就不好了,所以最常用的模式其实是混合型的。
//prototype pattern
function Abby(){}
Abby.prototype.name = "Abby";
Abby.prototype.age = "22";
Abby.prototype.sayName = function(){
alert("prototype pattern: " + this.name);
}
var person1 = new Abby();
person1.sayName();
4 构造函数模式和原型模式的混合类型。将所有属性不是方法的属性定义在函数中(构造函数方式),将所有属性值为方法的属性利用prototype在函数之外定义(原型方式)。 推荐使用这种方式创建对象。
//hybrid constructor & prototype pattern
function Student(name, sno){
this.name = name;
this.sno = sno;
this.sayName = function(){
alert("hybrid constructor & prototype pattern: " + this.name);
}
}
Student.prototype = {
constructor : Student,
teacher : ["mike", "abby"],
sayTeacher : function(){
alert("hybrid constructor & prototype pattern(teacher): " + this.teacher);
}
}
var zhangsan = new Student("zhangsan", "22");
var lisi = new Student("lisi", "23");
zhangsan.sayName();
lisi.sayName();
zhangsan.sayTeacher();
lisi.sayTeacher();
5 动态原型方式
动态原型方式可以理解为混合模式的一个特例。该模式中,属性为方法 的属性直接在函数中进行了定义,但是因为if从句从而保证创建该对象的实例时,属性的方法不会被重复创建。推荐使用这种模式。
//dynamic prototype pattern
function Person(){
this.name = "Mike";
this.age = 22;
}
if (typeof Person._lev == "undefined"){
Person.prototype.lev = function(){
return this.name;
}
Person._lev = true;
}
var x = new Person();
alert(x.lev());
6. 寄生构造函数模式
//parasitic constructor pattern (hybird factory)
function Person1(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 mike = new Person1("Mike", 22, "Software Engineer");
mike.sayName();
7 稳妥构造函数模式,这种模式不用this,不用new,目的是安全,这是一种方法,不是主流
//durable constructor pattern
function Person2(name, age, job){
var o = new Object;
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
alert(name);
}
return o;
}
var mike = Person2("Mike", 22, "Software Engineer");
mike.sayName();
JS中的继承主要依靠原型链。
每个构造函数都拥有一个原型对象,原型对象都包含一个指向构造函数的指针(constructor),实例都包含一个指向原型对象的内部指针(_proto_)。
如果对原型进行多次赋值,那么后面的赋值会覆盖前面的,也就是通过原型链只能继承离实例化最近的一个 原型对象。
原型链继承的本质就是一个单链表的深度搜索。例如,原型对象(Son.prototype)等于另一个原型(Person)的实例(person1),那么此时的原型对象(Son.prototype)将包含一个指向另一个原型(Person.prototype)的指针,相应的,另有一个原型(Person.prototype)中也包含着一个指向另一个构造函数(Person())的指针。
再如,另一个原型(Person.prototype)又是另一个类型(Person)的实例(person1),那么上述关系依旧成立,如此层层递进,就构成了实例与原型的链条。这就是所谓的原型链。
所有引用类型默认继承了Object类型,所有函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向Object.prototype.这也正是自定义类型能通过使用toString()等默认方法的原因。
在通过原型链实现继承时,不能使用对象字面量创建原型对象,这样会重写原型链。
call函数的用法(可用于实现继承)
call([thisObj[,arg1[, arg2[, [,.argN]]]]]),调用一个对象的一个方法,以另一个对象替换当前对象。
call方法可以用来代替另一个对象调用一个方法。call方法可将一个函数的对象上下文从初始的上下文改变为由htisObj指定的新对象。如果没有提供thisObj参数,那么Global对象被用作thisObj。
function Animal(name){
this.name = name;
this.showName = function(){
alert(this.name);
}
}
function Cat(name){
Animal.call(this, name);
}
var cat = new Cat("Black Cat");
cat.showName();
Animal.call(this) 的意思就是使用 Animal对象代替this对象,那么 Cat中不就有Animal的所有属性和方法了吗,Cat对象就能够直接调用Animal的方法以及属性了.
同样,如果使用多个call就可以实现多重继承。
new操作符创建实例的过程:创建一个新对象->将构造函数的作用域赋给新对象(因此this就指向了这个新对象)->执行构造函数的代码(为这个新对象添加属性)->返回新对象