JS中的原型

我们创建的每个函数都有一个 prototype(原型)属性,(注意是函数才有原型)这个属性是一个对象,它的用途是包含可以由特定类型的所有实例共享的属性和方法。逻辑上可以这么理解:prototype 通过调用构造函数而创建的那个对象的原型对象。 使用原型的好处可以让所有对象实例共享它所包含的属性和方法。 也就是说, 不必在构造函数中定义对象信息, 而是可以直接将这些信息添加到原型中。

function Box() {} //声明一个构造函数

Box.prototype.name = 'Lee'; //在原型里添加属性

Box.prototype.age = 100;

Box.prototype.run = function () { //在原型里添加方法
       return this.name + this.age + '运行中...';
};

比较一下原型内的方法地址是否一致:
var box1 = new Box();
var box2 = new Box();
alert(box1.run == box2.run); //true,方法的引用地址保持一致

在原型模式声明中, 多了两个属性, 这两个属性都是创建对象时自动生成的。 __proto__属性是实例指向原型对象的一个指针,它的作用就是指向构造函数的原型属性 constructor。通过这两个属性,就可以访问到原型里的属性和方法了。
PS:IE 浏览器在脚本访问__proto__会不能识别,火狐和谷歌浏览器及其他某些浏览器均能识别。虽然可以输出,但无法获取内部信息。
alert(box1.__proto__); //[object Object]
判断一个对象是否指向了该构造函数的原型对象, 可以使用 isPrototypeOf()方法来测试。
alert(Box.prototype.isPrototypeOf(box)); //只要实例化对象,即都会指向

原型模式的执行流程:
1.先查找构造函数实例里的属性或方法,如果有,立刻返回;
2.如果构造函数实例里没有,则去它的原型对象里找,如果有,就返回;
虽然我们可以通过对象实例访问保存在原型中的值, 但却不能访问通过对象实例重写原型中的值。
var box1 = new Box();
alert(box1.name); //Lee,原型里的值
box1.name = 'Jack';
alert(box.1name); //Jack,就近原则,
var box2 = new Box();
alert(box2.name); //Lee,原型里的值,没有被 box1 修改
如果想要 box1 也能在后面继续访问到原型里的值, 可以把构造函数里的属性删除即可,
具体如下:
delete box1.name; //删除属性
alert(box1.name);
如何判断属性是在构造函数的实例里,还是在原型里?可以使用 hasOwnProperty()函数
来验证:
alert(box.hasOwnProperty('name')); //实例里有返回 true,否则返回 false


原型模式创建对象也有自己的缺点, 它省略了构造函数传参初始化这一过程, 带来的缺点就是初始化的值都是一致的。而原型最大的缺点就是它最大的优点,那就是共享。
原型中所有属性是被很多实例共享的, 共享对于函数非常合适, 对于包含基本值的属性也还可以。但如果属性包含引用类型,就存在一定的问题:
function Box() {};
Box.prototype = {
    constructor : Box,
    name : 'Lee',
    age : 100,
    family : ['父亲', '母亲', '妹妹'], //添加了一个数组属性
    run : function () {
    return this.name + this.age + this.family;
  }
};
var box1 = new Box();

box1.family.push('哥哥'); //在实例中添加'哥哥'
alert(box1.run());
var box2 = new Box();
alert(box2.run()); //共享带来的麻烦,也有'哥哥'了
PS:数据共享的缘故,导致很多开发者放弃使用原型,因为每次实例化出的数据需要保留自己的特性,而不能共享。

为了解决构造传参和共享问题,可以组合构造函数+原型模式:
function Box(name, age) { //不共享的使用构造函数
this.name = name;
this.age = age;
this. family = ['父亲', '母亲', '妹妹'];
};
Box.prototype = { //共享的使用原型模式
constructor : Box,
run : function () {
return this.name + this.age + this.family;
}
};
PS:这种混合模式很好的解决了传参和引用共享的大难题。是创建对象比较好的方法。
原型模式, 不管你是否调用了原型中的共享方法, 它都会初始化原型中的方法, 并且在
声明一个对象时,构造函数+原型部分让人感觉又很怪异,最好就是把构造函数和原型封装
到一起。为了解决这个问题,我们可以使用动态原型模式。
function Box(name ,age) { //将所有信息封装到函数体内
this.name = name;
this.age = age;
if (typeof this.run != 'function') { //仅在第一次调用的初始化
Box.prototype.run = function () {
return this.name + this.age + '运行中...';
};
}
}
var box = new Box('Lee', 100);
alert(box.run());
当第一次调用构造函数时,run()方法发现不存在,然后初始化原型。当第二次调用, 就
不会初始化, 并且第二次创建新对象, 原型也不会再初始化了。 这样及得到了封装, 又实现
了原型方法共享,并且属性都保持独立。
if (typeof this.run != 'function') {
alert('第一次初始化'); //测试用
Box.prototype.run = function () {
return this.name + this.age + '运行中...';
};
}
var box = new Box('Lee', 100); //第一次创建对象
alert(box.run()); //第一次调用
alert(box.run()); //第二次调用
var box2 = new Box('Jack', 200); //第二次创建对象
alert(box2.run());
alert(box2.run());
PS:使用动态原型模式,要注意一点,不可以再使用字面量的方式重写原型,因为会
切断实例和新原型之间的联系。
以上讲解了各种方式对象创建的方法, 如果这几种方式都不能满足需求, 可以使用一开
始那种模式:寄生构造函数。
function Box(name, age) {
var obj = new Object();
obj.name = name;
obj.age = age;
obj.run = function () {
return this.name + this.age + '运行中...';
};
return obj;
}
寄生构造函数,其实就是工厂模式+构造函数模式。这种模式比较通用,但不能确定对
象关系,所以,在可以使用之前所说的模式时,不建议使用此模式。
在什么情况下使用寄生构造函数比较合适呢?假设要创建一个具有额外方法的引用类
型。由于之前说明不建议直接 String.prototype.addstring,可以通过寄生构造的方式添加。
function myString(string) {
var str = new String(string);
str.addstring = function () {
return this + ',被添加了!';
};
return str;
}
var box = new myString('Lee'); //比直接在引用原型添加要繁琐好多
alert(box.addstring());
在一些安全的环境中, 比如禁止使用 this 和 new, 这里的 this 是构造函数里不使用 this,
这里的 new 是在外部实例化构造函数时不使用 new。这种创建方式叫做稳妥构造函数。
function Box(name , age) {
var obj = new Object();
obj.run = function () {
return name + age + '运行中...'; //直接打印参数即可
};
return obj;
}
var box = Box('Lee', 100); //直接调用函数
alert(box.run());
PS:稳妥构造函数和寄生类似。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值