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