(1)字面量的形式创建对象
//通过字面量的方式直接创建对象
let person={
name:"张三",
age:14,
fn:function(){}
}
//通过{}方式创建对象,{}这种形式创建对象相当于new Object()的语法糖
let obj={};
obj.name="李四"; //通过对象.的形式添加属性或方法
obj.fn=function(){};
字面量这种方式创建大量相似对象时,会产生大量重复代码。但是js和一般面向对象的语言不同,在es6之前没有类的概念,但是我们可以利用函数进行模拟,从而产生可复用的对象创建方式,常见的有以下几种
(2)工厂模式,工厂模式的主要工作就是利用函数封装创建对象的细节,从而通过调用函数来达到复用的目的。
// 使用工厂模式创建对象
// 定义一个工厂方法
function createObject(name){
var obj = new Object();
o.name = name;
o.sayName = function(){
console.log(this.name);
};
return obj;
}
var o1 = createObject('张三');
var o2 = createObject('李四');
//优点:解决了前面的代码重复的问题
console.log(o1.sayName===o2.sayName);//false
虽然通过函数的方式解决了代码重复问题,但是还有一个很大的问题就是创建出来的对象无法和某个类型联系起来,它只是简单的封装了复用代码,但是没有建立起对象和类型之间的关系
(3)构造函数模式。js中每一个函数都可以作为构造函数使用(es6中的箭头函数例外),只要一个函数通过new关键字调用,我们就可以称它为构造函数。
//通过构造函数的方式创建对象
function Person(name,age){
this.name=name;
this.age=age;
this.sayName=function(){
console.log(this.name);
}
}
//创建对象
let p1=new Person("张三",20)
let p2=new Person("李四",30)
p1.sayName();//"张三"
p2.sayName();//"李四"
通过构造函数的方式创建对象,对象和类型之间建立了联系,但是构造函数存在的一个缺点就是造成了不必要的函数对象的创建,因为在js中函数也是一个对象,因此如果对象属性中包含函数的话,那么每次创建对象就会新建一个函数对象,浪费了不必要的内存空间,因为函数是所有创建实例对象所通用的
(4)原型模式。每一个函数都有一个prototype属性。这个属性的属性值是一个对象,它包含了通过构造函数创建的实例对象都能共享访问的属性和方法。因此我们可以为原型对象添加公用的属性和方法,从而实现代码的复用。
function Person(name,age){
Person.prototype.name="人"
//如果在原型上添加sayName方法会发生什么?
Person.prototype.sayName=function(){
console.log(this.name);
}
}
let p1=new Person();
let p2=new Person();
p1.sayName(); //'人'
Person.prototype.arr=[1,3,4]
p1.arr.push(5);
console.log(p2.arr);//[1,3,4,5]
这种方式对于构造函数模式来说,解决了函数对象复用的问题,但是这种模式也存在一问题。一个是没有办法通过传入参数来初始化值,另一个就是因为是所有实例对象所共有的,那么当原型上的属性的属性值是引用类型数据(例如数组),那么一个实例对数据的改变会影响到所有的实例对象
(5)组合使用构造函数和原型模式。这是创建自定义类型最常用的方法,因为构造函数模型和原型模型分开使用都有一些问题,因此可以组合使用这两种模式,通过构造函数来初始化对象的属性,通过原型来实现函数对象的复用,这种方法很好的解决了两种模式单独使用的缺点,唯一有点不好的地方就是由于使用了两种模式,代码的封装不够好
function Person(name,age){
this.name=name;
this.age=age;
this.sayName=function(){
console.log(this.name);
}
}
// 在原型上添加方法
Person.prototype.sayAge=function(){
console.log(this.age)
}
//如果在原型上添加sayName方法会发生什么?
Person.prototype.sayName=function(){
console.log(this.name,'原型上的方法');
}
let p1=new Person("张三",33);
let p2=new Person("李四",32);
p1.sayAge(); //33
p2.sayAge(); //32
// 当访问一个属性时,会现在对象自身上查找,自身上没有,才会去自己的原型对象上找
p1.sayName(); //'张三'
(6)动态原型模式。这种模式将原型方法赋值的过程移动到构造函数内部,通过对属性是否存在的判断,可以实现仅在第一次调用函数时对原型对象属性赋值一次。
function Person(name,age){
this.name=name;
this.age=age;
if(typeof this.sayName!=='function'){
Person.prototype.sayName=function(){
console.log(this.name);
}
}
}
let p1=new Person("张三",33);
let p2=new Person("李四",32);
// 当访问一个属性时,会现在对象自身上查找,自身上没有,才会去自己的原型对象上找
p1.sayName(); //'张三'
p2.sayName(); //'李四'
(7)寄生构造函数模式。这一模式和工厂模式的实现基本相同,它主要是基于一个已有的类型,在实例化时对实例对象进行拓展。这样既不用修改既不用修改原来的构造函数,也实现了扩展对象的目的。它的问题跟工厂模式的问题一样,不能建立对象和类型之间的关系。
function Person(name,age) {
let obj = new Object();
obj.name = name;
obj.age = age;
obj.sayName = function() {
console.log(this.name);
}
return obj;
}
let p1= new Person('张三',18);
p1.sayName();