虽然Object构造函数或对象字符量都可以用来创建单个对象,但这些方式有个明显的缺点:使用同一个接口创建很多对象,会产生大量重复代码。人们开始使用工厂模式的一种变体。

一、工厂模式

  1. function createPerson(name) {
  2. var o = new Object();
  3. o.name = name;
  4. o.say = function() {
  5. alert(this.name);
  6. }
  7. return o;
  8. }
  9. var person1 = createPerson("zx");

缺点:
工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)。

二、构造函数模式

  1. function Person(name) {
  2. this.name = name;
  3. this.ability = function(){
  4. alert("speaking")
  5. };
  6. this.say = function() {
  7. alert(this.name)
  8. }
  9. }
  10. var person1 = new Person("zx");
  11. var person2 = new Person("harden");

优点

1.通过constructor或者instanceof可以识别对象实例的类别

在上面的例子中,person1和person2都有一个constructor(构造函数)属性,该属性指向Person

  1. alert(person1.constructor == Person); //true
  2. alert(person2.constructor == Person); //true

对象的constructor属性最初是来表示对象类型的。但是,提到检测对象类型,还是instanceof更加可靠一些

  1. alert(person1 instanceof Object); //true
  2. alert(person1 instanceof Person); //true

2.可通过new关键字来创建对象实例

  1. function Person(name) {
  2. this.name = name;
  3. this.say = function() {
  4. alert(this.name)
  5. };
  6. this.ability = function(){
  7. alert("speaking")
  8. }
  9. }
  10. var person1 = new Person("zx");//更像oo语言中创建对象实例
  11. var person2 = new Person("zr");

缺点

相同的方法重复创建

多个实例的ability方法都是实现一样的效果,但是却存储了很多次(两个对象实例的ability方法是不同的,因为存放的地址不同)
image.png

打印这两个对象的结果,以及这两个对象的函数比较结果
image.png

这便是构造函数模式的缺点,表面上好像没有没什么问题,但是实际这样做有个很大的弊端。那就是对于每一个实例对象,ability方法都是一模一样的内容,每次生成一个实例,都必须为重复的内容,多占用了一些内存,这样浪费内存,也缺乏效率。

三、原型模式

Javascript规定,每一个构造函数都有一个prototype属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。
这意味着,我们可以把那些不变的属性和方法,直接定义在prototype对象上。

  1.  function Cat(name,color){
  2.     this.name = name;
  3.     this.color = color;
  4.   }
  5.   Cat.prototype.type = "猫科动物";
  6.   Cat.prototype.eat = function(){alert("吃老鼠")};

然后生成实例

  1.   var cat1 = new Cat("大毛","黄色");
  2.   var cat2 = new Cat("二毛","黑色");
  3.   alert(cat1.type); // 猫科动物
  4.   cat1.eat(); // 吃老鼠

这时所有实例的type属性和eat()方法,其实都是同一个内存地址,指向prototype对象,因此就提高了运行效率。

  1.  alert(cat1.eat == cat2.eat); //true

proto

  • 每个class都有显示原型prototype
  • 每个实例都有隐式原型proto
  • 实例的proto指向对应class的prototype

这里如果觉得很抽象的话,不妨直接输出cat1,看看这个实例里面到底有些什么

  1. function Cat(name, color) {
  2. this.name = name;
  3. this.color = color;
  4. }
  5. Cat.prototype.type = "猫科动物";
  6. Cat.prototype.eat = function () { alert("吃老鼠") };
  7.  var cat1 = new Cat("大毛","黄色");
  8. console.log(cat1);

image.png
在cat1内部有个proto的隐藏属性,不难发现该属性里存放的东西和Cat prototype里的很相似,于是可以试着比较一下看是否真的相等。

  1. console.log(cat1.__proto__==Cat.prototype); //true

发现返回的结果是true,于是可以断定,cat1内的proto属性指向的就是Cat的原型对象!于是可以大概画出如下的结构图。

image.png
由图可知,每个实例化的cat内的proto都会指向该类的原型对象,并且这个实例化对象享有原型对象上所有的属性和方法。
而在原型对象内部,有个constructor属性是指向原构造函数,proto属性指向他父类(也就是Object)的原型对象。