建立对象有不少种方式,首先是最简单基本的两种方式:chrome
①建立一个object实例安全
var o = new Object(); o.name = "a"; o.age = "12"; o.setName = function(){ alert(this.name); }
②对象字面量app
var o = { name: "a", age: "12", setName: function(){ alert(this.name); } };
这两种方法建立单个对象是没什么问题,但很明显的,若须要建立大量对象,就会产生不少重复的代码。因此如下就讲一下能解决这个问题的7种模式。函数
工厂模式:用一个函数来封装建立对象的细节,返回建立的对象。this
function createPerson(name, age){ var o = new Object(); o.name = name; o.age = age; o.setName = function(){ alert(this.name); }; return o; } var p1 = createPerson('a', 12); var p2 = createPerson('b', 23);
虽然工厂模式解决了建立多个类似对象的问题,可是却没法识别对象类型。spa
function Person(name, age){ this.name = name; this.age = age; this.setName = function(){ alert(this.name); }; } var p1 = new Person('a', 12); var p2 = new Person('b', 23); alert(p1.constructor == Person);//true alert(p1 instanceof Object);//true alert(p1 instanceof Person);//true
它和工厂模式代码上的主要区别就在于它显式的建立了一个此构造函数的实例,而且还能够注意到构造函数以一个大写字母开头。prototype
new建立实例的过程:指针
①建立一个新对象code
②将构造函数的做用域赋给新建立的对象对象
③为新建立的对象添加属性
④返回新对象
这种模式下,实例就有了特定的类型,好比上面的代码中的p1,它的类型再也不只是Object,还有Person。
以这种方式定义的构造函数定义在Global对象中。
但这种模式并非没有缺点:在构造函数中建立的方法会在每一个实例上从新建立一遍。在js中,方法即函数,也是一个对象,以上的建立方法就至关于:
function Person(name,age){ this.name = name; this.age = age; this.setName = new Function(" alert(this.name)"); } var p1 = new Person('a', 12); var p2 = new Person('b', 23); alert(p1.setName == p2.setName);//false
每建立一个实例,方法都会被建立一次,因此两个实例的方法并非同一个方法,但其实两个方法要完成的是相同的任务,那可不能够把方法定义到构造函数外面呢?
function Person(name, age){ this.name = name; this.age = age; this.setName = setName; } function setName(){ alert(this.name); } var p1 = new Person('a', 12); var p2 = new Person('b', 23);
固然能够,可是定义在全局做用域中的函数就使咱们建立的自定义引用类型再也不具备封装性了,若是对象须要不少方法,那么就须要定义不少个全局函数。
每一个被咱们建立的函数都有一个原型属性,它是一个指针,指向了一个原型对象,而这个原型对象包含了能够由特定类型的全部实例共享的属性和方法。很明显地,原型模式的好处:让全部对象实例能够共享它所包含的属性和方法。
function Person(){ } Person.prototype.name = 'a'; Person.prototype.age = '12'; Person.prototype.setName = function(){ alert(this.name); }; var p1 = new Person(); var p2 = new Person(); alert(p1.setName == p2.setName);//true alert(Object.getPrototypeOf(p1));//Object object alert(Object.getPrototypeOf(p1) == Person.prototype);//true
原型对象和构造函数还有实例的关系:
原型对象中有一个constructor属性指向构造函数,而构造函数中有一个prototype属性指向原型对象,实例中又有一个内部指针[[prototype]]指向原型对象。
由于constructor属性在原型对象中,因此这个属性是共享的,能够被对象实例访问。而[[prototype]]是一个内部指针,全部实现都没法访问到(可是在ff、safari、chrome中每一个对象都支持一个属性__proto__,其余实现中,这个属性对脚本则是彻底不可见的),只能经过isPrototypeOf()方法来肯定对象之间是否存在这种关系或者用Object.getPrototypeOf(instance)返回[[prototype]]的值。
能够经过对象实例访问保存在原型中的值,可是却不能经过对象实例重写原型的值,只能屏蔽原型中的那个属性。
搜索过程以下:
每次代码读取一个对象的属性时,会进行一次搜索。先搜索实例对象里的属性,若没有,再去实例对应的原型对象中搜索。
若在实例中设置了和原型对象中同名的属性,那么即便再将实例中设置的同名属性设置为null,原型对象中的那个属性也将再也不能被获取到,它会阻断咱们访问原型中的哪一个属性,但不会修改,除非使用delete操做符删除实例属性。
原型对象也能够用对象字面量的方式:
function Person(){} Person.prototype = { name: 'a', age: 12, setName: function(){ alert(this.name); } };
可是用这种方式至关于将原型对象重写了一遍,此时constructor就再也不指向Person了,而是指向了Object,因此应该在对象字面量中设置下constructor属性(从新设置的constructor属性的[[enumerable]]会被设置为true)。
还须要注意的一点是,本来即使实例建立在原型以前也没有关系,但若用对象字面量就会出错:
function Person(){} var p1=new Person(); Person.prototype = { name: 'a', age: 12, setName: function(){ alert(this.name); } }; p1.setName();//error
由于实例对象的内部指针是指向原型对象的,而若在建立实例以后重写原型对象,那么原型对象就不是同一个了,这将会切断现有原型与任何以前已经存在的对象实例之间的联系。
原型对象的问题:
function Person(){} Person.prototype = { name: 'a', age: 12, friends : ['z', 'x'], setName: function(){ alert(this.name); }; }; var p1 = new Person(); var p2 = new Person(); p1.friends.push('q'); alert(p1.friends);//['z','x','q'] alert(p2.friends);//['z','x','q']
当对实例一的引用类型属性push值时,也会让实例二的属性也获得push的那个值。
结合原型模式和构造函数模式,让构造函数模式用于定义实例属性,原型模式用于定义方法和共享的属性。
function Person(name, age){ this.name = name; this.age = age; this.friends = ['z', 'x']; } Person.prototype.setName = function(){ alert(this.name); }; var p1 = new Person(); var p2 = new Person(); p1.friends.push('q'); alert(p1.friends);//['z','x','q'] alert(p2.friends);//['z','x']
这种模式最大限度地节省了内存,还支持向构造函数中传递参数,集两种模式之长。
动态原型模式拥有上一个模式的优势而且更像类。
function Person(name, age){ this.name = name; this.age = age;
// 只有在第一次调用构造函数时会添加此函数 if(typeof this.setName != 'function'){ Person.prototype.setName = function(){ alert(this.name); }; } }
若是用了这种模式,那么就不能再使用对象字面量重写原型,由于会切断现有实例与新原型之间的联系。
寄生构造函数模式:建立一个构造函数,构造函数中封装了建立对象的代码,返回这个对象。
function Person(name, age){ var o = new Object(); o.name = name; o.age = age; o.setName = function(){ alert(this.name); }; return o; } var p1=new Person('a', 12);
这个模式通常用于建立一个具备额外方法的引用类型:
function SpecialArray(){ var values = new Array(); values.push.apply(values, arguments); values.toPipedString = function(){ return this.join('|'); }; return values; } var color = new SpecialArray('red', 'green');
alert(color instanceof SpecialArray); // false alert(color.toPipedString());//red|green
这个模式返回的对象与构造函数或者构造函数的原型属性之间没有关系,建议在可以使用其余模式的状况下,尽可能不使用这种模式。
稳妥构造函数模式:适合在安全的环境中,防止数据被其余应用程序改动,它不能使用this和new。这种模式建立的对象和构造函数之间也没有什么关系。
function Person(name, age){ var o = new Object(); var name = name; o.sayName = function(){ alert(name); }; return o; } var p1 = Person('a',12); p1.sayName();//a
这个模式和寄生构造函数模式同样,返回的对象与构造函数或者构造函数的原型属性之间也没有关系。