9.面向对象、面向对象-原型模式

面向对象

介绍: 到目前为止,我们解决问题时,都使用一系列的变量声明、条件、for/while语句和函数调用。这是过程型思维:先这样做,再那样做,等等。在面向对象编程中,我们从对象的角度考虑问题,而对象有状态(例如,汽车可能有油位)和行为(例如,汽车可以启动、行驶、停放和停止)。这是什么意思呢?面向对象编程让你能够解放思想,从更高的层次考虑问题。

我们可以将面向过程与面向对象看做手工烤面包和使用烤箱烤面包的差别。手工烤面包时,需要制作加热线圈,将线圈连接到电源并通电,手持面包放在离线圈很近的地方烤,然后耐心等待,等烤熟后再将线圈断电;使用烤箱时,只需将面包放入烤箱再按下按钮即可。第一种方式是过程型的,而第二种方式是面向对象的:你有一个烤箱对象,让你能够轻松地放入并烤好面包。

一、 使用对象字面量

到目前为止,我们学习的都是使用对象字面量来创建对象的。使用对象字面量
创建对象时,你逐个地指定其属性,假定我们把猫看成一个对象,它有"名字"和"颜色"两个属性。

  var Cat = {
    name : '',

    color : ''

  }

现在,我们需要根据这个原型对象的规格(schema),生成两个实例对象。

  var cat1 = {}; // 创建一个空对象

    cat1.name = "大毛"; // 按照原型对象的属性赋值

    cat1.color = "黄色";

  var cat2 = {};

    cat2.name = "二毛";

    cat2.color = "黑色";

思考:上面就是最简单的封装,对象字面量提供了一个便利方式,让你能够在代码中随时随地创建对象。但是,这样的写法有两个缺点,一是如果多生成几个实例,写起来就非常麻烦;二是实例与原型之间,没有任何办法,可以看出有什么联系。

二、 按约定创建对象

我们可以写一个函数,如果有一种让他像模具一样的对象创建方式,可帮
助创建基本结构相同的对象。这样,创建的所有对象都将看起来一样,它们包含在一个地方定义的所有属性和方法。解决代码重复的问题。

  function Cat(name,color) {

    return {

      name:name,

      color:color

    }

  }
 // 然后生成实例对象,就等于是在调用函数:

  var cat1 = Cat("大毛","黄色");

  var cat2 = Cat("二毛","黑色");

思考:这种方法的问题依然是,cat1和cat2之间没有内在的联系,不能反映出它们是同一个原型对象的实例。

三、 构造函数模式

介绍:为了解决从原型对象生成实例的问题,Javascript提供了一个构造函数(Constructor)模式。对象构造函数(简称为构造函数)让你能够更好地创建对象。构造函数犹如一个小型工厂,能够创建无数类似的对象。定义后,每当需要创建新对象时都可调用它。

要明白构造函数的工作原理,最佳方式是创建一个。我们以前面的小猫对象例,编写一个构造函数,让我们能够根据需要创建任意数量的小猫对象。若小猫对象,它包含属性name、color和weight。

  1. 先定义一个构造函数,再使用它来创建对象。所谓"构造函数",其实就是一个普通函数,但是内部使用了this变量。再对构造函数使用new运算符,就能生成实例,并且this变量会绑定在实例对象上。

比如,猫的原型对象现在可以这样写,

// 注意到我们给构造函数命名时,采用了首字母大写的方式。并非必须这样做,但人人都将此视为一种约定加以遵守。
  function Cat(name,color,weight){ // 构造函数与常规函数看起来没什么两样。这个函数的形参对应于要给每个小猫对象提供的属性。

    this.name=name; // 我们将每个形参都赋给了关键字this的属性

    this.color=color;

       this.weight = weight

  }

// 我们现在就可以生成实例对象了。

  var cat1 = new Cat("大毛","黄色",9);

  var cat2 = new Cat("二毛","黑色",12);

  alert(cat1.name); // 大毛

  alert(cat1.color); // 黄色
构造函数的工作原理

介绍:你知道了如何声明构造函数以及如何使用它来创建对象,但还需看看幕后的情况,以了解构造函数的工作原理。要明白构造函数的工作原理,关键在于了解运算符new都做了些什么。先来看看前面用来创建对象cat1的语句:

