一、工厂模式
抽象了创建具体对象的过程(用函数封装,用特定接口创建对象的细节)
优点:解决了创建多个相似对象的问题
缺点:无法识别生成的实例类型
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("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");
二、构造函数
用来初始化新创建的对象的函数是构造函数
优点:可以用来创建特定类型的对象。
缺点:是每个方法都要在每个实例上重新创建一遍,占用内存
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
};
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
2.1 构造函数与工厂模式的区别
- 没有显式地创建对象;
- 直接将属性和方法赋给了 this 对象;
- 没有 return 语句。
2.2 关于构造函数应注意的地方
- 构造函数的函数名第一个字母大写(构造函数本身也是函数,这样做的目的是区别其他函数)
- 构造函数的作用是为了创建不同的类型对象
- 要创建构造函数的实例对象,必须使用new操作符来调用函数(构造函数与其他函数的唯一区别);可以用一个构造函数,构造多个实例对象
// 当作构造函数使用
var person = new Person("Nicholas", 29, "Software Engineer");
person.sayName(); //"Nicholas"
// 作为普通函数调用
Person("Greg", 27, "Doctor"); // 添加到 window
window.sayName(); //"Greg"
// 在另一个对象的作用域中调用
var o = new Object();
Person.call(o, "Kristen", 25, "Nurse");
o.sayName(); //"Kristen"
2.3 new操作符调用构造函数会经历4个步骤
- 创建一个新对象;
- 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象);
- 执行构造函数中的代码(为这个新对象添加属性);
- 返回新对象。
三、原型模式
优点:让所有的对象实例共享原型对象所包含的属性和方法
缺点:所有实例在默认情况下都取得相同的属性值
当某个实例改变引用类型值的属性,其他实例的这个属性的值也会被同步改变(属性:数组,操作:push)
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
person1.sayName(); //"Nicholas"
var person2 = new Person();
person2.sayName(); //"Nicholas"
alert(person1.sayName == person2.sayName); //true
3.1 理解原型模式
每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象(函数的原型对象),而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。其实也就是调用构造函数生成的所有的实例共享的一个原型对象
3.2 理解原型对象
创建了自定义的构造函数(以Person为例)之后,该函数有一个prototype属性,这个属性指向函数的原型对象。而原型对象默认只会取得constructor(构造函数)属性,这个属性包含一个指向prototype 属性所在函数的指针。Person.prototype.constructor 指向Person。当调用构造函数创建一个新实例(person1)后,该实例的内部将包含一个指针 __ proto __(内部属性),这个属性指向构造函数的原型对象,而非构造函数。
注意:这个连接存在于实例与构造函数的原型对象之间,而不是存在于实例与构造函数之间。
以使用Person构造函数和Person.prototype 创建实例的代码为例,展示各个对象之间的关系
![](https://img-blog.csdnimg.cn/20200207231014256.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zOTMwNzI3Mw==,size_16,color_FFFFFF,t_70)
3.3 通过原型模式读取属性的方式
每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性。搜索首先对对象实例本身开始。如果在实例中找到了具有给定名字的属性,则返回该属性值;如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性。如果在原型对象中找到了这个属性,则返回该属性的值。也就是说,在我们调用 person1.sayName() 的时候,会先后执行两次搜索。首先,解析器会问:“实例 person1 有 sayName 属性吗?”回答:“没有”。然后继续搜索,再问:“person1 的原型有 sayName 属性吗?”答:“有”。于是读取那个保存在原型对象中的函数。当我们调用person2.sayName()时,将会重现相同的搜索过程,得到相同的结果。而这正是多个对象实例共享原型所保存的属性和方法的基本原理。
3.4 注意
- 虽然可以通过对象实例访问保存在原型中的值,但却不能通过对象实例重写原型中的值(下层不能碰上层)。如果我们在实例中添加了一个属性,而该属性与实例原型中的一个属性同名,那我们就在实例中创建该属性,该属性将会屏蔽原型中的那个属性。换句话说,添加这个属性只会阻止我们访问原型中的那个属性,但不会修改那个属性。
- hasOwnProperty()方法,只在给定属性存在于对象实例中时,而不是属于原型时候,才会返回true。通过使用hasOwnProperty()方法,什么时候访问的是实例属性,什么时候访问的是原型属性就一清二楚了。
- 重新定义原型对象的时候一定要注意下面这种方式,此时constructor不在指向Person(而是Object),为了依旧指向Person,需要显式指定。这是因为尽管可以随时为原型添加属性和方法,并且修改能够立即在所有对象实例中反映出来,但如果是重写整个原型对象,那么情况就不一样了,把原型修改为另外一个对象就等于切断了构造函数与最初原型之间的联系
function Person(){
}
Person.prototype = {
constructor : Person, (显式指示)
name : "Nicholas",
age : 29,
job: "Software Engineer",
sayName : function () {
alert(this.name);
}
};
四、组合使用构造函数模式和原型模式
创建自定义类型的最常见方式
构造函数模式用于定义实例属性;原型模式用于定义方法和共享的属性
优点:每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ["Shelby", "Court"];
}
Person.prototype = {
constructor : Person,
sayName : function(){
alert(this.name);
}
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
person1.friends.push("Van");
alert(person1.friends); //"Shelby,Count,Van"
alert(person2.friends); //"Shelby,Count"
alert(person1.friends === person2.friends); //false
alert(person1.sayName === person2.sayName); //true