动态原型模式

以构造函数模式组合原型模式在目前看来已经很完美了,硬要挑出它的缺点的话就是封装性差了点,动态原型模式正是致力于解决这个问题的一个方案:

function Person(name, age, sex){
	this.name = name
	this.age = age
	this.sex = sex
	Person.prototype.sleep = function(){
		alert(this.name + '睡觉了')
	}
}

将修改原型属性的代码一块写进构造函数里面。但是上面的代码还有一个问题,如果每创建出一个实例,那么就会为 Person.prototype.sleep 重新赋值,这是完全没有必要的,因此下面对此做出了一点修改:

function Person(name, age, sex){
	this.name = name
	this.age = age
	this.sex = sex
	if (typeof this.sleep !== 'function'){
		Person.prototype.sleep = function(){
			alert(this.name + '睡觉了')
		}
	}
}

typeof this.sleep !== 'function' 这句判断只会在第一次创建实例的时候为真。由于在创建第一次实例我们就为它的原型对象的属性 sleep 赋值了一个方法,所以在第二次创建新的实例的时候,这个 sleep 的类型自然为 function,判断也就不成立,也就不会再次为原型对象重复无意义的赋值操作。

那么能否再次简化代码,在内部使用字面量的方法重写 Person 的原型呢?

function Person(name, age, sex) {
	this.name = name
	this.age = age
	this.sex = sex
	if (typeof this.sleep !== 'function') {
		Person.prototype = {
			constructor: this,
			sleep: function() {
				alert(this.name + '睡觉了')
			}
		}
	}
}

上面的代码看似没什么问题,实则有个致命的错误:

const person1 = new Person('小明', 22, '男')
const person2 = new Person('小红', 22, '女')
console.log(person1.sleep)		// undefined
console.log(person2.sleep)		// f()

因为在第一次创建实例的时候,内部的 if 语句在进行它的第一次判断时,第一次创建的实例的原始的原型对象(在创建Person时自动创建的原型对象)就已经存在,所以 person1 内部的原型对象的指针指向的原型对象仍旧是原始的原型对象,在第二次创建出实例 person2 的时候,它的原型指针指向的才是 if 语句中我们重写的新的原型对象。这也就造成了在 person1 访问不到原型对象的属性 sleep,而 person2 却能够正常访问到属性 sleep。