【JS第22期】设计模式-原型模式

137 阅读6分钟

我们在创建函数的同时会自动创建一个prototype属性,这个属性指向一个对象,而这个对象的用途时包含可以由特定类型的所有实例共享的属性和方法。使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。如:

function Persion(name, age) {
  this.name = name;
  this.age = age;
}
Persion.prototype.sayName = function(){
  alert(this.name);
}

var p1 = new Persion('bill', 22)
p1.sayName(); // bill
var p2 = new Persion('bill2', 23)
p2.sayName(); // bill2

console.log(p1.sayName === p2.sayName) // true

在第21期中我们实例化后p1、p2后, 它们内置的方法sayName是分别指向不同的内存地址的,所以p1.sayName === p2.sayName 返回false, 而我们基于原型的方式可以实现方法的共用,减少了多次创建重复方法或属性。

理解原型对象

只要创建一个新的函数,就会根据特定规则为该函数创建一个prototype属性,这个属性所指向额度对象就是原型对象。原型对象会有一个默认属性contructor属性,这个属性值为一个指针,指向原型对象所在的函数(构造函数)

当调用构造函数创建一个新实例后,该实例的内部包含一个指针(内部属性),指向构造函数的原型对象。ECMA-262第5版中管这个指针叫[[Prototype]]。在大部分浏览器中都是通过__proto__来表示,而在其他宿主环境中,大多是不可见的。在此,要明确一点,这个连接存在于实例对象与构造函数的原型对象之间,而不是实例对象与构造函数之间。

可以通过isPrototypeof()方法来判断该构造函数的原型对象是否是该实例对象的原型。如:

Persion.prototype.isPrototypeof(p1) // true
Persion.prototype.isPrototypeof(p2) // true

ECMAScript 5增加一个新方法, 叫Object.getPrototypeof() ,获取指定对象的原型对象。如:

Object.getPrototypeof(p1); // {sayName: ƒ, constructor: ƒ}

多个对象实例共享原型所保存的属性和方法的基本原理

在读取某个对象的属性或方法时,都会进行搜索。搜索首先从对象实例本身开始。如果实例中找到指定的属性或方法,则返回该属性值或方法;如果没有找到,则搜索实例对象下__proto__指向的原型对象(实例化对象的构造函数的原型对象)的属性或方法,如果有,则返回该属性值或方法;如果没有,则继续查找下一层,以此类推,直到查找到最后一层Object.prototype指向的原型对象,如果扔没有查到,则返回undefined

当对象实例上添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性。如:

function Persion() {}
Persion.prototype.name = 'bill';

var p1 = new Persion()
var p2 = new Persion()
p1.name = 'bill2'
console.log(p1.name) // bill2
console.log(p2.name) // bill

通过delete 可以删除实例对象的属性。如:

delete p1.name

console.log(p1.name) // bill

通过hasOwnProperty()可以检测一个属性是否存在于实例中。如:

function Persion() {}
Persion.prototype.name = 'bill';
var p1 = new Persion()
p1.hasOwnProperty('name') // false

p1.age = 20;

p1.hasOwnProperty('age') // true

原型与in 操作符

单独使用in

in 通过对象能够访问指定属性返回true, 否则返回false。可以结合hasOwnPropery使用,判断是否是原型对象上的属性。如:

function hasPrototypeProperty(obj, name) {
  return !obj.hasOwnProperty(name) && (name in obj)
}

for-in

for-in 返回值为所有能够通过对象访问的、可枚举的属性,其中既包括实例对象中的属性,也包括原型上的属性。屏蔽了不可枚举的属性。如:

function Persion() {
  this.name = 'bill'
}
Persion.prototype.sex = 1;

Object.defineProperty(Persion.prototype, 'job',{
    value: 'seo', 
    enmuerable: false
  
})

Object.defineProperty(Persion, 'age',{
    value: 22,
    enmuerable: false
  
})

var p = new Persion();
for (var item in p) {
  console.log(item) // name sex 
}

IE早期版本的实现存在一个bug,即屏蔽不可枚举属性的实例属性不会出现在for-in循环中。如:

