阮一峰Js学习笔记(三 面向对象编程 :封装、继承)

1、封装
他是基于对象(object-based)的语言,但并不是面向对象(OOP)的,因为他本身没有类(enm。。。现在的语法有class了吧)。
我们要把"属性"(property)和"方法"(method),封装成一个对象,甚至要从原型对象生成一个实例对象:
1.1、原始模式

// 这是一个原型对象
var Cat = {
    name : '',
    color : ''
  }
// 每次生成实例都得这么写,太麻烦;实例和原型之间没有任何方法,看不出联系
var cat1 = {}; // 创建一个空对象
cat1.name = "大毛"; // 按照原型对象的属性赋值
cat1.color = "黄色";
var cat2 = {};
cat2.name = "二毛";
cat2.color = "黑色";

1.2、改进,解决代码重复问题

// 但两个cat之间没有联系,看不出是同一个原型对象的实例???(为啥看不出呀)
function Cat(name,color) {
    return {
      name:name,
      color:color
    }
  }

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

1.3、构造函数模式

// 内部使用了this,对构造函数使用`new`运算符,就能生成实例,并且`this`变量会绑定在实例对象上。
function Cat(name,color){
    this.name=name;
    this.color=color;
// 构造函数模式的弊端
// 每次生成一个实例都会生成重复的内容,浪费内存
              this.type = "猫科动物";
    this.eat = function(){alert("吃老鼠");};
  }
var cat1 = new Cat("大毛","黄色");
var cat2 = new Cat("二毛","黑色");
// 自动含有 constructor 属性,指向构造函数
alert(cat1.constructor == Cat); //true
alert(cat2.constructor == Cat); //true
// instanceof 运算符,验证原型对象与实例对象之间的关系。
alert(cat1 instanceof Cat); //true
alert(cat2 instanceof Cat); //true

1.4、原型模式
每个构造函数都有一个 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

1.5、Prototype模式的验证方法
isPrototypeOf():

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

hasOwnProperty():

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

in运算符:

// 判断某个实例是不是含有某个属性,不管是不是本地属性
alert("name" in cat1); // true

// 遍历某个对象的所有属性
for(var prop in cat1) { alert("cat1["+prop+"]="+cat1[prop]); }

2、构造函数继承

// 要让猫继承动物
function Animal(){
    this.species = "动物";
  }
function Cat(name,color){
    this.name = name;
    this.color = color;
  }

对象之间继承的 5 种方式:
2.1、构造函数绑定
使用call或apply方法,将父对象的构造函数绑定在子对象上

function Cat(name,color){
// 使用call或apply方法,将父对象的构造函数绑定在子对象上
    Animal.apply(this, arguments);
    this.name = name;
    this.color = color;
  }
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物

2.2、原型模式

Cat.prototype = new Animal(); // 将猫的原型指向了一个新的动物对象
// 上一行完全删除了Cat原型对象原先的值,任何一个原型对象和他的实例都有一个constructor属性,指向他的构造函数,如果不加上面那句,cat的构造函数就变成了Animal,而他其实是用Cat生成的,继承链混乱。
// 同样的,如果替换了原型对象,那么也需要指回来
// o.prototype = {};
// o.prototype.constructor = o;
Cat.prototype.constructor = Cat;
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物

2.3、直接继承prototype

function Animal(){ }
Animal.prototype.species = "动物";
 // 效率高,但是猫和动物的原型都指向了同一个对象,对猫的修改会影响到动物
Cat.prototype = Animal.prototype;
// 这句话把动物的原型也给改了。。变成Cat了
Cat.prototype.constructor = Cat;
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物

2.4、空对象做继承

var F = function(){};
F.prototype = Animal.prototype;
Cat.prototype = new F();
Cat.prototype.constructor = Cat;

alert(Animal.prototype.constructor); // Animal

2.5、拷贝继承:把父对象的所有属性和方法,拷贝进子对象

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

// 用作属性拷贝的函数
function extend2(Child, Parent) {
    var p = Parent.prototype;
    var c = Child.prototype;
    for (var i in p) {
      c[i] = p[i];
      }
    c.uber = p;
  }

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

3、非构造函数继承(两个普通对象继承)

var Chinese = {
    nation:'中国'
  };
var Doctor ={
    career:'医生'
  }

要让医生去继承中国人,也就是获得一个中国医生,三种方法
3.1、object()方法

// 把子对象的prototype属性指向父对象,从而联系起来
function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
  }

var Doctor = object(Chinese);
Doctor.career = '医生';
alert(Doctor.nation); //中国

3.2、浅拷贝(把父对象的全部属性拷贝给子对象)

function extendCopy(p) {
    var c = {};
// 注意是浅拷贝,只拷贝了引用,当子对象的属性变化时,父对象的对应属性会发生同样的变化
    for (var i in p) {
      c[i] = p[i];
    }
// “uber”是源某些人在模拟class时用来表示super的(因为百super是关键字所以不能直接度用)。
    c.uber = p;
    return c;
  }

var Doctor = extendCopy(Chinese);
  Doctor.career = '医生';
  alert(Doctor.nation); // 中国

3.3、深拷贝(jQuery库用的是这种继承方式)

// 递归调用浅拷贝
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);
Chinese.birthPlaces = ['北京','上海','香港'];
Doctor.birthPlaces.push('厦门');
// 这样父对象没有受到影响
alert(Doctor.birthPlaces); //北京, 上海, 香港, 厦门
alert(Chinese.birthPlaces); //北京, 上海, 香港

enm。。。关于基本类型和对象类型的存储方式有待复习???

4、继承机制的设计思想
http://www.ruanyifeng.com/blog/2011/06/designing_ideas_of_inheritance_mechanism_in_javascript.html
new命令引入了Javascript,用来从原型对象生成一个实例对象
new命令后面跟的不是类,而是构造函数
用构造函数生成实例对象,有一个缺点,那就是无法共享属性和方法
考虑到这一点,Brendan Eich决定为构造函数设置一个prototype属性。
5、Js定义类的三种方法
5.1、构造函数:比较复杂,用到this、prototype
5.2、Object.creat():不能实现私有属性和私有方法,实例对象之间也不能共享数据,对"类"的模拟不够全面。
5.3、极简主义法:在构造函数里定义实例对象,并返回它
优点:
容易理解,结构清晰优雅,符合传统的"面向对象编程"的构造;
让一个类继承另一个类,实现起来很方便。只要在前者的createNew()方法中,调用后者的createNew()方法即可;
只要不是定义在对象上的方法和属性,都是私有的,只能通过共用方法来调用

var Cat = {
    createNew: function(){
      var cat = {};
      var sound = "喵喵喵";
      cat.makeSound = function(){ alert(sound); };
      return cat;
    }
  };

还可以数据共享:

var Cat = {

    sound : "喵喵喵", // 这里是共享数据,当一个对象改变只后,另一个对象也会变
    createNew: function(){
      var cat = {};
      cat.makeSound = function(){ alert(Cat.sound); };
      cat.changeSound = function(x){ Cat.sound = x; };
      return cat;
    }
  };
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。