工厂模式
创建过程可以分为三步:
1、在函数里面显式地创建一个对象
2、在这个对象上添加属性和方法
3、返回这个对象
function personFn(name, sex, age) {
const obj = new Object()
obj.name = name
obj.sex = sex
obj.age = age
obj.sayName = function() {
console.log(this.name)
}
return obj
}
const p1 = personFn('js', '未知', '25')
优点: 解决了创建多个相似对象的问题。即相似的对象可以通过personFn这个方法来创建,不用一个个地通过Object构造函数或者对象字面量来创建,降低代码冗余。
缺点: 没有解决对象识别的问题。
构造函数模式
创建过程:直接将属性和方法赋给this
注:为区分构造函数和普通函数,构造函数首字母大写
function Person (name, sex, age) {
this.name = name
this.sex = sex
this.age = age
this.sayName = function() {
console.log(this.name)
}
}
const p1 = new Person('js', '未知', '25')
和工厂模式相比,构造函数的区别在于:
1、不用显式的创建对象
2、直接将属性和方法赋给了this
3、不用return
而构造函数模式胜过工厂模式的地方就在于它解决了对象识别的问题。可以将它的实例标记为特定的类型。比如以上的p1,它的类型是Person(当然也是Object)。
构造函数和普通函数真正的区别在于,构造函数的实例需要通过new来创建。
function Person (name, sex, age) {
this.name = name
this.sex = sex
this.age = age
this.sayName = function () {
console.log(this.name)
}
}
const p1 = Person('windy-boy', 'male', '18')
const p2 = new Person('windy-boy', 'male', '18')
console.log(p1) // undefined
console.log(p2) // Person {name: "windy-boy", sex: "male", age: "18", sayName: ƒ}
例子p1是当做普通函数来调用的,返回的是undefined。因为这里是在全局调用,所以这里的this指向的是全局即window。
这和其他普通函数是一样的。而p2是通过new来调用的。返回的是一个对象。这里我们就可以看出来,new会返回一个对象;并且会为这个对象添加属性和方法,要做到这一步,其实就将this指向了这个对象。那么,是不是任何函数通过new来调用,都会这样做呢?我们来随便写一个函数
function demo () {
}
const d = new demo()
console.log(d) // {}
返回的是一个空对象。再往函数里塞点东西
function demo () {
this.name = 'windy-boy',
this.sayName = function () {
console.log(this.name)
}
}
const d = new demo()
console.log(d) // {name: "windy-boy", sayName: ƒ}
对象就有了属性。
new要返回一个对象,必然一开始会新建一个对象,所以我们总结一下new的作用:
1、新建一个对象
2、将this指向新对象,即函数的作用域赋给了新对象
3、为新对象添加属性
4、返回新对象(如果函数内显示地return了一个值,那么该值将代替新对象返回)
所以,真正区分构造函数和普通函数的是new操作符
套用红宝书的话:
任何函数,只要通过new操作符来调用,那它就可以作为构造函数;而任何函数,如果不通过new操作符来调用,那它跟普通函数也不会有什么两样。
缺点:
构造函数里面的方法在实例化的时候都要重新创建一遍(方法也是一个对象,因此每定义一个函数,也就实例化了一个对象),所以,不同实例上的同名函数是不相同的。
解决方法:
在全局定义函数,在构造函数内部将某个属性等于该全局函数。
这样做会带来新的问题:
1、在全局作用域中定义的函数实际上只能被某个对象调用。这句话怎么理解呢?js中只有全局作用域和函数作用域(es6引入块级作用域),为避免命名冲突,我们会用闭包来模拟块级作用域。也就是说,当我们把函数放到全局的时候,我们就应该要确定,它不会产生冲突。如何做到不产生冲突 ?那就是有且只有一个对象在调用它。
2、如果对象需要定义很多个方法,也就需要定义很多个全局函数,那我们的这个自定义的引用类型就没有了封装性。