1.工厂模式的概念
什么是工厂模式:用于创建特定对象时降低代码冗余的一种方式
正常创建对象的缺点:
字面量创建对象:一次性,如果需要创建多个对象则会需要写很多重复的代码
构造函数创建对象:不能看出是一个整体
工厂模式创建对象的过程:
//将创建对象的代码封装在一个函数中
function createPerson(name, age, gender) {
var person = new Object();
person.name = name;
person.age = age;
person.gender = gender;
person.sayName = function () {
console.log(this.name);
}
return person;
}
//利用工厂函数来创建对象
var person1 = createPerson("zhangsan", 18, 'male');
var person2 = createPerson("lisi", 20, 'female');
使用工厂模式创建对象的优缺点:
优点:只需要输入参数就可以创建出一个对象
缺点:方法冗余 无法区分种类 因为返回的是一个对象,所以创建的实例的构造函数是Object而不是Person
2.自定义构造函数模式
构造函数:用于创建特定类型对象的方法,例如:Object,Array
自定义构造函数:
// 自定义构造函数
function Person(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
this.sayName = function () {
console.log(this.name);
}
}
var person1 = new Person('zhangsan', 29, 'male');
var person2 = new Person('lisi', 19, 'female');
person1.sayName(); // zhangsan
person2.sayName(); // lisi
自定义构造函数与正常的函数创建对象的**区别**:没有显式的创建对象 属性和方法直接赋值给this,
利用this调用 没有return
使用自定义构造函数创建一个对象:
var person1 = new Person('zhangsan', 29, 'male');
var person2 = new Person('lisi', 19, 'female');
注意:创建的对象会像正常对象一样有一个constructor属性,指向Person。在构造函数内部的this被赋值为这个新对象
new操作符:
在使用new操作符创建对象时,new操作符在构造函数中隐式的创建了一个当前构造函数的实例对象,
修改了this的指向,使其指向实例对象,执行函数体,返回实例对象
instanceof运算符
作用:用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上
语法:console.log(person1 instanceof Object); // true
使用函数表达式的方式声明自定义构造函数:
var Person = function (name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
this.sayName = function () {
console.log(this.name);
};
}
var person1 = new Person("zhangsan", 29, "male");
var person2 = new Person("lisi", 27, "female");
person1.sayName(); // zhangsan
person2.sayName(); // lisi
注意:在实例化对象时可以不传递参数,而这时就可以不用加构造函数后的括号,只需要new关键字就能调用相应的构造函数
构造函数与普通函数的区别:
构造函数与普通函数没有本质上的区别,构造函数也是一个函数,只是构造函数在调用时是使用new关键字来创建一个对象,而不使用new关键字调用构造函数而是直接调用构造函数,其效果就是一个普通函数
经典案例:
var Person = function (name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
this.sayName = function () {
console.log(this.name);
};
}
// 作为构造函数
var person = new Person("Jacky", 29, "male");
person.sayName(); // Jacky
// 作为函数调用
Person("lisi", 27, "female"); // 添加到全局对象 node global 浏览器 window
global.sayName(); // lisi
// 在另一个对象的作用域中调用
var o = new Object();
Person.call(o, "wangwu", 25, "male");
o.sayName(); // wangwu
注意:在没用使用new关键字调用构造函数时,会将属性和方法添加到全局对象中。
特别注意:当一个函数没有明确设置this的指向时(即没有对象调用,没有使用call,apply,bind改变this指向),this会始终指向global对象(浏览器中指向window对象)
构造函数的问题:在创建对象时,其中的方法都会重新创建一次,即每个通过构造函数创建的对象的方法都是不同的。虽然名字是一样的,但是是不同的两个方法
由于我们没必要定义两个作用一样但是却是两个不同的方法,因此,我们可以把函数的定义转移到构造函数外部
function Person(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
this.sayName = sayName;
}
function sayName() {
console.log(this.name);
}
var person1 = new Person("zhangsan", 29, "male");
var person2 = new Person("lisi", 27, "female");
构造函数模式的缺点:由于person1和person2共享了全局作用域上的sayName方法,导致全局作用域会被
扰乱(即那个函数实际上只能在一个对象上调用。如果这个对象需要多个方法,那么就要在全局作用域中定义
多个函数)
3.原型模式(用于解决构造函数模式的缺点)
语法:
function Person(){}
Person.prototype.name = "zhangsan";
Person.prototype.age = 29;
Person.prototype.gender = "male";
Person.prototype.sayName = function () {
console.log(this.name);
};
var person1 = new Person();
person1.sayName(); // zhangsan
var person2 = new Person();
person2.sayName(); // zhangsan
console.log(person1.sayName == person2.sayName); // true
概念:通过每一个函数中都会存在一个prototype属性,包含由特定引用类型的实例属性和方法,并且能够被
实例对象共享
原型层级:在访问对象属性时,会根据属性名先在对象实例本身开始搜索,如果搜索到了就返回对应的值,如果没有搜索到,则会根据指针向原型对象中搜索直到搜索到最大的引用对象Object,如果还是没有搜索到则返回error。
根据原型层级的思想可以发现,如果实例对象添加了一个和原型对象中的某一个属性同名的属性,由于首先先会对
实例对象进行搜索,则会把原型对象上的同名属性遮蔽住(是遮蔽住而不是覆盖,原型对象中的同名属性还继续
存在,但是访问时会优先返回实例对象上的同名属性值)
注意:即使把实例对象上的同名属性赋值为null也会产生遮蔽问题
如何解决这个遮蔽问题:通过delete操作符完全删除实例对象上的同名属性
原型与in操作符:in操作符可以检测某一个属性是否在实例或原型上
in操作符配合hasOwnPrototype方法判断某一个属性是否是原型属性:
语法:
function hasPrototypeProperty(object, name) {
//不在实例中但是可以访问到的属性属于原型属性
return !object.hasOwnProperty(name) && (name in object);
}
更简单的原型模式:
function Person() {}
Person.prototype = {
name: "zhangsan",
age: 29,
gender: "male",
sayName() {
console.log(this.name);
}
};
/* 在这个案例中,Person.prototype 被设置为等于一个通过对象字面量创建的
新对象。最终结果是一样的,只有一个问题:这样重写之后,Person.prototype
的 constructor 属性就不指向 Person了。在创建函数时,也会创建它的
prototype 对象,同时会自动给这个原型的 constructor 属性赋值。而上面的写
法完全重写了默认的 prototype 对象,因此其 constructor 属性也指向了完全
不同的新对象(Object 构造函数),不再指向原来的构造函数。*/
var person1 = new Person()
console.log(person1.constructor === Person); //false
console.log(person1.constructor === Object); //true
注意:与之前的原型模式相比,这个原型模式更简单,但是由于这种方式相当于重写
了Person.prototype属性,并且并没有重写constructor这个属性,所以当前的
constructor会指向Object而不是Person,需要专门设置这个值。
重新设置constructor的值:
Object.defineProperty(Person.prototype, “constructor”, {
enumerable: false,
value: Person
});
原型模式存在的问题:由于原型模式是把所有的属性和方法维护在原型中的,这可以使得
所有的实例对象都可以贡献这些方法,但是当共享一个引用类型时,相当于是共享同一个内存
地址,这会导致一个实例对象修改了这个引用类型的值,但是另一个实例对象的这个引用类型的
值也会被修改。
4.组合模式(构造函数模式+原型模式)
在组合模式创建实例对象时,对于这个实例对象的私有属性和方法,利用构造函数进行维护,而对于
一些实例对象之间共享的方法和属性,则使用原型模式进行维护。