JS之三面向对象

时间:2019-02-19
本文章向大家介绍JS之三面向对象,主要包括JS之三面向对象使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

概述

1.面向对象的概念
2.对象的创建方法
3.面向对象继承
4.私有变量
5.模块化演变

1.面向对象的概念

2.对象的创建方法

1.工厂的方式创建对象
2.构造函数创建对象
3.原型创建对象
4.混合模式:原型+构造函数(推荐)
5.稳妥构造函数模式

1. 工厂的方式创建对象

function createPerson(name,age,job){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function(){
      alert(this.name);
    }
    return o;// 如果是构造函数执行模式,如果返回的是一个引用类型,就把引用类型返回。
      // 如果是简单类型,那么就返回this
  }
  var person1 = createPerson('Grey',27,'Doctor');

函数 createPerson()每次它都会返回一个包含三个属性一个方法的对象
优点:可以进行批量的创建 都有公共默认值和属性的对象。
缺点:
1、对象的原型不能确认具体的类型、对象的构造函数也不明确。
2、对象的方法不能重用,每个对象的内存中都存储一份函数对象的内存。

2.构造函数创建对象

 function Cat(age, name) {
      this.age = age;
      this.name = name;
      this.run = function() {
        console.log(this.name + ' running....');
      };
      // 通过构造函数创建一个对象。
    var c1 = new Cat(19, 'dd');
    c1.age = 20; // 修改对象的属性值。
    c1.run();
    var c2 = new Cat(22, 'ss');

 // 当使用new来调用构造函数时
      // 1、创建一个空对象
      // 2、把空对象赋值给this   
      // 3、执行构造函数的里面代码,并给this的属性做赋值初始化
      // 4、把新创建的对象返回(如果有返回值,返回值是简单类型会直接忽略,还是返回this),如果是引用类型直接返回引用对象。

优点:
1、创建对象的时候默认初始化一些属性。
2、可以进行使用instanceof追溯对象的原型及构造函数。
c1 instanceof Cat; // true
c1.constructor === Cat
缺点:
对象的方法不能进行重用,每个对象里面都要存储一份方法对象。浪费内存。

3.原型创建对象

<script>
    function Cat() {
      this.age = 19;
      // 如果需要共享的方法和属性咱们一般放到 原型中定义。
      // this.run = function() {
      //   console.log('run');
      // }
    }

    // 原型中定义属性和方法
    Cat.prototype.run = function() {
      console.log(this.name, this.age);
    };
    // 问题: 私有的属性希望每个对象私自拥有。
    Cat.prototype.name = 'black cat!'; // 所有的新对象都共享这个一个属性。

    var c1 = new Cat(); // c1  有run方法
    var c2 = new Cat(); // c2  有run方法

    console.log(c1.name); // black cat!
    console.log(c2.name); // black cat!
    c1.run();  // black cat! 19

    console.log( c1.run === c2.run); // true

    c1.name = 'good cat!';  // 对象的属性分为:读取和设置两种模式。
    // 如果读取:自己没有这个属性,那么去原型上找,直到找到位置。 如果找不到undefined
    // 如果是写入:那么自己没有这个属性,那么直接添加一个自己属性。
    console.log(c1.name , c2.name);
    // good cat! black cat!
  </script>

优点:
所有原型上的属性和方法在所有的新对象中可以进行共享。
缺点:
如果对象需要自己特有的属性值,不与其他对象共享,则必须跟构造函数模式进行配合

4.混合模式:原型+构造函数

<script>
    function Cat(age, name) {  // 一般构造函数的首字母大写,我也称是Cat类。
      this.age = age;   // 每个对象都有自己私有的属性值的属性,放到构造函数中。
      this.name = name;
    }

    // 一般类型的方法,都放在原型上,让所有的对象都共享方法的内存。
    Cat.prototype.run = function() {
      console.log(this.name + ' running....');
    }
    var c1 = new Cat(19, 'jimi');
    var c2 = new Cat(19, 'Dimk');

    // c1.run === c2.run  // => true
    c1.age = 20; // c2.age = 19
  </script>

组合使用构造函数模式与原型模式
公共的属性和方法放到 原型上,独有的属性使用构造函数模式,放到对象自己身上。
优点:
既保证了方法等共享的属性能只在内存中保存一份,节省内存。
又可以实现每个对象有自己单独存放的属性。是一种经典的构建对象的方法

5.稳妥构造函数模式(不使用new)

<script>
    function Cat() {
      var o = {};
      o.age = 10;
      o.name = 'laoma';
      o.run = function() {
        console.log(o.name + ' running....');
      };
      return o;  // 如果是构造函数执行模式,如果返回的是一个引用类型,就把引用类型返回。
      // 如果是简单类型,那么就返回this
    }

    // 稳妥构造函数模式,要实现使用new构造一个对象和不使用new构造一个对象效果一样。
    var c1 = new Cat();// 构造函数调用模式
    var c2 = Cat();    // 函数调用模式
    // 缺点:  不能溯源 原型、构造函数  、 对象的方法内存不能共享,浪费内存。
  </script>

优点:
可以共享属性和方法的初始化代码。
无论用户是否用了new还是没有使用new都会被正确的返回新的对象
缺点:
无法追溯对象的原型和构造函数,默认没有公共的属性和方法,内存浪费。

3.面向对象继承

1.原型继承的方式
借用构造函数继承
组合继承
原型式继承
寄生继承方式
终极方式:寄生组合的方式

1.原型继承的方式

<script>
    // 动物基类
    function Animal(age, name) {
      this.age = age;
      this.name = name;
      this.foods = ['鱼', '鸟', '水果'];
    }

    // 在动物基类的原型上添加方法run
    Animal.prototype.run = function() {
      console.log(this.name + ' running....');
    }

    function Cat(age, name) {
      this.age = age;
      this.name = name;
    }
    // 原型的继承方式。
    // Cat.prototype.constructor === Cat
    Cat.prototype = new Animal();   // 父类的构造函数:执行第一次。
    Cat.prototype.constructor = Cat;  // 因为上面的代码把Cat的prototype指向了Animal

    var c = new Cat(19, 'sss');  //希望:cat继承animal的属性和方法
    c.run(); // 从Animal原型上继承的方法。
    c.foods.push('栗子');

    var c2 = new Cat(20, 'ssfdd');
    // c2.foods;//  ['鱼', '鸟', '水果', '栗子'];
    // 问题:
    // 1、子类的构造函数的参数,没法传递给父类的构造函数。
    // 2、子类的原型的constructor会被改变,需要自己改回来。
    // 3、如果父类里有引用类型的属性,那么所有的子类会共享这个引用类型。
  </script>

2.组合继承

 // 父类
    function Animal(age, name) {
      this.age = age;
      this.name = name;
      this.foods = ['水', '苹果'];
    }

    // 在父类的原型上创建一个run方法
    Animal.prototype.run = function() {
      console.log(this.name + '  running');
    };

    // 定义子类
    function Cat(age, name) {
      // Animal(age, name); // this === window;  函数执行模式,this === window
      // this == c
      // 第一次执行父类的构造函数。
      Animal.call(this, age, name);   //借用父类的构造函数,给子类创建实例属性。
    }

    // 第二次执行父类的构造函数。
    Cat.prototype = new Animal();     // 组合原型继承模式
    Cat.prototype.constructor = Cat;

    var c = new Cat(19, 'deail');

组合模式就是经典模式,解决了原型模式的两大问题。
组合继承模式:用原型继承原型属性和方法。用构造函数模式继承实例的属性和方法,非常经典的用法。
缺点:父类的构造函数需要执行两次。一次:设置原型对象,二次:构造函数借用时需要再执行一次

3.原型式继承

<script>
    // o就是要借用的对象。
    function object(o) {
      function F() {}
      F.prototype = o;  // 让空函数的原型指向 o对象。
      return new F();   // 创建一个f实例,f的内部原型指向o对象。
    }

    var m = {age : 19, name: 'laoma', friends: ["laoma2", "jimik" ]};

    var m1 = object(m);

    console.log(m1.friends);
    m1.age = 20;
     console.log(m1.age);
    // 优点:
    // 不需要使用new构造函数就可以直接  构造另外其他对象。
    // 缺点:
    // 所有构造出来的实例会共享 原型对象上的引用类型的属性。

    // EC5
    // Object.create();

  </script>

原型式继承是避免调用父类构造函数的一种巧妙的方式。本质就是借用对象来构造另外一个对象。
缺点:原型对象上的引用类型的属性会造成子类对象进行共享。

4.寄生继承模式

寄生式继承是原型式继承的加强版。
寄生继承模式是在原型式继承模式上增强原型对象的增强模式。只是对原型式继承的扩展而已。
寄生继承类似一个工厂模式,工厂内部把原型对象进行构造出另外一个实例,并对构造出来的实例进行增强,最后返回这个实例。

 // 寄生继承方式: 其实就是传一个对象到一个方法(工厂方法),方法内部
    // 根据传来的对象构造一个新对象,并对新对象进行扩展增强,
    // 返回新对象。

    function createPersion(p) {
      var o = object(p); // 同p对象构造一个新对象o
      o.say = function() {  // 对我新构造出来的对象o进行扩展
        console.log('hi');
      }
      return o;
    }
  </script>

5.寄生组合继承模式

由于组合继承模式需要执行两次的父类的构造函数,使用寄生继承模式替换原型继承模式,那么就可以实现父类的构造函数只执行一次的效果,是目前认为最经典的继承模式,最好的继承模式。YUI库也是大量使用了这种继承模式。

 <script>
    // 寄生组合继承模式
    // 组合了:寄生继承模式和借用构造函数继承模式。

    // 父类
    function Animal(age, name) {
      this.age = age;
      this.name = name;
      this.foods = ['水果','ls'];
    }

    // 父类原型上的方法:通过寄生继承的方式进行继承.
    Animal.prototype.run = function() {
      console.log(this.name + '  runnging');
    };

    function Cat(age, name) {
      // 使用借用构造函数继承模式来构建对象的实例属性。
      Animal.call(this, age, name);
    }
    // Cat.prototype = new Animal(); // 多执行了一次父类的构造函数。

    // 寄生继承的方法
    Cat.prototype = inheritFrom(Animal.prototype);

    var c1 = new Cat(19, 'laom');
    var c2 = new Cat(19, 'laom2');

    c1.run();
    c2.run();
    
    // 寄生继承模式。
    function inheritFrom(o) {
      var t = object(o);
      t.constructor = Cat; // 把Cat原型的构造函数指回Cat构造函数。
      return t;
    }

    // 原型式继承的方法。
    function object(o) {
      function F() {}
      F.prototype = o;
      return new F();
    }
  </script>

4.私有变量

JavaScript中并没有私有变量的概念,但是我们可以通过某些方式进行模拟。所谓的私有变量就指:对象的某个属性只能通过对象的方法进行访问,不能直接通过对象进行访问。
构造函数模拟私有变量的方式
其他模拟方法

第一种模拟私有变量的方式

function Persion() {
      var age = 0;  // 私有变量,只能通过getAge和setAge来操作age变量。
      this.getAge = function() {
        return age;
      }
      this.setAge = function(a) {
        age = a;
      }
    }

    var p = new Persion();

    p.setAge(90);
    // 访问p的年龄
    console.log(p.getAge());//90

第二个种模拟私有变量的方式

 function Persion() {
      var age = 0;

      return {
        getAge: function() {
          return age;
        },
        setAge: function(num) {
          age = num;
        }
      }
    }

    var p = Persion(); // 创建一个persion对象。
    p.setAge(20);
    console.log(p.getAge());

5.模块化演变

JS开发的灾难:
全局变量的相互污染?
不同开发人员不同js文件相互干扰
不同框架中的全局变量相互干扰
没有模块、没有命名空间等手段
解决多人合作和模拟模块的尝试
模拟命名空间
自执行函数模拟局部作用域
模块化尝试封装

解决方法一:模拟命名空间

 // 第一个尝试;命名空间
    // a.js  老马  
    var Shop = {}; // 顶层命名空间
    Shop.User = {}; // 电商的用户模块
    Shop.User.UserList = {}; //用户列表页面模块。
    Shop.User.UserList.length = 19; // 用户一共有19个。

    // b.js 小米开发的。
    Shop.User.UserDetial = {};
    Shop.User.UserDetial.length = 20;
    console.log(Shop.User.UserDetial.length)//因为2者的UserDetial和UserList是不一样的,所以length是不会发生冲突的
    console.log(Shop.User.UserList.length)

解决方法二:自执行函数模拟局部作用域

    // => 给单个文件里面定义的局部变量都 变成 局部作用域里面的变量。
    // 第二个尝试:
    // a.js
    (function(){
      var a = 9;
    })();


    // b.js
    (function(){
      var a = "ssss";
    })();

    // 局部作用域和命名空间的用法减少了变量冲突的可能性。