创建模拟“类”对象:
Javascript没有”类“的概念。但可以通过构造函数和对象结合的形式来模拟出一个“类”的形式出来。并通过这样的形式,做到“类”的私有封装和公有属性和方法,甚至可以做到继承的目的。
工厂模式:
function createPerson(name,age,job){
var obj = new Object();
obj.name = name;
obj.age = age;
obj.job = job;
o.sayName = function(){
alert(this.name);
};
return obj;
}
var person1 = createPerson(Joson,22,Teacher)
var person2 = createPerson(Matt,24,Doctor)
实际上,工厂模式之所以叫做工厂模式,就是将一个 obj的对象放进一个具体函数里面去加工,这里的例子里 function createPerson函数就相当于一个工厂。然后对工厂里面属性和方法进行赋值和定义,在工厂加工的最后,一定要返回这个对obj象。当在外面引用这个函数并实例化时,这时候,这个obj对象的工厂就起到了一个“类”的作用。
构造函数模式:
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
};
}
var person1 = New Person(Joson,22,Teacher)
var person2 = New Person(Matt,24,Doctor)
构造函数模式在工厂模式基础上做出的改动在于:
1.没有显式创建obj对象
2.直接将属性和方法赋给this (这里的this指向后面New 出来的Person对象)
3.没有return语句
注意,在构造函数模式中,构造函数的函数名首字母一定要大写!
构造函数只是将工厂模式在函数内部显式创建obj对象改为了在外部 New的地方隐式创建对象。并且将this指针指向这个隐式创建的对象中来。
因此,这里New语句干的事有:
1.在Person构造函数内部创建一个对象。
2.将this指针指向这个创建的对象,也就是改变作用域。
3.在构造函数中隐式return这个对象。
4.调用这个构造函数,并实例化。
所以,如果创建一个函数,当仅仅只用声明函数的方式去调用它,那么它就是一个普通的函数(其中的this就指向了window),当用New的方式去调用一个函数,它就成了一个构造函数。
构造函数模式的问题在于,构造函数内部的属性和方法都是私有的,外部无法访问到内私有的属性和方法,并且不同实例化后的对象的相同方法,也都是不同的。例如,上面的person1 和 person2,如果我们alert(person1.sayName === person2.sayName);它将返回false。因为person1和person2实例化访问的都是独立且不公有的属性和方法。
原型模式:
在创建每个函数之初,js会自动有一个prototype(原型)属性,这个属性是一个指针,指向该函数的一个后花园对象。而这个所谓的后花园的对象就可以封装一些特定类型的属性和方法,这些属性和方法很棒在于他们都是共享共有的属性和方法。
function Person(){
};
Person.prototype.name = Jason; Person.prototype.age = 22
Person.prototype.job = Teacher; Person.prototype.sayName = function(){
alert(this.name);
}
var person1 = New Person()person1.sayName();
var person2 = New Person()person2.sayName();
alert(person1,sayName = person2.sayName)
此时Person函数为一个空函数,由于后面又用了New语句将Person函数变成了对象。所有的属性和方法定义在该Person对象的原型(prototype)上。而原型具有共有共享实例属性和方法的特性,所以最后的alert最后会返回一个true。
这里也可以用alert(Person.prototype.isPrototypeOf(person1))来检测该实例是否指向Person的原型。
在原型模式中:
每个实例里有一个[[prototype]]指针,他始终指向构造函数的原型。代码在读取某个对象的某个属性时,优先检索实例内部的属性,若实例内部没有,再去通过[[prototype]]指针,去检索原型里有没有这个属性和方法。
每个原型中都有一个constructor属性,它也是一个指针,指向该构造函数内部。所以Person.prototype.constructor === Person。原型模式也正是因为有constructor这个属性才达到了共享属性和方法的效果。
注意一点,虽然可以通过对象的实例访问保存在原型中的属性和方法,但却不能通过对象实例重写修改原型中的属性和方法。如果我们在外部实例写一个
person1.name = "Fred"
最后alert(person1.name)返回Fred,这个Fred只是屏蔽了原型中的Jason,但并没有修改原型中的Jason,也就是说alert(person2.name)时,还是会返回原型中的Jason。用delete方法可以去掉这种实例上的屏蔽:
delete person1.name;
alert(person1.name)返回Jason
使用hasOwnProperty()方法可以检查一个属性是否来自于实例,如果返回true,则来自于实例,如果返回false,则来自于原型。
alert(person1.hasOwnProperty("name"); 返回false则表示这个name属性来自于原型。
person1.name = "Fred";
alert(person1.hasOwnProperty("name"); 返回true则表示这个name属性来自于实例。
混合式构造函数模式和原型模式:
原型模式的优点也是它的缺点,只有共有属性方法,却没有私有属性方法。所有实例都共享一个原型。这是不符合大众需求的。在原型模式中,我们定义的构造函数为一个空函数。所以,我们只要把自己想要私有的属性和方法放进构造函数里面就可以实现属性和方法的私有化,在外部无法访问。把想要共有的属性和方法放进原型中,即可。
极简主义模式:
上面的混合了构造函数和原型模式写法的代码量还是太多太繁杂了,为此,以后市面上最常用的一种写法为此:
var Dog= {
//此处放共有属性和方法
sound:"汪汪汪"
Dog.jump = function(){ dosomething };
creatFn: function(){
//函数内部放私有方法和属性
var dog= {};
dog.name = "狗狗";
dog.makeSound = function(){ alert(Dog.sound); };
dog.changeSound = function(param){Dog.sound = param};
return dog;
}
};
var dog1 = Dog.creatFn();
dog1.makeSound(); // 汪汪汪
var dog2 = Dog.createFn();
dog2.changeSound("呜呜呜");
dog1.makeSound();//“呜呜呜”
此模式先创建一个Dog对象,在对面内部放置公有的属性和方法,通过对象字面量的形式创建一个函数,在函数内部再创建一个空的dog对象,在函数内部就可以放置私有的属性和方法了。最后再在函数内部返回这个dog对象就完成了一个模拟“类”极简写法的实现。其实就是在工厂模式外面再包一层对象。
继承:
原型链继承:
先简单回顾一下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针constructor,而每个实例都包含一个指向原型对象的内部指针[[prototype]]。那么如果我们将一个函数的原型对象指向另一个构造函数的实例,会怎样呢?显然,此时就形成了一个原型链,原型链上则实现了继承的特性。
function SuperType(){
this.SuperItem = true;
} //创建一个父函数
SuperType.prototype.getSuperValue = function(){
return this.SuperItem;
} //写入一个父函数的原型公有方法
function SubType(){
this.SubItem = false;
} //创建一个子函数
SubType.prototype = new SuperType();//将父构造函数指向子函数的原型,实现了原型链的链接,使子构造函数继承了父构造函数。SubType.prototype.getSubValue = function(){ return this.SubItem;} //写入一个子函数的原型公有方法
SubType.prototype.getSuperVallue = function(){ return false;} //(红色一段)在SubType的原型中重写改写父类中的方法var instance = new SubType();
alert(instance.getSuperValue) //返回false 因为被红色的一行代码改写了
最后的实例化是实例化的子构造函数SubType,最后却可以去调用父构造函数的getSuperValue的方法,则SubType已经继承了SuperType。
注意写原型链继承的顺序必须是先创建父函数,写父函数原型,再创建子函数,再做原型链的链接,再才去写子函数的原型,或者像红色一行代码一样去通过子函数的原型是重写改写父函数或父函数原型的方法。并且,红色那一行代码改写的父函数原型的方法只是屏蔽掉父函数原型的方法,并非真正改写了SuperType.prototype里的方法。
原型链继承的缺点还是因为原型的关系,实例出来的instance1和instance2会共享原型链中的数据,很多时候我们不需要共享时,则这种方法就不可用了,并且也无法传参。
别忘了原型:
记住,所有的函数(js内置函数也好,自定义函数也会)的默认原型都是Object的实例。也就是说Object.prototype里面写了很多js内置的属性和方法,比如toString(),valueof()等。
借用构造函数(类式继承):
借用构造函数就很好地解决了原型链继承的问题,通过借用构造函数模式继承,实例出来的instance1和instance2的数据不会共享,是相互独立的。借用构造函数是通过apply()或call()来进行继承,由于这两个方法第二个参数可以进行传参,所以,子函数可以向父函数进行传参,也解决了原型链继承做不到的问题。
function SuperType(name){
this.name = name;
this.colors = ["red","blue","green"];
}
function SubType(){
SuperType.call(this,"Jason");//此处实现SubType对SuperType的继承,也就是将SuperType的作用域指向了this的作用域(此处this是SubType的作用域),Jaon为向父类传递的一个参数
this.age = 22; //为了避免SyperType被重写改写,此处在子类型中最好再额外加一个子类型自己的属性。
}
var instance1 = new SubType(); //实例化子构造函数
instance1.colors.push("blac");
alert(instance1.colors); //"red,blue,green,blac"
alert(instance1.name); //"Jason"
var instance2 = new SubType();
alert(instance1.colors); //"red,blue,green"
通过借用构造函数的形式,实例化的instance1和instance2各自的函数类的属性都是相互独立不共享的。并且可以通过call方法的加入第二个参数从子类向父类传参。但要想做到既要数据共享又要数据独立,则也可以进行混合式继承。
混合式继承:
混合式继承则是将私有的属性和方法封装在函数体里面,把共享属性和方法放在函数的原型里面即可。并通过SubType.prototype = new SuperType()的方法对共享属性和方法做继承链接,通过SuperType.call(this)的方法对私有属性和方法做继承链接。
function Parent(age){
this.name = ['mike','jack','smith'];
this.age = age;
}
Parent.prototype.run = function () {
return this.name + ' are both' + this.age;
};
function Child(age){
Parent.call(this,age);//借用构造函数继承
}
Child.prototype = new Parent();//原型链继承
var test = new Child(21);
alert(test.run());//返回mike,jack,smith are both21
混合式继承也不是没有缺点,他的缺点在于做了两次继承,就调用了两次父函数,这样做并不是最理想的方法。
原型式继承:
和上面讲的原形链继承只有一字之差,但是不是同一个内容。我们所说的原型式继承,就是我们上节课所讲的,通过JS内置的Object.create()方式来创建新的类。
var box = {
name : 'Jaon',
arr : ['brother','sister','baba']
};
var b1 = Object.create(box,{name:{value:"Greg"}});//第一个参数为要继承的对象名,接受第二个参数可以去改变原对象里面的某个属性值
alert(b1.name);//"Greg"
b1.name = 'mike'; //会覆盖对象里的属性
alert(b1.name);//mike
alert(b1.arr);//brother,sister,baba
b1.arr.push('parents');
alert(b1.arr);//brother,sister,baba,parents
var b2 = Object.create(box);
alert(b2.name);//Jason
alert(b2.arr);//brother,sister,baba,parents
如果低等级浏览器没有这个Object.create()方法,那么可以自定义这个方法:
function Create(o){
function F(){}
F.prototype = o;
return new F();
}//事实上,调用这个函数也是在做构造函数的处理
在没有必要兴师动众地创建构造函数,而仅仅只想让一个对象做“类”一样的修改,则完全可以用原型式继承,不过别忘了,原型式继承也是为了共享它的属性和方法。
寄生式继承:
function creatAnother(original){
var clone=new Object(original);
clone.sayHi=function(){
alert("hi")
};
return clone;
}
var person={
name:"haorooms",
friends:["hao123","zhansan","lisi"]
}
var antherPerson=creatAnother(person);
antherPerson.sayHi();//hi
寄生式继承和原型式继承是一样的思路。这里createAnother函数就是一个用语封装继承过程的函数(此例子就是将一个sayHi的方法继承到person对象里去)。将person对象放到createAnother函数里去加工一遍,就可以将sayHi方法继承到person对象里面去了。
寄生混合式继承:
由于混合式继承会有调用两次父函数的缺点,则寄生混合式继承则可以解决这个问题。
function inheritPrototype (subType,superType) {
var prototype = Object.create(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
};
function SuperType (name) { //父类
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
SuperType.prototype.sayName = function () { //写入一个父类原型中的方法
alert(this.name);
}
function SubType(name, age) { //子类
//继承属性
SuperType.call(this,name);
this.age = age;
}
//继承方法
inheritPrototype(SubType,SuperType);
SubType.prototype.sayAge = function () { //写入一个子类原型中的方法
alert(this.age);
}
var instance = new SubType("Jason",22);
alert(instance.sayName()); //返回Jason
alert(instance,sayAge()); //返回22
如此,就完美做到了既只调用一次父函数,又可以通过借用构造函数继承私有属性值和子类对父类进行传参,又可以通过寄生式继承继承共享的方法(私有化是为了在实例化两个构造函数时,实例1和实例2相互之间是独立的属性,共享化则反之,此处不做多余的具体体现)。