如题,我们应如何下手呢?
举个例子,如何创建水浒中的好汉呢?
一、最简单的办法,就是用对象字面量一个一个地直接创建。
如下代码所示:
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
操作符调用了,那么它会执行如下操作:
- 在内存中创建一个新的对象(空对象)。
- 这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性。
- 构造函数内部的
this
,会指向创建出来的新对象。 - 执行函数的内部代码(函数体代码)。
- 如果构造函数没有返回非空对象,则返回创建出来的新对象。
四、构造函数和普通函数的关系
一个普通的函数被new
操作符调用了,那么这个函数就称之为是一个构造函数,所以构造函数就是个普通函数。
那在被 new
操作符调用前,就没法判断出它是普通函数还是构造函数了吗?是的,普通函数和构造函数都是用 function
关键字声明的,是一样的东西,这是JavaScript设计上的缺陷,在设计之初就没有做区分。
如果硬要区分,只能按照约定俗成,构造函数命名使用大驼峰,普通函数命名使用小驼峰。
在ES6出来后,有了类的概念,使用了 class
关键字,它和构造函数是一回事儿,这样就跟普通函数区分开了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。