var cat1 = new Cat("大毛","黄色",9);

请看赋值运算符的右边,所有的操作都是在这里进行的。我们来跟踪一下其执行过程。

  1. new 首先创建一个新的空对象。

  2. 接下来,new 设置 this,使其指向这个新对象。

    this存储了一个引用,指向代码当前处理的空对象。

  3. 设置this后,调用函数Cat,并将 “大毛”,“黄色” 和 9作为实参传递给它。

//           "大毛" "黄色"  9
function Cat(name,color,weight) {
 this.name = name;
 this.color = color;
 this.weight = weight;
} 
  1. 接下来,执行这个函数的代码。与大多数构造函数一样,Cat给新创建的this对象的属性赋值。
this.name ="大毛"
this.color = "黄色"
this.weight = 9
  1. 最后,Cat函数执行完毕后,运算符new返回this指向新创建的对象的引用。

    注意:它会自动为你返回this,你无需在代码中显式地返回。指向新对象的引用被返回后,我们将其赋给变量cat1。

var cat = this
在构造函数中定义方法

介绍:构造函数Cat创建的小猫对象也可以设置方法。因为在构造函数中,除了能给属性指定值外,还可以定义方法。

例子:给Cat添加bark方法,扩展前面的代码。

function Cat(name,color,weight) {
 this.name = name;
 this.color = color;
 this.weight = weight;
 // 要添加bark方法,只需将一个函数(这里是一个匿名函数)赋给属性this.bark。
 this.bark = function() {
   if (this.weight > 12) {
      alert(this.name + " says meow!");
   } else {
      alert(this.name + " SAYS MEOW!");
   }
 };
}

var cat = new Cat("小花","黑色", 14);

cat.bark()

注意:与以前创建的其他所有对象一样,我们使用this来表示当前对象。

「课堂练习」

实现点咖啡的构造函数

要求:

  1. 补充完整下面的代码
  2. 我们需要方法getSize,它根据加入的咖啡量返回一个字符串
  • 咖啡量为8盎司时返回small
  • 咖啡量为12盎司时返回medium
  • 咖啡量为16盎司时返回large。
  1. 我们还需要方法toString,它返回一个字符串,指出你点的是哪种咖啡,如 You’ve ordered a small House Blend coffee. 或者 You’ve ordered a large DarkRoast coffee.
    部分代码:
function Coffee(roast, ounces) {
 this.roast = roast;
 this.ounces = ounces;

 // some  code...

}
var houseBlend = new Coffee("House Blend", 12);
console.log(houseBlend.toString());
var darkRoast = new Coffee("Dark Roast", 16);
console.log(darkRoast.toString());

理解对象实例

介绍:你无法通过观察确定JavaScript对象是哪种类型的对象,如小狗或汽车。在JavaScript中,
对象是一个动态的结构,无论对象包含哪些属性和方法,其类型都是object。

function Cat(name,color,weight) {
 this.name = name;
 this.color = color;
 this.weight = weight;
 // 要添加bark方法,只需将一个函数(这里是一个匿名函数)赋给属性this.bark。
 this.bark = function() {
   if (this.weight > 12) {
      alert(this.name + " says meow!");
   } else {
      alert(this.name + " SAYS MEOW!");
   }
 };
}

var cat = new Cat("小花","黑色", 14);

console.log(typeof cat) // "object"

function Dog(name, breed, weight) {
   this.name = name;
   this.breed = breed;
   this.weight = weight;
   this.bark = function() {
   if (this.weight > 25) {
      alert(this.name + " says Woof!");
   } else {
      alert(this.name + " says Yip!");
   }
 };
}

var dog = new Dog("阿福", "Mixed", 38);
console.log(typeof dog) // "object"

思考:如果知道对象是由哪个构造函数创建的,就能获悉一些有关它的信息。上面的代码中,每当你使用运算符new调用构造函数时,都将创建一个新的对象实例。因此,如果你使用构造函数Cat创建一个对象,这个对象就是猫;更准确地说,这个对象就是一个Cat实例。

