创建对象的方式

282 阅读4分钟

1.直接通过字面量的方式创建

let obj = {
    name: 'john',
    age: 24,
    number: 18
  }
我们经常会使用这种方式创建对象

2.使用Object.create()来创建对象

该方法有两个参数:Object.create(proto, [propertiesObject])。第一个参数proto来提供新创建的对象的原型对象,第二个参数(可选)默认为undefined。如果没有指定为undefined,那么这个参数对象包含了要添加到新创建的那个对象上的可枚举属性以及对该可枚举属性的属性描述符。

如果propertiesObject参数是null或非原始包装对象,则抛出一个TypeError异常。

 let o = {
    name: 'Tom',
    address: 'china'
  };
 let obj1 = Object.create(o);
 console.log(obj1);
 
 //以下是为新创建的对象添加了可枚举属性number
 let obj2 = Object.create(o,{
   number: {
     value: 18204,
     writable: true,
     enumerable: true,
     configurable: true
   }
 });
 console.log(obj2);

3.工厂模式创建对象

工厂模式虽然解决了多个相似对象之间的流水线似的创建问题,但是并没有解决对象识别的问题。对象中的constructor最初是用来标识对象类型的(或通过instanceof判断)。下面例子中对象识别的问题是函数f不是obj1和obj2的构造函数,obj1、obj2的原型也不是f.prototype

  function f(name,age) {
    var obj = new Object();
    obj.name = name;
    obj.age = age;
    obj.sayName = function () {
      console.log(this.name);
    };
    return obj;
  }
  let obj1 = f('lb',23);
  let obj2 = f('lb',22);
  console.log(obj1.constructor === f); //false
  console.log(obj2.constructor === f); //false
  //或者用instanceof检测
  console.log(obj1 instanceof f);  //false
  console.log(obj2 instanceof f);  //false

4.使用构造函数来创建对象

  //为了解决对象无法被识别的问题,现在使用构造函数来创建对象
  function Create(name,age) {
    this.name = name;
    this.age = age;
    this.sayName = function () {
      console.log(this.name);
    }
  }
  let c1 = new Create('lb',23);
  let c2 = new Create('fjq',20);
  console.log(c1 instanceof Create); //true
  console.log(c2 instanceof Create); //true
  console.log(c1.sayName === c2.sayName); //false

可以看到上面c1和c2都是Create的实例对象,这也就解决了对象识别的问题。但是构造函数也不是完全没有缺陷的,使用构造函数创建对象,每一个方法都会在实例对象上重新创建一遍。由于方法(函数)也是对象,这样就会造成大量的内存浪费。通过将构造函数转移到构造函数外部可以解决该问题。

5.构造函数创建对象的优化

function Create(name,age) {
    this.name = name;
    this.age = age;
    this.sayName = sayName
  }
  function sayName() {
    console.log(this.name);
  }
  let c1 = new Create('lb',23);
  let c2 = new Create('fjq',20);
  console.log(c1.sayName === c2.sayName); //true

以上将sayName函数转移到构造函数外部可以看到,两个实例对象都共享了全局作用域中的sayName函数。但是新问题又出现了,在全局作用域中定义的函数只能被某个对象调用,这让全局作用域下的函数有点名不副实。其次,大量在全局作用域中定义方法,也不熟是一个好的做法。这些问题,可以通过原型模式解决。

6.原型模式创建对象

每一个函数都有prototype属性,该属性是一个指针,指向一个对象,我们将这个对象称为原型对象。在原型对象上的属性和方法所有实例对象都可以共享。因此,不必在构造函数中添加属性和方法,直接写在原型对象上即可。

function Fn() {

  }
  Fn.prototype.name = 'jack';
  Fn.prototype.age = 22;
  Fn.prototype.sayName = function () {
    console.log(this.name);
  };
  //或者直接用字面量的方式写
  // Fn.prototype = {
  //   name: 'jack',
  //   age: 22,
  //   sayName: function () {
  //     console.log(this.name);
  //   },
  //   constructor: Fn
  // }
  var fn = new Fn();
  fn.sayName(); //jack

以上通过原型模式创建的实例对象,都可以访问其原型对象上的方法和属性。而原型模式也会出现问题。首先,它省略了构造函数传递初始化参数这一环节,结果所有实例都共享了相同的属性和方法,而这正不是我们所希望的。由于原型中的属性和方法是共享的,对于包含引用类型的时候就会出问题,如下。

function Student() {

  }
  Student.prototype = {
    name: 'jack',
    age: 29,
    color: ['red','yellow'],
    constructor: Student,
    sayName () {
      console.log(this.name);
    }
  };
  let stu1 = new Student();
  let stu2 = new Student();
  stu1.color.push('orange');
  console.log(stu1.color); //["red", "yellow", "orange"]
  console.log(stu2.color); //["red", "yellow", "orange"]
 将stu1.color所引用的数组修改了,其结果也会反应在stu2.color数组上,而有时候这样正不是我们所希望的

7.组合使用构造函数模式和原型模式

构造函数模式用于定义实例的属性,原型模式用于定义共享的属性和方法。这种方式避免了构造函数创建对象的缺点,即所有的属性和方法都会在实例对象上。又避免了原型模式创建对象的缺点,即把所有的属性和方法都写在原型对象上,省略了构造函数初始化参数这一环节。

function Student(name,age) {
    this.name = name;
    this.age = age;
    this.color = ['yellow','red']
  }
  Student.prototype = {
    constructor: Student,
    sayName () {
      console.log(this.name);
    }
  };
  let s1 = new Student('jack',19);
  let s2 = new Student('john',29);
  s1.color.push('orange');
  console.log(s1.color); //["yellow", "red", "orange"]
  console.log(s2.color); //["yellow", "red"]