本文参考自《javaScript高级程序设计》第三版,俗称“红宝书”。

graph TB
A[创建对象的方式]  
A --> B(字面量)
A --> C(工厂模式)
A --> D(构造函数模式) --> E(new Object)
 
A --> F(原型模式)
style C fill:#2ff,fill-opacity:0.1,stroke:#faa,stroke-width:4px,color:blue
style D fill:#2ff,fill-opacity:0.1,stroke:#faa,stroke-width:4px,color:blue
style F fill:#2ff,fill-opacity:0.1,stroke:#faa,stroke-width:4px,color:blue

下面重点比较工厂模式、构造函数模式、原型模式。

工厂模式

定 义: 工厂模式是软件工程领域的一种设计模式。
优 点: 可多次调用函数, 可解决创建多个相似对象的问题。
缺 点: 无法解决对象识别问题(无法知道一个对象的类型)。

function Student(name,age,hobby){
  let p = new Object(); // 显示地创建一个对象
  p.name = name;
  p.age = age; 
  p.hobby = function(){
      console.log(`我的爱好是${hobby}`);
  }
  return p;
}

let studentA = Student('xiaoMing',10,'drawing')
let studentB = Student('xiaoHua',12,'dance')

构造函数模式

使用构造函数重写上面的例子:

function Student(name,age,hobby){
   this.name = name;
   this.age = age;
   this.hobby = function(){
        console.log(`我的爱好是${hobby}`);
   }
}
let studentA = new Student('xiaoMing',10,'drawing');
let studentB = new Student('xiaoHua',12,'dance');

/***************** 检测实例类型 *****************/ 
console.log(studentA instanceof Student); // true
console.log(studentB instanceof Student); // true

console.log(studentA instanceof Object); // true
console.log(studentB instanceof Object); // true

/************ 不同实例上的同名函数是不相等的 ************/ 
console.log(studentA.hobby === studentB.hobby); // false

构造函数的函数名第一个字母始终大写。
构造函数本身也是函数,是可以用来创建对象的函数。
new操作符经历步骤:

  1. 创建一个对象。
  2. 将构造函数的作用域赋给新对象(所以,this指向新对象)。
  3. 为新对象添加属性(即:执行构造函数中代码)。
  4. 返回新对象。

优 点: 可以将它的实例标识为一种特定的类型。
缺 点: 每个方法都要在实例上创建一遍。不同实例上的同名函数是不相等的。
如果将构造函数内部的 自定义函数 定义在构造函数外面的作用域中,将导致这些自定义函数的封装性极差。

原型模式

定 义: 每个函数都有一个prototype(原型)属性,该属性是一个指向特定对象(函数的原型对象)的指针。
优 点: 所有对象实例可以共享原型对象的属性和方法。

function Student(){}
  Student.prototype.name = 'Jack';
  Student.prototype.age = 12;
  Student.prototype.hobby =  function (){
    return this.name;
  };
  
let studentD = new Student();
let studentE = new Student();

/************** 实例共享原型对象的方法 **************/
console.log(studentD.hobby === studentE.hobby); // true

1. 原型对象的理解

定 义: 只要函数一旦创建,就会根据特定的规则为该函数创建一个prototype属性,该属性
指向函数的原型对象。


所有原型对象 会默认只获得一个 constructor (构造函数)属性(指针): 该属性指向 prototype属性所在函数; 其他方法则从Object继承而来。


构造函数constructor : 当调用构造函数创建一个新实例后,该实例内部包含一个指针(指向构造函数的原型对象)。ECMA-262第5版定义这个指针为[[prototype]], 但浏览器支持__proto__来访问。


isPrototypeOf( ) 检测一个对象是否存在于另一个对象的原型链中。

console.log( Student.prototype.isPrototypeOf(studentE) ); // true
console.log( Student.prototype.isPrototypeOf(studentD) ); // true

Object.getPrototypeOf( ) 取得一个对象的原型

console.log( Object.getPrototypeOf(studentD) );

如下图所示:

图片.png


对象属性的搜索
当访问一个对象的属性时: 首先会在该实例对象上面搜索指定的属性名,
如果找到了,则返回该属性值; 否则 就会在该实例对象的原型对象上继续搜索,找到了就
返回该属性值, 没找到返回undefined 。


实例属性 与 原型对象属性的联系
当为实例对象添加一个属性时, 那么新增的这个属性会屏蔽原型对象中的同名属性,
此时在实例对象中无法访问原型对象中的同名属性。在实例对象中给这个新增的属性设置任何值都不会影响到原型对象中同名属性。
如果使用delete在实例对象中删除一个属性后,那么在原型对象中可以重新访此 同名属性。


hasOwnProperty( ) 检测一个属性是否存在实例对象中
下面代码举例说明

function Student(){  }
    
Student.prototype.name = 'Jack';
Student.prototype.age = 12;
Student.prototype.hobby =  function (){
    return this.name;
};

let studentSth = new Student(); // 创建一个实例

studentSth.grade = 90; // 在实例对象中添加一个属性grade 

 /*** 实例studentSth对象 中存在属性 grade ***/
console.log(studentSth .hasOwnProperty('grade')); // true

 /* 实例studentSth对象 中不存在属性 name*/
console.log(studentSth.hasOwnProperty('name'));  // false

2. 原型对象与 in 操作符

for(let attribute in studentSth){
    console.log(attribute);
}

/** 依次打印出该实例对象及实例原型对象中的属性名称 **/
 //  grade
 //  name
 //  age
 //  hobby

Obeject.keys( )

/*** 在实例上调用 ****/
console.log(Object.keys(studentSth)); // ["grade"]

/**** 在实例的原型对象上调用 ****/
console.log(Object.keys(studentSth.__proto__)); //  ["name", "age", "hobby"]

Object.getOwnPropertyNames( )

/*** 在实例上调用 ****/
console.log(Object.getOwnPropertyNames(studentSth)); // ["grade"]

/**** 在实例的原型对象上调用 ****/
console.log(Object.getOwnPropertyNames(studentSth.__proto__));
 // ["constructor", "name", "age", "hobby"]  
 // constructor是不可枚举属性

3. 原型的动态性

随时可以为原型对象添加属性和方法,并且,
修改能立即在所有实例对象中反应出来。

4. 原生对象的原型

通过原生对象的原型,可以获取默认方法的引用,也可自定义新的方法。

String.prototype.say = function (){
   console.log('修改了原生对象的原型属性');
}
let str = ' this is a';
str.say() // 修改了原生对象的原型属性

5. 原型对象的问题

问题一: 省略了为构造函数传递初始化参数。
问题二: 属性共享的特性。

6. 构造函数模式 与 原型模式 组合使用


简单即可
6 声望2 粉丝