JavaScript创建对象

146 阅读6分钟

对象创建方式

一、new Object()

创建一个Object的实例,再依次添加相应的属性和方法

var obj = new Object()
obj.name = "Beatrix"
obj.age = 21
console.log(obj)

二、对象字面量

  var obj = {
      "name":'Beatrix',
      "age":'21'
    }
  • 优点: 能够以字面量的形式快速的创建出对象
  • 缺点: 当需要创建大量且相似的对象时,这样的方式显得臃肿麻烦,同时也会产生很多重复的代码

三、工厂模式

在ES6之前,JS没有类的概念,因此使用函数来产生可复用的对象创建方式

  • 工厂模式指的是使用函数来封装对象的细节,从而抽象了创建具体对象的过程,通过调用函数可以创建多个相似的对象
 function createObj(name, age) {
                var obj = new Object();
                obj.name = name;
                obj.age = age;
                obj.sayName = function(name) {
                    console.log(`I am` + age);
                };
                return obj;
            }
            var obj1 = createObj("Beatrix", 21);
            var obj2 = createObj("zzzzq", 22);
            console.log(obj1);
  • 缺点: 创建的对象没有建立与类型之间的关系

四、构造函数模式

关于构造函数 详情见 juejin.cn/post/692721…

function Person(name, age) {
    this.name = name
    this.age = age
    this.sayName = function (){
        alert(this.name)
    }
}

let p1 = new Person('Beatrix', 21)
p1.sayName() // Beatrix
  • 优点:

显然通过构造函数创建可以将实例化对象与具体的类型(构造函数)联系在一起,可以通过原型来识别对象的原型(instanceof)

  • 缺点:
 this.sayName = function (){
        alert(this.name)
 }

像上面代码中,实例化对象的每个方法都要在每个实例上重新创建一遍

在js中函数也是一个对象,因此对象用于方法的话,那么每次都会去创建一个函数对象,浪费了不必要的内存空间, 因为函数是所有实例都可以拥有的

五、原型模式

  • 每一个函数都拥有prototype属性,它是一个对象,它包括了通过构造函数创建的所有实例化对象都能共享的属性和方法
function Person() {}
Person.prototype.name = "Beatrix"
Person.prototype.age = 21
Person.prototype.sayName = function() {
	alert(this.name)
}
var person1 = new Person();
person1.sayName();//Beatrix

var person2 = new Person();
person2.sayName();//Beatrix
  • 优点:

可以使用原型对象(prototype)来添加公共的属性和方法,从而实现函数对象的复用

  • 缺点:
  1. 初始化参数的问题:该模式省略了初始化参数的过程,所有的实例化对象都会取得默认的属性值
  2. 共享属性的问题:因为所有的实例对象都共享一组属性,若共享属性为基本类型就没有问题,但是共享属性若为引用类型,那么因为共享的原因各个对象对于属性的操作都不是独立的,就会造成读写的混乱
 function Person() {}
 //设置原型对象
 Person.prototype.name = "Beatrix";
 Person.prototype.age = 21 
 Person.prototype.grade = [1,2,3,4];   
 
 var person1 = new Person();
 person1.grade.push(5);//改变grade属性
 var person2 = new Person();
 console.log(person2.grade);//[1,2,3,4,5]

六、组合使用构造函数和原型模式

  • 这是创建自定义类型最常见的方式,使用构造函数模式让每个实例都拥有了自己的属性,同时使用原型模式让其共享着对方法的引用
function Person(name, age) {
    this.name = name
    this.age = age
}
Person.prototype = {
	constructor: Person,
    sayName: function() {
    	alert(this.name)
    }
}
var person1 = new Person("Beatrix", 21)
var person2 = new Person("ZZZ",22)
console.log(person1.name);//"Beatrix"
console.log(person1.sayName===person2.sayName);//true
  • 优点: 解决了单独使用构造函数模式或者原型模式带来的弊端
  • 缺点: 同时使用两种方式,代码的封装性不是很好