var o = {
  toString: function() {
    return 'bill';
  }
}
for (var item in o) {
  if (item == 'toString') {
    console.log('yes') // 在ie中不会显示
  }
}

可以通过ECMAScript 5 中 Object.keys() 获取所有可枚举的属性。如:

function Persion() {
  this.name = 'bill'
}
Persion.prototype.sex = 1;

Object.defineProperty(Persion.prototype, 'job',{
    value: 'seo', 
    enmuerable: false
  
})

Object.defineProperty(Persion, 'age',{
    value: 22,
    enmuerable: false
  
})

var p = new Persion();
Object.keys(p) // ['name']  注意只能获取 实例对象的可枚举的属性,无法获取原型对象的可枚举属性

Object.getOwnPropertyNames() 返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括Symbol值作为名称的属性)组成的数组。

function Persion() {
  this.name = 'bill'
}
Persion.prototype.sex = 1;

Object.defineProperty(Persion.prototype, 'job',{
    value: 'seo', 
    enmuerable: false
  
})

Object.defineProperty(Persion, 'age',{
    value: 22,
    enmuerable: false
  
})

Object.getOwnPropertyNames(Persion)  // ["length", "name", "arguments", "caller", "prototype", "age"]

更简单的原型语法

通过对象字面量的方式重写原型对象。如:

function Persion(){}
Persion.prototype = {
  name: 'bill',
  age: 12,
  sayName: function(){
    console.log(this.name);
  }
}

上例中,我们将Persion.prototype通过对象字面量的形式创建了一个新对象。这要注意一点就是constructor属性,在我们通过这种方式重写定义以后,constructor属性指向的对象默认为object构造函数,不再指向Persion构造函数。如果需要constructor属性指向Persion构造函数,则可以通过以下方式进行。如:

function Persion(){}
Persion.prototype = {
  constructor:Persion,
  name: 'bill',
  age: 12,
  sayName: function(){
    console.log(this.name);
  }
}

注意 上面这种方式重设constructor属性,则会导致它的enumerable属性设置为true。默认情况下,constructor属性的enumerable为false。如果像设为不可枚举的可以通过Object.defineProperty()。如:

function Persion(){}
Persion.prototype = {
  name: 'bill',
  age: 12,
  sayName: function(){
    console.log(this.name);
  }
}
Object.defineProperty(Persion, 'constructor',{
    value: Persion,
    enmuerable: false
  
})

原型的动态性

我们先创建实例对象,然后再修改原型对象,也是可以访问的。如:

var p = new Persion()
Persion.prototype.sayAge = function() {
  console.log(this.age)
}
p.sayAge() 

但如果重写原型对象,则会切断现有原型与任何之前已经存在的对象实例之间的联系,它们原来引用的原型对象的指针没有改变为新原型对象的指针。如:

function Persion() {}

var p = new Persion()

Persion.prototype = {
  name : 'bill',
  age: 11,
  sayName: function(){console.log(this.name)}
}
p.name // undefined
p.sayName() //  p.sayName is not a function

原生对象原型

我们可以为原生对象的原型对象中添加新方法,不过这种方式一般不建议这么做,因为会导致命名冲突或一些内置方法被重写等。 我们可以试着写一个实例。如:

String.prototype.startsWith = function(txt) {
  return this.indexof(txt) === 0;
}
var msg = 'hello world'
msg.startsWith('hello') // true

原型对象的问题

  • 省略了为构造函数传递初始化参数的问题
  • 原型的共享导致属性值为引用类型被修改后,所有实例化对象的会跟着修改。如:
function Persion(){}
Persion.prototype.ages = [11,22];

var p1 = new Persion();
p1.ages // [11, 22];

p2.ages // [11, 22];

p1.push(33)
p1.ages // [11, 22, 33]
p2.ages // [11, 22, 33]

如有侵权,请发邮箱至wk_daxiangmubu@163.com 或留言,本人会在第一时间与您联系,谢谢!!

关注我们
长按二维码关注我们,了解最新前端资讯