实例化方法:
创建一个对象,然后给这个对象新建属性和方法。
var box = new Object(); //创建一个Object对象
box.name = 'Lee'; //创建一个name属性并赋值
box.age = 100; //创建一个age属性并赋值
box.run = function () { //创建一个run()方法并返回值
return this.name + this.age + '运行中...';
};
alert(box.run()); //输出属性和方法的值
上面创建了一个对象,并且创建属性和方法,在run()方法里的this,就是代表box对象本身。这种是JavaScript创建对象最基本的方法,但有个缺点,想创建一个类似的对象,就会产生大量的代码。
工厂模式:
function createObject(name, age) { //集中实例化的函数
var obj = new Object();
obj.name = name;
obj.age = age;
obj.run = function () {
return this.name + this.age + '运行中...';
};
return obj;
}
var box1 = createObject('Lee', 100); //第一个实例
var box2 = createObject('Jack', 200); //第二个实例
alert(box1.run());
alert(box2.run()); //保持独立
工厂模式解决了重复实例化的问题,但还有一个问题,那就是识别问题,因为根本无法搞清楚他们到底是哪个对象的实例。
alert(typeof box1); //Object
alert(box1 instanceof Object); //true
构造函数模式:
function Box(name, age) { //构造函数模式
this.name = name;
this.age = age;
this.run = function () {
return this.name + this.age + '运行中...';
};
}
var box1 = new Box('Lee', 100); //new Box()即可
var box2 = new Box('Jack', 200);
alert(box1.run());
alert(box1 instanceof Box); //很清晰的识别他从属于Box
使用构造函数的方法,即解决了重复实例化的问题,又解决了对象识别的问题,但问题是,这里并没有new Object(),为什么可以实例化Box(),这个是哪里来的呢?
使用了构造函数的方法,和使用工厂模式的方法他们不同之处如下:
1.构造函数方法没有显示的创建对象(new Object());
2.直接将属性和方法赋值给this对象;
3.没有renturn语句。
构造函数的方法有一些规范:
1.函数名和实例化构造名相同且大写,(PS:非强制,但这么写有助于区分构造函数和普通函数);
2.通过构造函数创建对象,必须使用new运算符。
原型:
prototype通过调用构造函数而创建的那个对象的原型对象。使用原型的好处可以让所有对象实例共享它所包含的属性和方法
在原型模式声明中,多了两个属性,这两个属性都是创建对象时自动生成的。__proto__属性是实例指向原型对象的一个指针,它的作用就是指向构造函数的原型属性constructor。通过这两个属性,就可以访问到原型里的属性和方法了。
PS:IE浏览器在脚本访问__proto__会不能识别,火狐和谷歌浏览器及其他某些浏览器均能识别。虽然可以输出,但无法获取内部信息。
alert(box1.__proto__); //[object Object]
判断一个对象是否指向了该构造函数的原型对象,可以使用isPrototypeOf()方法来测试。
alert(Box.prototype.isPrototypeOf(box)); //只要实例化对象,即都会指向
in操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实例中还是原型中。
alert('name' in box); //true,存在实例中或原型中
原型模式的执行流程:
1.先查找构造函数实例里的属性或方法,如果有,立刻返回;
2.如果构造函数实例里没有,则去它的原型对象里找,如果有,就返回;
图解
原型模式创建对象也有自己的缺点,它省略了构造函数传参初始化这一过程,带来的缺点就是初始化的值都是一致的。而原型最大的缺点就是它最大的优点,那就是共享。
原型中所有属性是被很多实例共享的,共享对于函数非常合适,对于包含基本值的属性也还可以。但如果属性包含引用类型,就存在一定的问题:
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:稳妥构造函数和寄生类似。