JavaScript学习记录-原型

创建的每个函数都有一个prototype(原型)属性,这个属性是一个对象,它的用途是包含可以由特定类型的所有实例共享的属性和方法。逻辑上可以这么理解:prototype通过调用构造函数而创建的那个对象的原型对象。

使用原型的好处可以让所有对象实例共享它所包含的属性和方法。也就是说,不必在构造函数中定义对象信息,而是可以直接将这些信息添加到原型中。

一、原型的属性判断

1、比较构造函数和原型

(1)构造函数

function Demo(Name,Age){
    this.name=Name;     //实例属性
    this.age=Age;
    this.run=function(){    //实例方法
        return this.age+"岁的"+this.name+"在跑步";
    };
}
var demo1=new Demo1("bert",24);//实例化
alert(demo1.run());

构造函数逻辑图 

(2)原型

function Demo(){};//构造函数体内什么都没有,有了就变成上面的了
Demo.prototype.name="bert";//原型属性
Demo.prototype.age=24;
Demo.prototype.run=function(){  //原型方法
    return this.age+"岁的"+this.name+"在跑步";
};
var demo1=new Demo();
var demo2=new Demo();
alert(demo1.run());

 原型的逻辑关系图

 

 在原型模式声明中,多了两个属性,这两个属性都是创建对象时自动生成的。__proto__属性是实例指向原型对象的一个指针,它的作用就是指向构造函数的原型属性constructor。通过这两个属性,就可以访问到原型里的属性和方法了。

2、判断一个对象是否指向了该构造函数的原型对象,可以使用isPrototypeOf()方法来测试,

//接上例
alert(Demo.prototype.isPrototypeOf(demo1)); //返回结果true

3、原型模式的执行流程

(1)先查找构造函数实例里的属性或方法,如果有,立刻返回;

function Demo(){
    this.name="Tom";
};
Demo.prototype.name="bert";
Demo.prototype.age=24;
Demo.prototype.run=function(){
    return this.age+"岁的"+this.name+"在跑步";
};

var demo1=new Demo();
alert(demo1.name);    //返回结果Tom

(2)如果构造函数实例里没有,则去它的原型对象里找,如果有,就返回;

function Demo(){};
Demo.prototype.name="bert";
Demo.prototype.age=24;
Demo.prototype.run=function(){  
    return this.age+"岁的"+this.name+"在跑步";
};

var demo1=new Demo();
demo1.name="Tom";   //实例属性,并没有重写原型属性,此处添加和上一个代码中在{}中添加是一样的
alert(demo1.name);  //返回结果Tom,就近原则

var demo2=new Demo();
alert(demo2.name);    //返回结果bert
//实例属性不会共享,所以demo2访问不到实例属性,只能访问原型,原型是共享的

4、判断实例中是否存在指定属性

判断属性是在构造函数的实例里,还是在原型里,可以使用hasOwnProperty()函数来验证,

alert(box.hasOwnProperty('name'));	//若实例里有返回true,否则返回false

5、判断实例或原型中是否存在指定属性

in操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实例中还是原型中,

alert("name" in demo1);

也就是说,不管实例中或原型中的属性是否存在(即时为空),只要存在“name”就返回true,反之就是false。

6、判断只有原型中是否存在指定属性

function isProperty(object,property){
    return !object.hasOwnProperty(property)&&(property in object);
}

function Demo(){};
Demo.prototype.name="bert";
Demo.prototype.age=24;
Demo.prototype.run=function(){  
    return this.age+"岁的"+this.name+"在跑步";
};
var demo1=new Demo();
alert(isProperty(demo1,"name"));

若实例中存在name属性,或者原型中的name属性没有,都会返回False,这就可以判断只有原型中存在的情况。

二、原型的字面量

1、原型属性的不同创建

回顾原型的逻辑关系图,构造函数创建原型,

function Demo(){}

var demo=new Demo();
alert(demo.prototype);  //返回结果undefined,使用对象实例无法访问到prototype
alert(demo._proto_);    //undefined,使用对象实例访问prototype的指针
alert(Demo.prototype);  //[object Object],使用构造函数名(对象名)访问prototype

为了让属性和方法更好的体现封装的效果,并且减少不必要的输入,原型的创建可以使用字面量的方式,

function Demo(){}
Demo.prototype={   //使用字面量的方式创建原型对象,这里的{}就是对象是Object,new Object就相当于{}
    name:"bert",
    age:24,
    run:function(){
        return this.age+"岁的"+this.name+"在跑步";
    }
};
var demo=new Demo();
alert(demo.run());

2、不同创建的区别

