js原型、原型继承与非ES6的面向对象编程

一 对象封装与原型

JS是一种基于对象的语言,遇到的所有东西几乎都是对象。但在ES6之前它并不是一种真正的面向对象编程(OOP)语言,因为语法中没有class(类)。

ES6之前,要把"属性"和"方法",封装成一个对象,甚至从原型对象生成一个实例对象,有下列做法

1. 原始模式-生成生成实例对象

写一个函数,解决生成对象代码重复的问题。

function Cat(name,color) {
    return {
      name:name,
      color:color
    }
  }

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

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

问题:cat1和cat2之间没有内在联系,不能反映出是同一个原型对象的实例。

2. 构造函数模式

"构造函数",就是一个普通函数(函数名首字母大写!),但内部使用了this变量。对构造函数使用new运算符,就能生成实例,且this变量会绑定在实例对象上。

原型对象 实例:

function Cat(name,color){
    this.name=name;
    this.color=color;
  }

生成实例对象:

var cat1 = new Cat("大毛","黄色");
var cat2 = new Cat("二毛","黑色");
alert(cat1.name); // 大毛
alert(cat1.color); // 黄色

用 new 构造函数 生成的实例对象会自动含有constructor属性,指向其构造函数。

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

JS的instanceof运算符,可验证原型对象(这里就是构造函数)与实例对象之间的关系:  

alert(cat1 instanceof Cat); //true

问题: 存在浪费内存问题。 对每一个实例对象,共同属性和方法都一样,每次生成实例,却必须重复,多占用了内存。

function Cat(name,color){
    this.name = name;
    this.color = color;
    this.type = "猫科动物"; //共同属性
    this.eat = function(){alert("吃老鼠");}; //共同方法
  }

更好的做法是让公共属性和方法在内存中只生成一次,然后所有实例都指向那个内存地址,于是就有了Prototype模式(原型模式)。

3. Prototype模式(原型模式)

(1) Prototype(原型的定义) !重点!

JS的每个构造函数(注意是构造函数!)都有一个prototype属性,指向另一个对象(其所有属性和方法,都会被构造函数的实例继承)。

可以把共同的属性和方法,直接定义在prototype对象上。

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

生成实例:

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

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

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

(2)prototype属性的相关辅助方法

isPrototypeOf() 判断某个proptotype对象和某个实例之间的关系。

alert(Cat.prototype.isPrototypeOf(cat1)); //true

hasOwnProperty() 判断某一个属性是本地属性,还是继承自prototype对象。

alert(cat1.hasOwnProperty("name")); // true

in运算符 a. 判断某实例是否有某属性,不管是不是本地属性

alert("name" in cat1); // true

b. in运算符还可遍历某对象所有属性:

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

二 构造函数的继承

1. 用prototype模式(常见)

例:

现有"动物"对象的构造函数:

  function Animal(){
    this.species = "动物";
  }

还有"猫"对象的构造函数:

  function Cat(name,color){
    this.name = name;
    this.color = color;
  }

怎样才能使"猫"继承"动物"?

使用prototype模式:

"猫"的prototype对象,指向一个Animal的实例,那么所有"猫"的实例,就能继承Animal:

```   Cat.prototype = new Animal(); Cat.prototype.constructor = Cat;

var cat1 = new Cat("大毛","黄色"); alert(cat1.species); // 动物


第一行,将Cat的prototype对象指向一个Animal的实例。
 
第二行,由于任何一prototype对象都有constructor属性,指向其构造函数。加了"Cat.prototype = new Animal();"这一行后,Cat.prototype.constructor指向Animal(原来是Cat)。

每一个实例也有constructor属性,默认调用prototype对象的constructor属性。因此实例cat1.constructor也指向Animal!
 
**这会导致继承链的紊乱(实例cat1明明是用构造函数Cat生成的)**,因此必须手动纠正,将Cat.prototype对象的constructor值改为Cat(第二行,很重要,编程时务必要遵守)。

**即时刻遵守:**
**如果替换了prototype对象,下一步必然是为新的prototype对象加上constructor属性,指回原来的构造函数**:

o.prototype = ... ; o.prototype.constructor = o;


## 二 非构造函数的继承

例:
现在有一对象"中国人"。

  var Chinese = {     nation:'中国'   };


还有一对象"医生"。

  var Doctor ={     career:'医生'   }


怎样才能让"医生"去继承"中国人",生成"中国医生"的对象?
这**两个对象都是普通对象,不是构造函数,无法使用构造函数方法实现"继承"**。

### 方法1:使用object()方法

object()方法

function object(o) {     function F() {}     F.prototype = o;     return new F();   }


这个object()函数把子对象的prototype属性,指向父对象

使用时,第一步,在父对象基础上,生成子对象:

  var Doctor = object(Chinese);


再加上子对象本身的属性:

  Doctor.career = '医生';


这时,子对象已经继承了父对象的属性:

  alert(Doctor.nation); //中国


### 方法2 把父对象的属性,全部拷贝给子对象,也能实现继承。

#### 浅拷贝(不建议)
这样的拷贝有一个问题:如果父对象的属性等于数组或另一个对象,子对象获得的只是一个内存地址,而不是真正拷贝,因此存在父对象被篡改的可能。

只能拷贝基本类型的数据,这种拷贝叫做"浅拷贝"。

#### 深拷贝(建议)
**"深拷贝"**,就是能**实现真正意义上的数组和对象的拷贝**。它的实现只要**递归调用"浅拷贝"**就行了。

function deepCopy(p, c) {     var c = c || {};     for (var i in p) {       if (typeof p[i] === 'object') {         c[i] = (p[i].constructor === Array) ? [] : {};         deepCopy(p[i], c[i]);       } else {          c[i] = p[i];       }     }     return c;   }


使用:

  var Doctor = deepCopy(Chinese);

```

参考

已学习

Javascript 面向对象编程(一):封装 http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_encapsulation.html

Javascript面向对象编程(二):构造函数的继承 http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_inheritance.html

Javascript面向对象编程(三):非构造函数的继承 http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_inheritance_continued.html

未学习

proto和prototype来深入理解JS对象和原型链 https://github.com/creeperyang/blog/issues/9

results matching ""

    No results matching ""