头图

如题,我们应如何下手呢?

举个例子,如何创建水浒中的好汉呢?

一、最简单的办法,就是用对象字面量一个一个地直接创建。

如下代码所示:

var hero1 = {
  name: '宋江',
  nickname: '呼保义',
  star: '天魁星',
  speak: function() {
    console.log('替天行道')
  }
}
var hero2 = {
  name: '卢俊义',
  nickname: '玉麒麟',
  star: '天罡星',
  speak: function() {
    console.log('替天行道')
  }
}
var hero3 = {
  name: '吴用',
  nickname: '智多星',
  star: '天机星',
  speak: function() {
    console.log('替天行道')
  }
}

console.log(hero1)
console.log(hero2)
console.log(hero3)

可以看出,每个好汉的区别只是 name​ 、 nickname​ 、 star​ 的值不同,但是按照上面的操作会产生很多重复的代码。

而且,扩展性也很差,如果要给每个好汉再添加一个 age​ 属性,就需要对每个字面量对象进行修改。

能不能把这些重复的操作进行封装,将不同的值做为参数传递给封装的代码块呢?

二、使用工厂模式封装。

对上面的代码进行修改,如下所示:

function createHero(name, nickname, star) {
  var hero = {}

  hero.name = name
  hero.nickname = nickname
  hero.star = star
  hero.speak = function() {
    console.log('替天行道')
  }

  return hero
}

var hero1 = createHero('宋江', '呼保义', '天魁星')
var hero2 = createHero('卢俊义', '玉麒麟', '天罡星')
var hero3 = createHero('吴用', '智多星', '天机星')

console.log(hero1)
console.log(hero2)
console.log(hero3)

思路是这样的,创建一个 createHero​ 函数,在函数内部,有三步操作:

  • 首先创建一个名为 hero​ 的空对象。
  • 执行函数的内部代码。
  • 最后将此 hero​ 对象返回。

执行 createHero​ 函数,根据不同的好汉数据传入对应的实参,最终也实现了创建多个相似对象的功能。

而且它还具有一定的可扩展性,如果再添加一个 age​ 属性,也是很方便的。

这个 createHero​ 函数就像一个工厂,你给它传递不同的实参,就像是给这个工厂提供不同的原材料,最后这个工厂会创造出一个对应的产品,并返回给你。像这样的函数,我们可以称之为工厂函数。

工厂函数有一个不好的地方,就是创建出来的对象,使用 typeof​ 来识别类型,返回的都是 'object'​ ,不能准确地反映出对象的实际类型

三、使用构造函数。

上述的工厂模式适用多种编程语言,而JavaScript已经默认提供了类似工厂模式,且符合面向对象思维方式的一种创建对象的规则。

把上面的 createHero​ 函数改造一下,按照面向对象的思想,在此函数中不用创建一个空对象,而函数中的 this​ 就代表一个对象。

所以初步改造后的代码如下:

function createHero(name, nickname, star) {
  this.name = name
  this.nickname = nickname
  this.star = star
  this.speak = function() {
    console.log('替天行道')
  }

  return this
}

var hero1 = createHero('宋江', '呼保义', '天魁星')

console.log(hero1)

但是,经过改造并没有符合我们的预期,函数执行后返回的是一个 Window​ 对象,因为这是用默认的方式来执行函数,那 this​ 就是指向了 Window​ 对象。

所以,我们把没有意义的 return​ 语句删除掉:

function createHero(name, nickname, star) {
  this.name = name
  this.nickname = nickname
  this.star = star
  this.speak = function() {
    console.log('替天行道')
  }
}

var hero1 = createHero('宋江', '呼保义', '天魁星')

console.log(hero1)

这样改造后,跟工厂函数相比,我们漏掉了两个重要的环节:

  • 在函数开始创建一个空对象。
  • 在函数结尾把此空对象返回。

那就是说,我们只需要将 this​ 指向一个新对象,最后再把它返回出去,就能符合我们的设想,类似这样:

function createHero(name, nickname, star) {
  this = {}
  this.name = name
  this.nickname = nickname
  this.star = star

  return this
}

var hero1 = createHero('宋江', '呼保义', '天魁星')

console.log(hero1)

但是,这样写是有语法问题的, this​ 是不能指向一个新对象的,运行就会报错。

🤔如何解决呢?

😇 这个时候只需要在函数调用的前面加上 new 操作符,就可以解决了。

同时,按照约定俗成, new​ 操作符后边的函数名称应该是名词,且遵循大驼峰的命名规则。

所以,改造后的最终代码如下:

function Hero(name, nickname, star) {
  this.name = name
  this.nickname = nickname
  this.star = star
  this.speak = function() {
    console.log('替天行道')
  }
}

var hero1 = new Hero('宋江', '呼保义', '天魁星')

console.log(hero1)

为什么会这样呢?根据JavaScript规范规定,如果一个函数被 new​ 操作符调用了,那么它会执行如下操作:

  1. 在内存中创建一个新的对象(空对象)。
  2. 这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性。
  3. 构造函数内部的 this​ ,会指向创建出来的新对象。
  4. 执行函数的内部代码(函数体代码)。
  5. 如果构造函数没有返回非空对象,则返回创建出来的新对象。

四、构造函数和普通函数的关系

一个普通的函数被new​ 操作符调用了,那么这个函数就称之为是一个构造函数,所以构造函数就是个普通函数

那在被 new​ 操作符调用前,就没法判断出它是普通函数还是构造函数了吗?是的,普通函数和构造函数都是用 function​ 关键字声明的,是一样的东西,这是JavaScript设计上的缺陷,在设计之初就没有做区分

如果硬要区分,只能按照约定俗成,构造函数命名使用大驼峰,普通函数命名使用小驼峰。

在ES6出来后,有了类的概念,使用了 class​ 关键字,它和构造函数是一回事儿,这样就跟普通函数区分开了。


BigDipper
17 声望0 粉丝