JavaScript建立对象的方式有不少,经过Object构造函数或对象字面量的方式也能够建立单个对象,显然这两种方式会产生大量的重复代码,并不适合量产。接下来介绍七种很是经典的建立对象的方式,他们也各有优缺点。(内容主要来自于《JavaScript高级程序设计》,还参考了一下别人写的文章)安全
function createPerson(name, job) { var o = new Object(); o.name = name; o.job = job; o.sayName = function() { console.log(this.name); } return o } var person1 = createPerson('Mike', 'student') var person2 = createPerson('X', 'engineer')
能够无数次调用这个工厂函数,每次都会返回一个包含两个属性和一个方法的对象。
工厂模式虽然解决了建立多个类似对象的问题,可是没有解决对象识别问题,即不能知道一个对象的类型。函数
function Person(name, job) { this.name = name; this.job = job; this.sayName = function() { console.log(this.name); } } var person1 = new Person('Mike', 'student') var person2 = new Person('X', 'engineer')
没有显示的建立对象,使用new来调用这个构造函数,使用new后会自动执行以下操做:
①建立一个新对象;
②将构造函数的做用域赋给新对象(所以this就指向了这个新对象);
③执行构造函数中的代码(为这个新对象添加属性);
④返回新对象。
缺点:每一个方法都要在每一个实例上从新建立一遍。
建立两个完成一样任务的的Function实例的确没有必要。何况有this对象在,根本不用在执行代码前就把函数绑定到特定的对象上,能够经过这样的形式定义:this
function Person( name, age, job ){ this.name = name; this.age = age; this.job = job; this.sayName = sayName; } function sayName(){ alert( this.name ); }
如此一来,就能够将sayName()函数的定义转移到构造函数外部。而在构造函数内部,咱们将sayName属性设置成全局的sayName函数。这样的话,因为sayName包含的是一个指向函数的指针,所以person1和person2对象就能够共享在全局做用域中定义的同一个sayName()函数。prototype
这样作解决了两个函数作同一件事的问题,可是新的问题又来了:在全局做用域中定义的函数实际上只能被某个对象调用,这让全局做用域有点名存实亡。而更重要的是:若是对象须要定义不少方法,那么就须要定义不少个全局函数,这样一来,咱们自定义的这个引用类型就毫无封装性可言了。设计
这些问题能够经过使用原型模式来解决。指针
function Person() { } Person.prototype.name = 'Mike' Person.prototype.job = 'student' Person.prototype.sayName = function() { console.log(this.name) } var person1 = new Person()
将信息直接添加到原型对象上。使用原型的好处是可让全部的实例对象共享它所包含的属性和方法,没必要在构造函数中定义对象实例信息,而是能够将这些信息直接添加到原型对象中。
①理解原型
不管何时,只要建立了一个新函数,就会根据一组特定的规则为该函数建立一个prototype属性。
在默认状况下,全部prototype属性都会自动得到一个constructor(构造函数)属性,这个属性包含一个指向prototype属性所在函数的指针。
每当代码读取某个对象的某个属性时,都会执行一搜索,目标是具备给定名字的属性。搜索首先从对象实例自己开始。若是在实例中找到了具备给定名字的属性,则返回该属性的值;若是没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具备给定名字的属性。若是在原型对象中找到了这个属性,则返回该属性的值。
虽然能够经过对象实例访问保存在原型中的值,但却不能经过对象实例重写原型中的值。
若是咱们在实例中添加了一个属性,而该属性与实例中的一个属性同名,那么就会在实例中建立该属性,该属性将会屏蔽原型中的那个属性。
即便是将属性设置为null,也只是在实例中的属性值为null。
不过,使用delete操做符能够彻底删除实例属性,从而可以从新访问原型中的属性。
使用hasOwnProperty() 方法能够检测一个属性是存在于实例中,仍是存在与原型中。这个方法只在给定属性存在于对象实例中时,才会返回true。code
②原型与in操做符
in操做符会在经过对象可以访问给定属性时返回true,不管该属性是存在于实例中仍是原型中。对象
③更简单的原型语法ip
function Person(){ } Person.prototype = { name : "Mike", age : 29, job : "engineer", syaName : function(){ alert( this.name ); } };
//在上面的代码中,将Person.prototype设置为等于一个以对象字面量形式建立的新对象。最终结果相同,但有一个例外:constructor属性再也不指向Person。作用域
组合使用构造函数模式和原型模式是使用最为普遍、认同度最高的一种建立自定义类型的方法。它能够解决上面那些模式的缺点,使用此模式可让每一个实例都会有本身的一份实例属性副本,但同时又共享着对方法的引用,这样的话,即便实例属性修改引用类型的值,也不会影响其余实例的属性值了。还支持向构造函数传递参数,可谓是集两种模式的优势。
function Person(name) { this.name = name; this.friends = ['Jack', 'Merry']; } Person.prototype.sayName = function() { console.log(this.name); } var person1 = new Person(); var person2 = new Person(); person1.friends.push('Van'); console.log(person1.friends) //["Jack", "Merry", "Van"] console.log(person2.friends) // ["Jack", "Merry"] console.log(person1.friends === person2.friends) //false
动态原型模式将全部信息都封装在了构造函数中,初始化的时候。能够经过检测某个应该存在的方法是否有效,来决定是否须要初始化原型。
function Person(name, job) { // 属性 this.name = name; this.job = job; // 方法 if(typeof this.sayName !== 'function') { Person.prototype.sayName = function() { console.log(this.name) } } } var person1 = new Person('Mike', 'Student') person1.sayName()
只有在sayName方法不存在的时候,才会将它添加到原型中。这段代码只会初次调用构造函数的时候才会执行。此后原型已经完成初始化,不须要在作什么修改了,这里对原型所作的修改,可以当即在全部实例中获得反映。
其次,if语句检查的能够是初始化以后应该存在的任何属性或方法,因此没必要用一大堆的if语句检查每个属性和方法,只要检查一个就行。
这种模式的基本思想就是建立一个函数,该函数的做用仅仅是封装建立对象的代码,而后再返回新建的对象
function Person(name, job) { var o = new Object(); o.name = name; o.job = job; o.sayName = function() { console.log(this.name) } return o } var person1 = new Person('Mike', 'student') person1.sayName()
这个模式,除了使用new操做符并把使用的包装函数叫作构造函数以外,和工厂模式几乎同样。
构造函数若是不返回对象,默认也会返回一个新的对象,经过在构造函数的末尾添加一个return语句,能够重写调用构造函数时返回的值。
首先明白稳妥对象指的是没有公共属性,并且其方法也不引用this。稳妥对象最适合在一些安全环境中(这些环境会禁止使用this和new),或防止数据被其余应用程序改动时使用。
稳妥构造函数模式和寄生模式相似,有两点不一样:1.是建立对象的实例方法不引用this;2.不使用new操做符调用构造函数
function Person(name, job) { var o = new Object(); o.name = name; o.job = job; o.sayName = function() { console.log(name) //注意这里没有了"this"; } return o } var person1 = Person('Mike', 'student') person1.sayName();
和寄生构造函数模式同样,这样建立出来的对象与构造函数之间没有什么关系,instanceof操做符对他们没有意义