七、动态原型模式

  • 将所有信息都封装到构造函数中,从而解决属性和函数分离的问题。与组合方式思想基本相同,共用的函数和属性用原型方式,非共用的的函数和属性用构造函数的方式。
function Person(name, age) {
    this.name = name
    this.age = age
    if(typeof (this.sayName) !== "function"){
		Person.prototype.sayName: function() {
        	alert(this.name);
        }
    }
}

var person1 = new Person("Beatrix", 21);//sayName不存在,向原型中添加
var person2 = new Person("zzz",22)//sayName已经存在,不会再向原型添加
  • 优点:

通过判断构造函数的原型中是否已经定义了共享的方法或属性,来决定是否需要初始化原型,只在初次调用构造函数时执行 if 语句检查的可以是初始化之后应该存在的任何属性或方法

  • 缺点:

不能使用对象字面量去重写(赋值)原型(prototype),如果在已经创建了实例的情况下重写原型,那么就会切断现有实例与新原型之间的联系。详情见下

Person.prototype = {对象字面量
	 constructor:Person,        
     sayName: function() {
     alert(this.name);
       }
  }

动态原型模式重写

function Person(name, age) {
    this.name = name
    this.age = age
    if(typeof (this.sayName) !== "function"){
		Person.prototype = {
		  constructor:Person,        
          sayName: function() {
              alert(this.name);
          }
        }
    }
}

var person1 = new Person("Beatrix", 21);//第一次创建实例化对象
var person2 = new Person("zzz",22);

p1.sayName();//报错:Uncaught TypeError: p1.sayName is not a function

更改执行顺序之后,p1的结果和之前相同,如下

function Person(name, age){
    this.name = name;
    this.age = age;
}
 
var p1 = new Person('Beatrix', 21);//先于重写原型创建
console.log(Person.prototype);//{constructor: ƒ}
 
Person.prototype = {
    constructor: Person,
    sayName: function(){
        console.log(this.name);
    }
}
var p2 = new Person('ZZZZ', 20 );//后于重写原型创建
console.log(Person.prototype);//{constructor: ƒ, sayName: ƒ}
 
 p1.sayName();//Uncaught TypeError: p1.sayName is not a function

  • 主要原因

p1 和 p2二者区别就在于通过new关键字创建对象的先后顺序,是先于重写原型创建,还是后于重写原型创建。

通过new创建对象时,p1的__proto__属性是会指向Person.prototype(原型对象) 而重写原型之后,创建了一个新的原型对象,原本的Person.prototype指针指向了新的原型对象,而旧的原型对象被只被p1.__proto__引用着,因为旧的原型对象上没有sayName方法 所以会报错

  • 总结:

第一次创建实例对象时(p1 =new Person()),先new,然后执行构造函数,重写原型 ,那么此时实例的__proto__指向的还是原来的原型,不是重写后的原型。第二次创建实例,因为新原型对象已经创建好,所以实例的__proto__指向的就是重写的这个原型。使用【原型模式】添加属性的方式操作的一直是同一个原型,所以也就不存在先后的问题。

八、寄生构造函数模式

  • 创建一个函数,这个函数作用仅仅是用来封装创建对象的代码,返回新创建的对象,基本和工厂模式相同,只是创建对象时是通过new来实现的
 function CreateObj(name, age) {
    var obj = new Object();
    obj.name = name;
    obj.age = age;
    obj.sayName = function(name) {
        console.log(`I am` + age);
     };
    return obj;//重写
 }
 var obj1 = new CreateObj("Beatrix", 21)
  • 注意: 使用new创建对象时,当构造函数不返回值的情况下,默认会返回新创建的对象,因此在构造函数末尾添加一个return 重写构造函数的返回值
  • 优点:

它主要是基于一个已经有的类型,在此基础上对实例化对象进行扩展,这样不用修改原来的构造函数,也能达到扩展对象的目的,以上就是具有额外属性的一个特殊对象

  • 缺点:

和工厂模式相同,不能使用instanceof来确定对象类型