// 这些小猫都是由同一个构造函数创建的,因此可以认为它们是相同类型的对象。
function Cat(name,color,weight){

    this.name=name;

    this.color=color;

       this.weight = weight

}

// 每个实例都有一组独特的属性值,但使用构造函数Cat创建的对象都是Cat实例。

var cat1 = new Cat("大毛","黄色",9); // Cat 实例 cat1

var cat2 = new Cat("二毛","黑色",12); // Cat 实例 cat2

概念: 说对象是某个构造函数的实例可以通过JavaScript提供的方法判断。

  1. 使用运算符instanceof来确定对象是由哪个构造函数创建的。
    // `instanceof`运算符,验证原型对象与实例对象之间的关系。
  alert(cat1 instanceof Cat); //true

  alert(cat2 instanceof Cat); //true

实际上,创建对象时,运算符new在幕后存储了一些信息,让你随时都能确定对象是由哪个构造函数创建的。运算符instanceof就是根据这些信息来确定对象是否是指定构造函数的实例。

JavaScript没有严格意义上的对象类型。如果要比较两个对象,看它们是否都是小猫或小狗,可检查它们是否是以相同的方式(即使用相同的构造函数)创建的。前面说过,小猫之所以是小猫,是因为它是由构造函数Cat创建的;而小狗之所以是小狗,是因为它是由构造函数Dog创建的。

  1. 使用构造函数创建对象后,实例对象会自动含有一个constructor属性,指向它们的构造函数。

  alert(cat1.constructor == Cat); //true

  alert(cat2.constructor == Cat); //true
实例对象也可以有独特的属性

介绍:使用构造函数来创建一致的对象,即包含相同属性和方法的对象。使用构造函数创建对象后,可对其进行修改,因为使用构造函数创建的对象是可以修改的。

// 使用构造函数Cat创建的小猫对象cat。
var cat = new Cat("大毛","黄色",9); 
cat.owner = "老李";  // 要给这个对象添加新属性,只需给这个新属性赋值即可。
delete cat.weight; // 也可使用运算符delete删除既有的属性。
cat.trust = function(person) { // 要添加新方法,只需将这个方法赋给一个新属性。
   return (person === "老李"); 
};

注意

  1. 上面的代码只修改了对象cat。如果给对象cat添加一个方法,那么只有cat对象有这个方法,其他小猫对象都不会包含这个方法。并且修改小猫示例对象,它依然是小猫实例对象。
  2. 通常,你不会使用构造函数来创建一个对象,再将它修改得面目全非,看起来根本不像是使用这个构造函数创建的。一般而言,你使用构造函数来创建相当一致的对象;但如果你希望对象更灵
    活,JavaScript也提供了这样的支持。作为代码设计人员,由你决定如何以你和同事认为合理的方式使用构造函数和对象。
对象字面量在构造函数中的应用

介绍:前面简要地比较了对象构造函数和对象字面量,并指出了对象字面量依然很有用,但你没有看到这方面的示例。下面对构造函数Car进行细微的修改,让你知道在什么情况下通过使用一些对象字面量,可让代码更整洁,使其更易于阅读和维护。

function Car(make, model, year, color, passengers, convertible, mileage) {
   this.make = make;
   this.model = model;
   this.year = year;
   this.color = color;
   this.passengers = passengers;
   this.convertible = convertible;
   this.mileage = mileage;
   this.started = false;
   this.start = function() {
      this.started = true;
   }; 
 // 其他方法
}

注意:上面的构造函数我们使用了大量的形参(总共7个)。编写调用这个构造函数的代码时,必须按正确的顺序指定所有实参。

思考:这里的问题是,构造函数Car包含大量的形参,难以阅读和维护。另外,调用这个构造函数的代码编写起来也比较困难。这看似只是小小的不便,但可能导致的bug比你想象得多;不仅如此,这些bug还常常难以发现。传递大量实参时,可使用一种适用于任何函数的常见技巧(无论它是常规函数还是构造函数)。这种技巧的工作原理如下:将所有实参都放到一个对象字面量中,再将这个对象字面量传递给函数。这将通过一个容器(对象字面量)传递所有的值,从而不必操心实参与形参的顺序问题。

例子:用一个对象字面量替代所有实参对于调用构造函数Car的代码,用一个对象字面量替代所有的实参