使用构造函数创建原型对象和使用字面量创建对象在使用上基本相同,但还是有一些区别,字面量创建的方式使用constructor属性不会指向实例,而会指向Object,构造函数创建的方式则相反。

(1)构造函数创建原型

function Demo(){};
Demo.prototype.name="bert";
Demo.prototype.age=24;
Demo.prototype.run=function(){  
    return this.age+"岁的"+this.name+"在跑步";
};
var demo1=new Demo();
alert(demo1.constructor);   //返回结果function Demo(){}

(2)字面量方式创建原型对象

function Demo(){}
Demo.prototype={ 
    name:"bert",
    age:24,
    run:function(){
        return this.age+"岁的"+this.name+"在跑步";
    }
};
var demo2=new Demo();
alert(demo2.constructor);   //返回结果function Object() { [native code] }

那就好奇了字面量方式为什么constructor会指向Object

因为Box.prototype={};这种写法其实就是创建了一个新对象。而每创建一个函数,就会同时创建它prototype,这个对象也会自动获取constructor属性。所以,新对象的constructor重写了Box原来的constructor,因此会指向新对象,那个新对象没有指定构造函数,那么就默认为Object若想要字面量方式的constructor也指向实例对象,可以:

function Demo(){}
Demo.prototype={
    constructor:Demo,   //强制指向Demo
    name:"bert",
    age:24,
    run:function(){
        return this.age+"岁的"+this.name+"在跑步";
    }
};
var demo2=new Demo();
alert(demo2.constructor==Demo);

3、原型的声明

原型的声明是有先后顺序的,所以重写的原型会切断之前的原型。譬如,上例中,紧接着Demo.prototype={},重新再写一个Demo.prototype={},后写的里面若只重写一个属性,那之前的其他属性仍然不存在了,这里重写覆盖的是一个整体。

4、原型模式创建对象的缺点

(1)无法传参导致初始化值都一样

原型模式创建对象省略了构造函数传参初始化这一过程,那就导致了初始化的值都是一致的,实例化对象是返回的都是创建的对象里的属性值。但这原型最大的缺点也正是它最大的优点,这样也就可以共享。

function Demo(){};
Demo.prototype={
    constructor:Demo,
    name:"bert",
    age:24,
    family:["father","mother","sister"],
    run:function(){
        return "Family members are "+this.family+" and "+this.name;
    }
}
var demo1=new Demo();
alert(demo1.run());

(2)对于引用类型的共享问题

原型中所有属性是被很多实例共享的,共享对于函数非常合适,对于包含基本值的属性也还可以。但如果属性包含引用类型,就存在一定的问题,譬如上例,

var demo1=new Demo();
alert(demo1.family);    //返回初始时原型中属性
demo1.family.push("borther");   //数组最后添加一个元素
alert(demo1.family);    //返回结果father,mother,sister,borther

var demo2=new Demo();
alert(demo2.family);    //返回结果同样为father,mother,sister,borther

明明新建一个demo2想要引用的时候原型中初始的属性,然而现在共享了demo1添加后的引用类型的原型。

5、缺点的解决

(1)为了解决构造传参和共享问题,可以组合构造函数+原型模式

function Demo(Name,Age){    //不共享的使用构造函数
    this.name=Name;
    this.age=Age;
    this.family=["father","mother","sister"];
}
Demo.prototype={    //共享的使用原型模式
    constructor:Demo,
    run:function(){
        return "Family members are "+this.family+" and "+this.name;
    }
};
var demo1=new Demo("bert",24);
var demo2=new Demo("Tom",25);
alert(demo1.run());    //Family members are father,mother,sister and bert
alert(demo2.run());    //Family members are father,mother,sister and Tom

var demo3=new Demo();  
demo3.family.push("borther");  
alert(demo3.family);    //father,mother,sister,borther
var demo4=new Demo();
alert(demo4.family);    //father,mother,sister

(2)构造函数+原型部分的方法分两个模块感觉会怪异,最好就是把构造函数和原型封装到一起。为了解决这个问题,我们可以使用动态原型模式

先将原型封装到构造函数中,

Demo.prototype.run=function(){
        return "Family members are "+this.family+" and "+this.name;
    };

但将上述代码放在构造函数的{}中,每次实例化时就会原型初始化一次,这就很浪费了。原型的初始化只需要一次就够了,没必要每次构造函数实例化时候就初始化,所以需要一个判断语句,

function Demo(Name,Age){
    this.name=Name;
    this.age=Age;
    this.family=["father","mother","sister"];

    if(typeof this.run!="function"){
        Demo.prototype.run=function(){
            return "Family members are "+this.family+" and "+this.name;
        };
    }
}

猜你喜欢

转载自blog.csdn.net/bertZuo/article/details/81873737