Revan1i

vuePress-theme-reco revan1i    2019 - 2020
Revan1i Revan1i

Choose mode

  • dark
  • auto
  • light
Home
TimeLine
Category
  • javascript
  • css
  • react
  • js
  • how
  • math
  • regexp
  • algorithm
  • feeling
Tag
Contact
  • GitHub
author-avatar

revan1i

25

文章

20

标签

Home
TimeLine
Category
  • javascript
  • css
  • react
  • js
  • how
  • math
  • regexp
  • algorithm
  • feeling
Tag
Contact
  • GitHub

Javascript基础之创建对象的多种方式及优缺点

vuePress-theme-reco revan1i    2019 - 2020

Javascript基础之创建对象的多种方式及优缺点


revan1i 2019-11-26 09:01:17 javascript base

# 前言

摘自《JavaScript高级程序设计》第三版 6.2 创建对象章节

# 工厂模式

工厂模式定义一个用于创建对象的接口。考虑到在 ECMAScript 中无法创建类,开发人员就发明了一种函数,用函数来封装以特定的接口创建 对象的细节。

function createPerson(name) {
  var o = new Object();
  o.name = name;
  o.sayName = function() {
    alert(this.name);
  }
  return o;
}

var person1 = createPerson('foo');
var person2 = createPerson('bar');
console.log(person1.__proto__ === person2.__proto__);  // true, 实例的__proto__指向原型
1
2
3
4
5
6
7
8
9
10
11
12

缺点:对象无法识别,因为所有实例都指向同一个原型。

# 构造函数模式

function Person(name) {
  this.name = name;
  this.sayName = function() {
    console.log(this.name)
  }
}

var person1 = new Person('foo');
console.log(person1.constructor == Person);  // true 可以识别为构造函数Person
console.log(person1 instanceof Person);      // true
1
2
3
4
5
6
7
8
9
10

优点:实例可以识别为一个特定的类型。

缺点:每次创建实例时,构造函数的方法都被创建一次。

# 原型模式

function Person(name) {

}

Person.prototype.name = 'foo';
Person.ptototype.getName = function() {
  console.log(this.name);  // foo
}

var person1 = new Person();
1
2
3
4
5
6
7
8
9
10

优点:方法不会重新创建。

缺点:

  1. 所有属性和方法都共享;
  2. 不能初始化参数。

# 原型模式优化v1

function Person(name) {

}

Person.prototype = {
  name: 'foo',
  sayName: function() {
    console.log(this.name)
  }
}

var person1 = new Person();
1
2
3
4
5
6
7
8
9
10
11
12

优点:减少了不必要的输入,封装性好一点;

缺点:重写了原型(Person.prototype设置为等于一个以对象字面量形式创建的新对象),丢失了 constructor 属性。

# 原型模式优化v2

function Person(name) {

}

Person.prototype = {
  name: 'foo',
  sayName: function() {
    console.log(this.name)
  }
}

var person1 = new Person();
1
2
3
4
5
6
7
8
9
10
11
12

优点:减少了不必要的输入,封装性好一点;

缺点:重写了原型(Person.prototype设置为等于一个以对象字面量形式创建的新对象, constructor 属性指向 Object 构造函数),丢失了 constructor 属性。

console.log(person1.constructor === Person) // false
console.log(person1.constructor === Object) // true
1
2

# 原型模式优化v3

将 constructor 设置回适当的值

这种方式重设 constructor 属性会导致它的 [[Enumerable]] 特性被设置为true。默认情况下,原生的 constructor 属性是不可枚举的,可以用 Object.defineProperty() 修改。

function Person(name) {

}

Person.prototype = {
  constructor: Person,
  name: 'foo',
  sayName: function() {
    console.log(this.name)
  }
}

// 重设构造函数
Object.defineProperty(Person.prototype, 'constructor', {
  enumerable: false,
  value: Person
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

优点:实例可以通过 constructor 属性找到所有构造函数。

缺点:还是原型模式的缺点。

# 组合模式

组合使用构造函数模式与原型模式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。

function Person(name) {
  this.name = name;
}

Person.prototype = {
  constructor: Person,
  sayName: function() {
    console.log(this.name);
  }
}

var person1 = new Person('foo');
1
2
3
4
5
6
7
8
9
10
11
12

优点:很好的分开私有和共享,使用最广泛的方式。

缺点:有人还是希望能写到一块。

# 动态原型模式

function Person(name) {
  this.name = name;
  if (typeof this.getName != 'function') {
    Person.prototype.sayName = function () {
      console.log(this.name)
    }
  }
}

var person1 = new Person('foo');
person1.sayName();   // foo
1
2
3
4
5
6
7
8
9
10
11

优点:原型方法只在构造函数初次调用时才会执行。 缺点:不能使用对象字面量重写原型,因为在已经创建了实例情况下重写原型,会切断现有实例与新原型之间的联系。

如果想用字面量重写原型,可以在重写原型后 return 新的实例出来。











 







function Person(name) {
  this.name = name;
  if (typeof this.getName != "function") {
    Person.prototype = {
      constructor: Person,
      getName: function () {
        console.log(this.name);
      }
    }

    return new Person(name);
  }
}

var person1 = new Person('kevin');

person1.getName(); // kevin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 寄生构造函数模式

function Person(name) {

  var o = new Object();
  o.name = name;
  o.sayName = function() {
    console.log(this.name)
  }

  return o
}

var person1 = new Person('foo');
console.log(person1 instanceof Person);   // false
console.log(person1 instanceof Object);   // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14

寄生构造函数模式跟工厂模式很类似,多了一个 return 重写调用构造函数时的返回值。但是无法用 instanceof 来确定对象类型。

这样方法可以在特殊情况下使用。比如我们想创建一个具有额外方法的特殊数组,但是又不想直接修改Array构造函数,我们可以这样写:

function SpecialArray() {
  var values = new Array();

  values.push.apply(value, arguments);

  values.toPipedString = function () {
    return this.join("|");
  };
  
  return values;
}

var colors = new SpecialArray('red', 'blue', 'green');
var colors2 = SpecialArray('red2', 'blue2', 'green2');


console.log(colors);
console.log(colors.toPipedString()); // red|blue|green

console.log(colors2);
console.log(colors2.toPipedString()); // red2|blue2|green2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 稳妥构造函数模式

function Person(name) {
  var o = new Object();
  o.sayName = function() {
    console.log(name)
  }
  return o;
}

var person1 = Person('foo');

person1.sayName(); // foo
person1.name = 'bar';
person1.sayName(); // foo
console.log(person1.name);  // bar
1
2
3
4
5
6
7
8
9
10
11
12
13
14

稳妥方式,指的是没有公共属性,而且其方法也不引用 this 的对象。 与寄生构造函数模式不同的是:

  1. 新创建的实例方法不引用 this
  2. 不使用 new 操作符调用构造函数

稳妥模式适合在一些安全的环境中,但和工厂模式一样,也是无法识别对象所属类型。