var cadi = new Car("GM", "Cadillac", 1955, "tan", 5, false, 12892);
var cadiParams = {
   make: "GM", 
   model: "Cadillac", 
   year: 1955, 
   color: "tan", 
   passengers: 5, 
   convertible: false, 
   mileage: 12892
   };
   // 只需将所有实参都存储到对象字面量的恰当属性中即可。我们使用了构造函数使用的属性名。
   // 因此,对于调用构造函数Car的代码,可将其重写为下面这样
var cadi = new Car(cadiParams);

完成了重大的转变。代码不仅更整洁,阅读起来也容易得多,至少在我们看来如此。但是因为构造函数Car本身依然要求向它传递7个实参,而不是1个对象。下面来修改构造函数Car的代码修改构造函数Car:

var cadiParams = {
   make: "GM", 
   model: "Cadillac", 
   year: 1955, 
   color: "tan", 
   passengers: 5, 
   convertible: false, 
   mileage: 12892
   };
var cadi = new Car(cadiParams);

// 现在,需要删除构造函数Car的所有形参,用所传入对象的属性替代。我们将把这个形参命名为params。为使用这个对象,还需对这个构造函数的代码稍作修改

function Car(params) {
   this.make = params.make;
   this.model = params.model;
   this.year = params.year;
   this.color = params.color;
   this.passengers = params.passengers;
   this.convertible = params.convertible;
   this.mileage = params.mileage;
   this.started = false;
   this.start = function() {
      this.started = true;
   }; 
   this.stop = function() {
      this.started = false;
   }; 
   this.drive = function() {
      if (this.started) {
         alert("Zoom zoom!");
      } else {
         alert("You need to start the engine first.");
      } 
   };
}

「课堂练习」

使用闭包保护对象内的属性不被外部修改

要求:

  1. 补充完整下面的代码
  2. 定义一个私有变量weight属性
  3. 拥有getWeight特权方法可以访问私有属性weight
  4. 拥有addWeight特权方法可以修改私有属性weight + 1

部分代码:

function Bird(weight) {
  
  
  
}

var bird = new Bird(10)

console.log(bird.getWeight()) // 10
bird.addWeight() 
console.log(bird.getWeight()) // 11

原型模式

构造函数的弊端

介绍:构造函数方法很好用,但是存在一个浪费内存的问题。我们以Dog对象为例

function Dog(name, breed, weight) {
    this.name = name;
    this.breed = breed;
    this.weight = weight;
    this.bark = function() {
        if (this.weight > 25) {
            alert(this.name + " says WOOF!");
        } else {
            alert(this.name + " says woof!");
        }
    };
}

通过使用这个构造函数,可创建一致的小狗对象,并根据喜好进行定制;还可利用这个构造函数中定义的方法(这里只有一个bark)。另外,每个小狗对象都从构造函数那里获得了相同的代码,未来需要修改代码时,这可避免很多麻烦。这很好,但在运行阶段执行下面的代码时,情况将如何呢?

var fido = new Dog("Fido", "柴犬", 38);
var fluffy = new Dog("Fluffy", "贵宾", 30);
var spot = new Dog("Spot", "吉娃娃", 10);

表面上好像没什么问题,但是实际上这样做,有一个很大的弊端。那就是对于每一个实例对象,bark()方法功能完全相同,但每个小狗对象都有自己的副本。每一次生成一个实例,都必须为重复的内容,多占用一些内存。一般而言,我们不希望每次使用构造函数实例化一个对象时,都创建一组新的方法。这样会影响应用程序的性能,占用计算机资源。这可能是个大问题,在移动设备上尤其如此。

console.log(fido.bark === fluffy.bark) // false
console.log(fido.bark === spot.bark) // false
console.log(fluffy.bark === spot.bark) // false

思考:能不能让bark()方法在内存中只生成一次,然后所有实例都指向那个内存地址呢?回答是可以的。JavaScript提供更强大的对象创建方式。

首先我们思考使用构造函数的主要目的:试图重用行为。
例如,创建大量的小狗对象时,我们希望这些对象都使用相同的bark方法。通过使用构造函数,我们只需将bark方法放在构造函数Dog中,这样每次实例化对象时,都将重用方法bark的代码,从而在代码层面实现重用行为的目的。但在运行阶段,这种解决方案的效果并不好,因为每个小狗对象都将获得自己的bark方法副本。

