单例模式
对象中的属性名和属性值来描述这个对象的特征的,每一个对象名都是一个命名空间,以命名空间进行分组,减少全局变量泛滥的代码管理机制;
- 单例模式的作用:
- 给属性名或者方法加一个前置标志(命名空间),这样相同名字的两个方法就不会冲突了
- 实现模块化开发,相同的功能组件可以很快进行迁移使用;方便扩展维护和团队协作开发
- 虽然将零散的描述汇总在一起,通过属性名和属性值可以详细地描述一个对象的特征了,但是还存在不足:处于手工作模式,生产效率慢,不能实现批量生产;
工厂模式
function createPerson(name,age,job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = {
alert(this.name);
}
return o;
}
var person1 = createPerson("r",18,"www");
var person2 = createPerson("m",18,"qqq");
- 函数createPerson()能够根据接受的参数构建一个包含所有必要信息的person对象,可以无数次的调用这个函数,它都会返回一个包含3个属性和1个方法的对象。工厂模式虽然解决了创建多个类似对象的问题,但却没有解决识别对象的问题。
构造函数模式
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.setName = function(){
console.log(this.name);
}
}
var person1 = new Person("r",18,"www");
var person2 = new Person("m",18,"qqq");
- 这个栗子中,Person()函数取代了createPerson()函数,我们注意到,Person()中的代码除了与createPerson()中相同的代码之后还有以下几点不同:
- 没有显示式创建对象;
- 直接将属性和方法赋给了this对象;
- 没有return语句。
- 此外,函数名Person的第一个字母大写了。按照惯例,构造函数始终都应该以一个大写字母开头,非构造函数以小写字母开头。
创建Person()的实例,必须要使用new操作符,以这种方式调用构造函数会经历有以下步骤:- 创建一个新对象;
- 将构造函数的作用域赋给新对象(这时,this就指向这个新对象);
- 执行构造函数中的代码(为这个新对象添加属性);
- 返回新对象。
- 在前面的栗子中,person1和person2分别保存着Person的不同实例,但这2个对象都有一个名叫 constructor属性,该属性执行Person。
contstructor
- 每创建一个函数,就会同时创建它的prototype对象,这个对象也会自动获得contstructor属性,这个属性默认指向该函数。
console.log(person1.contstructor == Person);//true
console.log(person2.contstructor == Person);//true
- 构造函数也是函数,不存在定义构造函数的特殊语法,任何函数,只要使用new操作符来调用,那么它就是构造函数;构造函数与其他函数不一样的区别就算,调用的方式不同罢了。
- 构造函数的问题:每个方法都要在每个实例上重新创建一遍,在前面的栗子中,person1和person2都有一个名为setName()方法,但两个方法不是同一个Function的实例,也就是函数同名但是引用不是同一个,因为EMCAScript中的函数是对象,因此每定义一个函数,也就是实例化了一个对象。
person1.setName === person2.setName //false
原型链模式
- 每个函数都会有一个prototype属性,这个属性就像是一个指针,指向一个对象,指向一个特定类型的所有实例共享的属性和方法。
function Persin(){}
Person.prototype.name = "rm";
Person.protptype.age = 18;
Person.prototype.job = "lll";
Person.prototype.setName = function(){
console.log(this.name);
}
var person1 = new Person();
person1.setName();//rm
var person2 = new Person();
person2.setName();//rm;
console.log(person1.setName === person2.setName); //true
- 将setName()方法和所有属性直接添加到了Person的prototype属性中,所有的方法和属性就是所有实例共享的。也就是说,person1和person2访问的都是同一组属性和同一个setName()函数。
- 图为Person的构造函数和Person.prototype创建的代码的实例的形象图
关于constructor的注意点
function Person(){} Person.prototype = { name:"eee", age:22 } var first = new Person(); console.log(first instanceof Object);//true console.log(first instanceof Person);//true console.log(first.constructor === Person);//false console.log(first.constructor === Object);//true- 上面的栗子就是因为将Person函数的prototype对象重写了,虽然用instanceof操作符测试Object和Person都返回true,但是constructor属性则等于Object而不再是Person了。如果constructor值很重要的时候,我们需要手动给他设回之前的值。
function Person(){} Person.prototype = { coustanceof:Person,//☜☜☜看这里 name:"eee", age:22 } var first = new Person(); console.log(first instanceof Object);//true console.log(first instanceof Person);//true console.log(first.constructor === Person);//true console.log(first.constructor === Object);//false
如何在原型对象上创建方法
- 所有原生引用类型(Object、Array、String等)都在其构造函数的原型上定义了方法,并且我们也可以定义新方法
alert(typeof Array.prototype.sort);//"function"
alert(typeof String.prototype.substring);//"function"
在原型上定义新的方法
String.prototype.strwith = function(test){
return this.indexOf(test) == 0
}
var msg = "Hello";
console.log(msg.strwith("Hello"));//true
- strwith()方法是定义在String.prototype的,那么当前环境中所有的字符串都可以调用这个方法。因为这种定义的方法可能会和原生方法产生冲突,因此不建议直接在对象的原型上添加方法或修改方法。
原型对象针对引用类型存在的问题
- 原型中的所有属性都是被很多实例共享的,这种共享对于函数非常合适,对于基本类型也还可以,但是对于一些引用类型就不那么友好了。例如
function Person(){}
Person.prototype = {
constructor: Person,
name:"ll"
ary:["aaa","bbb"],//看这里
sayName:function(){
console.log(this.name)
}
}
var person1 = new Person();
var person2 = new Person();
person1.ary.push("ccc");
console.log(person1.ary);//["aaa", "bbb", "ccc"]
console.log(person2.ary);//["aaa", "bbb", "ccc"]
console.log(person1.ary === person2.ary);//true
- 在上面的Person.prototype对象中,有一个名叫ary的字符串数组,还有2个Person的实例,然后我们修改了person1.ary引用的数组,向数组添加了一个字符串,但是由于ary数组是存在于Person.prototype上而不是person1中,所以刚刚修改ary的结果也会反映到person2中。
使用构造函数和原型模式
- 构造函数模式用于定义实例属性,原型模式用于定义方法和共享属性
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.ary = ["aa","bb"];
}
Person.prototype = {
constructor: Person,
sayName:function(){
console.log(this.name);
}
}
var person1 = new Person("May",22,"hello");
var person2 = new Person("April",22,"welecom");
person1.ary.push("cc");
console.log(person1.ary);//["aa", "bb", "cc"]
console.log(person2.ary);//["aa", "bb"]
console.log(person1.ary === person2.ary);//false
继承
prototype模式
将继承者的prototype对象,指向一个继承父类的实例上,那么所有继承者的实例都能继承父类
function SuperType (){
this.prototype = true;
}
SuperType.prototype.getSuperValue = function(){
return this.prototype;
}
//继承 SubPerType
function SubType (){
this.Subprototype = false;
}
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function(){
return this.Subprototype;
}
var instace = new SubType();//这时 instace.construcror 指向的是 SuperType
instace.getSuperValue();//true
定义了SuperType和SubType。SubType通过了将SuperType的一个实例,给了SubType.prototype方法继承的,实现的本质是重写原型对象,也就是说存在于SuperType实例的方法和属性,也同时存在SubType.prototype中了。在确认继承关系之后,我们又给SubType.prototype添加了一个方法。当我们去new一个SubType的实例时,该实例同时拥有SubType和SuperType的属性和方法了。如果所示
图中没有提供SubType默认的原型,而是重写之后的新原型
构造函数模式
使用call或者apply方法,将父对象的构造函数绑定在子对象上
//动物对象的构造函数
function Animal(){
this.species = "动物";
}
//猫对象的构造函数
function Car (name,color){
this.name = name;
this.color = color;
}
如何让猫继承动物呢?
function Cat(name,color){
Animal.apply(this,arguments);//"<------重点是这行"
this.name = name;
this.color = color;
}
var cat1 = new Cat("maomao","白色");
cat1.species;//"动物"
直接继承prototype
function Animal(){}
Animal.prototype.species = "动物";
将Cat的prototype对象,指向Animal的prototype对象
function Cat(name,color){
this.name = name;
this.color = color;
}
Cat.prototpye = Animal.prototype;
Cat.prototype.constructor = Cat;//<---会改变Animal.prototype对象的constructor
var cat1 = new Cat("大毛","黑色");
cat.species //"动物"
Animal.prototype.constructor //cat