1.通过字面量的方式创建对象
let per = {
name:'zhangsan',
age:25,
job:'html',
sayName:function(){
alert(this.name);
}
}
缺点:代码复用性差,如果要创建多个类似的对象,会产生大量的重复代码
2.通过new操作符和内置构造函数Object()
let obj = new Object();
obj.name = "张三";
obj.say = function () {
console.log("nihao");
}
缺点:代码复用性差,如果要创建多个类似的对象,会产生大量的重复代码
3.工厂模式
将对象构建的过程封装在一个函数中。这个函数能创建一个对象,并为他进行初始化赋值,最后返回这个对象。若要新建对象,调用这个工厂函数即可
function createPerson(name,age,job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
alert(this.name);
}
return o;
}
var person1 = createPerson('zhang',30,'java');
var person2 = createPerson('zhao',25,'php');
工厂模式缺点:工厂模式解决了重复实例化的问题,但还有一个问题,无法搞清楚他们到底是哪个对象的实例
4.构造函数模式
function Person(name,age,job){
this.name= name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
}
}
let person1 = new Person('zhang',30,'java');
let person2 = new Person('zhao',25,'php');
创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型。而这正是构造函数模式胜过工厂模式的地方。
然而,使用构造函数的主要问题,就是每个方法都要在每个实例上重新创建一遍。在上面的例子中,person1和person2都有一个名为sayName()的方法,但那两个方法不是同一个Function的实例,创建两个完成同样任务的Function实例的确没有必要;况且有this对象在,根本不用在执行代码前就把函数绑定到特定对象上面。因此可以像下面这样,通过把函数定义转移到构造函数外部来解决这个问题。
function Person(name,age,job){
this.name= name;
this.age = age;
this.job = job;
this.sayName = sayName();
}
function sayName(){
alert(this.name);
}
var person1 = createPerson('zhang',30,'java');
var person2 = createPerson('zhao',25,'php');
在这个例子中,我们把sayName()函数的定义转移到构造函数外部。而在构造函数内部,我们将sayName属性设置成等于全局的sayName函数,这样person1和person2对象就共享了在全局作用域中定义的同一个sayName()函数。这样确实解决了两个函数在做同一件事的问题,可是新问题又来了:在全局作用域中定义的函数实际上只能被某个对象调用,这让全局作用域有点名不副实。更让人无法接受的是:如果对象需要定义很多方法,那么就要定义很多全局函数。好在,这些问题可以通过使用原型模式来解决。
5.原型模式
我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。使用原型对象的好处是可以让所有的对象实例共享他所包含的属性和方法
function Person(){}
Person.prototype.name = 'zhang';
Person.prototype.age = '22';
Person.prototype.job = 'html5';
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
原型模式也不是没有缺点。首先,它省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值。虽然这会在某种程度上带来一些不方便,但还不是原型的最大问题。原型模式的最大问题是由共享的本性所导致的。
原型中所有属性是被很多实例共享的,这种共享对于函数非常合适。对于那些包含基本值的属性倒也说的过去,通过在实例上添加一个同名属性,可以隐藏原型中的对应属性。然后,对于包含引用类型的属性来说,问题就比较突出了。
function Person(){}
Person.prototype = {
constructor:Person,
name:'zhang',
age :'22',
job :'html5',
friends:['wang','li'],
sayName : function(){
alert(this.name);
}
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push('zhao');
alert(person1.friends); //'wang,li,zhao'
alert(person2.friends); //'wang,li,zhao'
alert(person1.friends === person2.friends); //true
从上面的打印的结果我们就可以知道为什么很少人单独使用原型模式了,实例一般都是要有属于自己的全部属性的
6.构造函数+ 原型模式
组合使用构造函数模式和原型模式,是创建自定义类型的最常见方式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。结果,每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度的节省了内存。
function Person(name,age,job){
this.name= name;
this.age = age;
this.job = job;
this.friends = ['wang','li'];
}
Person.prototype = {
constructor:Person,
sayName : function(){
alert(this.name);
}
}
var person1 = new Person('zhang',26,'java',);
var person2 = new Person('sun',25,'php');
person1.friends.push('zhao');
alert(person1.friends); //'wang,li,zhao'
alert(person2.friends); //'wang,li'
alert(person1.friends === person2.friends); //false
在上面的例子中,实例属性都是在构造函数中定义的,而由所有实例共享的属性constructor和方法satName()则是在原型中定义的。而修改了person1.friends(向其中添加一个新字符串),并不会影响到person2.friends,因为他们分别引用了不同的数组。
7.动态原型模式(完美方案)
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
if (typeof this.sayName != "function") {
Person.prototype.sayName = function() {
alert(this.name);
};
}
}
8.Object.create()
ES5定义了一个名为Object.create()的方法,它创建一个新对象,其中第一个参数是这个对象的原型,第二个参数对对象的属性进行进一步描述
9.另有寄生构造函数模式和稳妥构造函数模式