而要解决以上问题这是我们需要充分利用JavaScript的对象模型。JavaScript对象模型基于原型的概念,在这种模型中,可通过扩展其他对象(即原型对象)来创建对象。为演示原型式继承,下面首先来创建小狗原型。

原型模式概念

介绍:JavaScript对象可从其他对象那里继承属性和行为。,avaScript使用原型式继承,其中其属性和方法被继承的对象称为原型。

例子:创建一个小狗的构造函数包含属性namebreedweight,因为这些属性随小狗而异各不相同, 同时再创建一个小狗原型。它是一个对象,包含所有小狗都需要的属性和方法speciesbark()run()wag()。有了该小狗原型后,这些小狗对象拥有物种属性和需要发出叫声、奔跑或摇尾巴时,都可使用原型提供的这些行为。

function Dog(name, breed, weight) {
    this.name = name;
    this.breed = breed;
    this.weight = weight;
}

// 这里给小狗原型添加了属性和方法。
Dog.prototype.species = "犬科"

Dog.prototype.bark = function() {
    if (this.weight > 25) {
        alert(this.name + " says WOOF!");
    } else {
        alert(this.name + " says woof!");
    }
}; 

Dog.prototype.run = function() {
      
    alert(this.name + " Run!");
       
}; 

Dog.prototype.wag = function() {
      
    alert(this.name + "Wag!");
       
}; 

// 像通常那样创建小狗对象。
var fido = new Dog("Fido", "柴犬", 38);
var fluffy = new Dog("Fluffy", "贵宾", 30);
var spot = new Dog("Spot", "吉娃娃", 10);

// 然后,像通常那样对每个小狗对象调用方法。每个小狗对象都从原型那里继承了这些方法。
fido.bark();
fido.run();
fido.wag();

fluffy.bark();
fluffy.run();
fluffy.wag();

spot.bark();
spot.run();
spot.wag();

概念:首先,需要创建小狗对象Fido、Fluffy和Spot的对象图,让它们继承新创建的小狗原型。为表示继承关系,我们将绘制从小狗实例到原型的虚线。注意,我们只将所有小狗都需要的方法和属性放在小狗原型中,因为所有小狗都将继承它们。对于所有随小狗对象而异的属性,如name,我们都将其都放在小狗实例中,因为每条小狗的这些属性都各不相同。

9.面向对象、面向对象-原型模式_第1张图片

「课堂练习」

使用原型属性来减少重复代码

要求:优化下面的代码

  1. 所有Bird实例可能会有相同的numLegs值,所以在每一个Bird的实例中本质上都有一个重复的变量numLegs。

部分代码:

function Bird(name) {
  this.name = name;
  this.numLegs = 2;
}

var duck = new Bird("duck");
var canary = new Bird("canary");
console.log(duck.numLegs); // 在控制台输出 2
console.log(canary.numLegs); // 在控制台输出 2

继承的工作原理

概念:Javascript规定,每一个构造函数都有一个prototype属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。

继承的方法和属性并不包含在各个小狗对象中,而是包含在原型中,上面的例子如何让小狗发出叫声呢?这正是继承的用武之地。对对象调用方法时,如果在对象
中找不到,将在原型中查找它:

  1. 首先,需要编写一些代码。例如,一个小狗对象调用方法bark的代码:
  2. 为执行这些代码,我们在实例fido中查找方法bark,但没有找到。
  3. 既然在实例fido中找不到方法bark,我们就沿继承链上移,在其原型中接着查找。
  4. 在小狗原型中查找,发现其中确实有方法bark。
  5. 最后,找到方法bark后,我们调用它,导致小狗对象fido发出叫声。

属性的情况也一样。如果我们编写了需要获取fido.name的代码,将从fido对象中获取这个值。如果要获取fido.species的值,将首先在对象fido中查找;在这里找不到后,将接着在小狗原型中查找

