js的几种模式和继承

470 阅读6分钟

单例模式

对象中的属性名和属性值来描述这个对象的特征的,每一个对象名都是一个命名空间,以命名空间进行分组,减少全局变量泛滥的代码管理机制;

  • 单例模式的作用:
    • 给属性名或者方法加一个前置标志(命名空间),这样相同名字的两个方法就不会冲突了
    • 实现模块化开发,相同的功能组件可以很快进行迁移使用;方便扩展维护和团队协作开发
    • 虽然将零散的描述汇总在一起,通过属性名和属性值可以详细地描述一个对象的特征了,但是还存在不足:处于手工作模式,生产效率慢,不能实现批量生产;

工厂模式

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()中相同的代码之后还有以下几点不同:
    1. 没有显示式创建对象;
    2. 直接将属性和方法赋给了this对象;
    3. 没有return语句。
  • 此外,函数名Person的第一个字母大写了。按照惯例,构造函数始终都应该以一个大写字母开头,非构造函数以小写字母开头。
    创建Person()的实例,必须要使用new操作符,以这种方式调用构造函数会经历有以下步骤:
    1. 创建一个新对象;
    2. 将构造函数的作用域赋给新对象(这时,this就指向这个新对象);
    3. 执行构造函数中的代码(为这个新对象添加属性);
    4. 返回新对象。
  • 在前面的栗子中,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