一般使用字面量的形式直接创建对象,但是这种创建方式对于创建大量相似对象的时候,会产生大 量的重复代码。但 js和一般的面向对象的语言不同,在 ES6 之前它没有类的概念。但是可以使用函数来 进行模拟,从而产生出可复用的对象创建方式,常见的有以下几种:
1.工厂模式
//搞一个工厂函数反复调用
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');
//总结:工厂模式虽然解决了创建多个相似对象的问题,但是没有解决对象识别问题,即不能知道一个对象的类型
2.构造函数模式
//没有显示的创建对象,使用new来调用这个构造函数,使用new后会自动执行如下操作:
/*
①创建一个新对象;
②将构造函数的作用域赋给新对象(因此this就指向了这个新对象);
③执行构造函数中的代码(为这个新对象添加属性);
④返回新对象。
*/
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');
/*************************************************************************************/
//缺点:每个方法都要在每个实例上重新创建一遍。解决方法如下:
//创建两个完成同样任务的的 Function 实例的确没有必要。况且有 this 对象在,根本不用在执行代码前就把函数绑定到特定的对象上
function Person( name, age, job ){
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;//函数指针,这样实例就会共享一个函数地址
}
function sayName(){
alert( this.name );
}
//这样也有问题
//1.全局作用域中定义的函数(sayName)实际上只能被某个对象调用,这让全局作用域有点名不副实。
//2.如果对象需要定义很多方法,那么就需要定义很多个全局函数,这样一来,我们自定义的这个引用类型就毫无封装性可言了。
//这些问题可以通过使用原型模式来解决。⬇
3.原型模式
//将信息直接添加到原型对象上。
//使用原型的好处是可以让所有的实例对象共享它所包含的属性和方法,不必在构造函数中定义对象实例信息,而是可以将这些信息直接添加到原型对象中。
function Person() { }
Person.prototype.name = 'Mike';//这样可就每个人都叫MIKE了哈
Person.prototype.job = 'student';
Person.prototype.sayName = function() {
console.log(this.name)
}
var person1 = new Person();
//还有更简单的写法
function Person(){ }
Person.prototype = {
constructor = Person()//设置新原型,再将constructor指回构造函数
name : "Mike",
age : 29,
job : "engineer",
syaName : function(){
alert( this.name );
}
};
原型易错点
无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个 prototype 属性。 在默认情况下,所有 prototype 属性都会自动获得一个 constructor(构造函数) 属性,这个属性包含一个指向 prototype 属性所在函数的指针。
每当代码读取某个对象的某个属性时,都会执行一搜索,目标是具有给定名字的属性。搜索首先从对象实例本身开始。如果在实例中找到了具有给定名字的属性,则返回该属性的值;如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性。如果在原型对象中找到了这个属性,则返回该属性的值。
虽然可以通过对象实例访问保存在原型中的值,但却不能通过对象实例重写原型中的值。 如果我们在实例中添加了一个属性,而该属性与实例中的一个属性同名,那么就会在实例中创建该属性,该属性将会屏蔽原型中的那个属性。
即使是将属性设置为 null,也只是在实例中的属性值为 null。 不过,使用delete操作符可以完全删除实例属性,从而能够重新访问原型中的属性。 使用 hasOwnProperty() 方法可以检测一个属性是存在于实例中,还是存在与原型中。这个方法只在给定属性存在于对象实例中时,才会返回 true。
4、组合使用构造函数模式和原型模式
//组合使用构造函数模式和原型模式是使用最为广泛、认同度最高的一种创建自定义类型的方法。
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
//值得注意,sayName中的this指向调用它的对象,可不是指向定义它的作用域,箭头函数的this指向定义它的作用域。
5、动态原型模式
//动态原型模式将所有信息都封装在了构造函数中,初始化的时候,可以通过检测某个应该存在的方法是否有效,来决定是否需要初始化原型。
//特点:节约第二次及以后调用的时间
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在这了,只有第一次调用Person时候才给原型绑定,避免重复操作。
6、寄生构造函数模式
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
//注意:
//这样创建出来的对象与构造函数之间没有什么关系, instanceof 操作符对他们没有意义。