介绍:明白如何使用继承后,意味着,我们可以把那些不变的属性和方法,直接定义在prototype对象上。便可以创建大量的实例对象了。例如上面的小狗对象都能发出叫声,但依赖于小狗原型提供的方法bark。原型模式实现的代码重用,不仅只需在一个地方编写代码,而且让所有小狗实例都在运行阶段使用同一个bark方法,从而避免了庞大的运行阶段开销。

console.log(fido.bark === fluffy.bark) // true
console.log(fido.bark === spot.bark) // true
console.log(fluffy.bark === spot.bark) // true

重写原型

概念:继承原型并不意味着必须与它完全相同。在任何情况下,都可重写原型的属性和方法,为此只需在对象实例中提供它们即可。这之所以可行,是因为JavaScript总是先在对象实例(即具体的小狗对象)中查找属性;如果找不到,再在原型中查找。因此,要为对象spot定制方法bark,只需在其中包含自定义的方法bark。这样,JavaScript查找方法bark以便调用它时,将在对象spot中找到它,而不用劳神去原型中查找。

例子: 在对象spot中重写方法bark

function Dog(name, breed, weight) {
    this.name = name;
    this.breed = breed;
    this.weight = weight;
}
// 这里给小狗原型添加了属性和方法。
Dog.prototype.species = "犬科"
Dog.prototype.bark = function() {
    if (this.weight > 25) {
        alert(this.name + " says WOOF!");
    } else {
        alert(this.name + " says woof!");
    }
}; 

Dog.prototype.run = function() {      
    alert(this.name + " Run!");       
}; 
Dog.prototype.wag = function() {      
    alert(this.name + "Wag!");       
}; 

var spot = new Dog("Spot", "吉娃娃", 10);
spot.bark = function() {
        alert(this.name + " says WOOF!");
}; 
// 在小狗对象上调用方法,将首先在对象spot中查找方法bark。
// 在对象spot中找到了方法bark,无需再在原型中查找。这个方法显示says WOOF!。
spot.bark()

prototype

概念:每个对象都连接到一个原型对象,并且他可以从中继承属性。所有通过对象字面量创建的对象都连接到Object.prototype,他是JavaScript中的标配对象。

介绍: 当你创建一个新对象时,你可以选择某个对象作为它的原型。JavaScript提供的实现机制杂乱而复杂,但是可以被明显的简化。我们观察下面的代码它将创建一个使用原对象作为其原型的新对象。

var stooge = {
    "first-name": 'Jackly',  
    "last-name": 'Howard'
}
function F ()  {
   
}
F.prototype = stooge
var another_stooge = new F()
/*
another_stooge => {
    __proto__: {
        first-name: "Jackly"
        last-name: "Howard"
    }
}
*/

原型连接在更新时是不起作用的。当我们对某个对象做出改变时,不会触及该对象的原型:

another_stooge['first-name'] = 'Harry';
another_stooge['middle-name'] = 'Moses';
another_stooge.nickname = 'Moe';

/*
 another_stooge => {
    first-name: "Harry"
    middle-name: "Moses",
    nickname: 'Moe'
    __proto__: {
        first-name: "Jackly"
        last-name: "Howard"
    }
}
*/

注意

  • 原型连接只有在检索值的时候才被用到。如果我们尝试去获取对象的某个属性值,但该对象没有此属性名,那么JavaScript会试着从原型对象中获取属性值。如果那个原型对象也没有该属性,那么再从它的原型中寻找,依此类推,直到该过程最后到达终点 Object.prototype。如果想要的属性完全不存在于原型链中,那么结果就是 undefined值。这个过程称为委托。

  • 原型关系是一种动态的关系。如果我们添加一个新的属性到原型中,该属性会立即对所有基于该原型创建的对象可见。

stooge.profession = 'actor';
another_stooge.profession//'actor'
  • delete不会触及原型链中任何对象。删除对象属性可能会让来自原型链中的属性透现出来
delete another_stooge['first-name'] 
delete another_stooge['first-name'] 

/*
 another_stooge => {
    middle-name: "Moses",
    nickname: 'Moe'
    __proto__: {
        first-name: "Jackly"
        last-name: "Howard"
    }
}
*/

「课堂练习」

