# 创建对象

# 工厂模式

以函数来封装特定接口实现创建对象。可以多次调用返回一些属性的对象,但是无法识别对象类型。

function createProgram(name, desc) {
  let obj = {}
  obj.name = name
  obj.desc = desc
  obj.sayHello = function() {
    console.log('hello', this.name)
  }
  return obj
}

let v = createProgram('Vue', '构建用户界面的渐进式框架')
1
2
3
4
5
6
7
8
9
10
11

# 构造函数模式

function Program(name, desc) {
  this.name = name
  this.desc = desc
  this.sayHello = function() {
    console.log('hello', this.name)
  }
}

let v = new Program('Vue', '构建用户界面的渐进式框架')
1
2
3
4
5
6
7
8
9

要创建Program的实例,需要使用new操作符,可以参考自定义实现new

实例的constructor属性指向构造函数,即

v.constructor = Program
1

此外,创建的对象既是Object的实例,也是Program的实例,通过instanceof判断

v instanceof Object // true
v instanceof Program // true
1
2

上文说到构造函数需要使用new操作符创建实例,如果用作普通函数,属性和方法会被添加到全局作用域中;也可以改变函数作用域到其他对象

Program('React', '用于构建用户界面的js库')
window.name // React

let o = {}
Program.call(o, 'React', '用于构建用户界面的js库')
o.sayHello() // hello React
1
2
3
4
5
6

# 原型模式

每个函数都有一个prototype指针,指向对象,这个对象包含所有实例共享的属性和方法。

function Program() {}
Program.prototype.name = 'Vue'
Program.prototype.desc = '构建用户界面的渐进式框架'
Program.prototype.sayHello = function() {
  console.log('hello', this.name)
}
let v = new Program()
let v1 = new Program()
v.sayHello == v1.sayHello // true
1
2
3
4
5
6
7
8
9

使用构造函数创建的对象具有相同的属性和方法,本质是原型链查找过程,即实例有值,就返回值 ,否则继续搜索原型。 Object.getPrototypeOf()可以返回实例的原型,即

Object.getPrototypeOf(v) // Program.prototype
1

简单的原型语法可以通过一个对象字面量重写原型对象:



 







function Program() {}
Program.prototype = {
  constructor: Program,
  name: 'Vue',
  desc: '构建用户界面的渐进式框架',
  sayHello: function() {
    console.log('hello', this.name)
  }
}
1
2
3
4
5
6
7
8
9

注意,每创建一个函数,就会同时创建它的 prototype 对象,这个对象也会自动获得 constructor 属性。而我们在这里使用的语法,本质上完全重写了默认的 prototype 对象,因此 constructor 属性也就变成了新对象的 constructor 属性(指向 Object 构造函数),不再指向 Program 函数,所以需要手动赋值 constructor 属性为 Program ,确保该属性能访问到合适的值。

此外constructor 属性的[[Enumerable]]特性被设置为 true,可以通过Object.defineProperty()改写

Object.defineProperty(Program.prototype, "constructor", {
  enumerable: false,
  value: Program
})
1
2
3
4

# 组合原型模式和构造函数模式 推荐

构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。支持向构造函数传递参数。

function Program(name, desc) {
  this.name = name
  this.desc = desc
  this.attrs = ['directives', 'mixins']
}
Program.prototype = {
  constructor: Program,
  sayHello: function () {
    console.log('hello', this.name)
  }
}

let v = new Program('Vue', '构建用户界面的渐进式框架')
let r = new Program('React', '用于构建用户界面的js库')

v.attrs.push('template')
v.attrs == r.attrs // false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 动态原型模式

通过在构造函数中初始化原型,动态原型模式可以把独立的构造函数和原型封装起来。

function Program(name, desc) {
  this.name = name
  this.desc = desc
  if (typeof this.sayHello !== 'function') {
    Program.prototype.sayHello = function () {
      console.log('hello', this.name)
    }
  }
}
1
2
3
4
5
6
7
8
9

只需要判断一个属性即可,代码在初次调用会执行,然后原型已经初始化。

# 寄生构造函数模式

function Program(name, desc) {
  let obj = {}
  obj.name = name
  obj.desc = desc
  obj.sayHello = function() {
    console.log('hello', this.name)
  }
  return obj
}
1
2
3
4
5
6
7
8
9

除了使用new操作符来初始化实例,基本和工厂模式创建的对象一致。

返回的对象与构造函数或者与构造函数的原型属性之间没有关系,构造函数返回的对象与在构造函数外部创建的对象没有什么不同

# 稳妥构造函数模式

稳妥对象指的是没有公共属性,而且其方法也不引用 this 的对象。
稳妥构造函数遵循与寄生构造函数类似的模式,但有两点不同:一是新创建对象的实例方法不引用 this;二是不使用 new 操作符调用构造函数。






 





function Program(name, desc) {
  let obj = {}
  obj.sayHello = function() {
    console.log('hello', name)
  }
  return obj
}

let v = new Program('Vue', '构建用户界面的渐进式框架')
v.sayHello()
1
2
3
4
5
6
7
8
9
10

上述代码变量v保存的是一个稳妥对象,除了调用sayHello方法,没有别的方式可以访问数据成员。