创建一个咖啡机器人原型

  1. 机器人都有相同的制造商 maker GECRus
  2. 机器人都有相同警告方法 speakWarning alert("Warning warning!!");
  3. 机器人都有相同制造咖啡方法 makeCoffee alert("Making coffee");
  4. 机器人拥有不同的name,year,owner属性

要求有两个机器人原型的实例:
其中一个1956年生产的Robby,它归Dr. Morbius所有,它的makeCoffee方法是 alert("喝咖啡时去星巴克")

另一个是1962年生产的Rosie,它归 George Jetson所有,拥有一个开关,能够打扫房间 alert("快速的清理房间中...")

部分代码:

 var robotObj={
            maker:function(){
                console.log("GECRus")
            },
            speakWarning:function(){
                alert("Warning warning!!");
            },
            makeCoffee:function(){
                if(this.name=="Robby"){
                    alert(this.name+"喝咖啡时去星巴克")
                }else{
                    alert("Making coffee");
                }
            }
        }
        function Robot(name,year,owner){
            this.name=name;
            this.year=year;
            this.owner=owner;
        }
        Robot.prototype=robotObj;
        var robby=new Robot("Robby", 1956, "Dr. Morbius");
        var rosie=new Robot("Rosie",1962,"George Jetson");
            rosie.cleanHouse=function(){
                alert(this.name+"快速的清理房间中...");
            }
            robby.makeCoffee();
            rosie.cleanHouse();


函数对象

思考:上面的例子中我们看到,构造函数Dog,它有一个prototype属性。这是一个指向原型的引用。Dog是个构造函数,即函数。函数也有属性吗?

解答:在JavaScript中,函数也是对象。实际上,在JavaScript中,几乎所有的东西都是对象,数组也是。

概念:JavaScript中函数就是对象,普通“键值对”对象其原型对象连接到Object.prototype。函数对象会隐藏连接到Function.prototype
(Function.prototype对象本身连接到Object.prototype)。

注意:每个函数在创建时会配有一个prototype属性。他的值是一个拥有constructor属性且值为该函数的对象,这和隐藏连接到Function.prototypr完全不同。

例子:通过Function.prototypr增加方法使得该方法对所有函数可用

Function.prototype.method = function(name,func) {
    if(!this.prototype[name]) {
        this.prototype[name] = func
    }
    return this
}

// 例 给Number.prototype增加一个integer方法来改善它。根据数组的正负判断是使用Math.ceiling 还是 Math.floor

Number.method('integer', function () {
    return Math[this < 0 ? 'ceil' : 'floor'](this);
})

console.log( (-10/3).integer() ) // -3

Prototype模式的验证方法

介绍:为了配合prototype属性,Javascript定义了一些辅助方法,帮助我们使用它

  • isPrototypeOf(): 这个方法用来判断,某个proptotype对象和某个实例之间的关系。
alert(Dog.prototype.isPrototypeOf(spot)); //true

alert(Dog.prototype.isPrototypeOf(fluffy)); //true
  • hasOwnProperty(): 每个实例对象都有一个hasOwnProperty()方法,用来判断某一个属性到底是本地属性,还是继承自prototype对象的属性。。如果属性是在对象实例中定义的,这个方法将返回true。如果属性不是在对象实例中定义的,但能够访问它,就可认为它肯定是在原型中定义的。
spot.hasOwnProperty("species"); // false
fido.hasOwnProperty("species"); // false
fido.hasOwnProperty("name"); // true
  • in运算符:
    • in运算符可以用来判断,某个实例是否含有某个属性,不管是不是本地属性。

      alert("name" in spot); // true
      
      alert("species" in spot); // true
      
    • in运算符还可以用来遍历某个对象的所有属性。

for(var prop in cat1) {
alert(“cat1[”+prop+"]="+cat1[prop]);
}
```

roperty(“name”); // true



- `in`运算符: 
  - in运算符可以用来判断,某个实例是否含有某个属性,不管是不是本地属性。
  
    ```javascript
    alert("name" in spot); // true

    alert("species" in spot); // true
    ```

  - in运算符还可以用来遍历某个对象的所有属性。
 
    ```javascript
       for(var prop in cat1) { 
            alert("cat1["+prop+"]="+cat1[prop]); 
        }
    ```
 










你可能感兴趣的:(面向对象编程)