js面向对象与原型和继承(ECMAScript5篇)

98 阅读5分钟

创建对象的方式

最基本的方式

var box = new Object(); //创建一个 Object 对象
   box.name = 'Lee'; //创建一个 name 属性并赋值
   box.age = 100; //创建一个 age 属性并赋值
   box.run = function() { //创建一个 run()方法并返回值
       return this.name + this.age + '运行中...';
   };
   alert(box.run()); //输出属性和方法的值

缺点:想创建一个类似的对象,就 会产生大量的代码。

var box2 = new Object();
  box2.name = 'Jack';
  box2.age = 200;
  box2.run = function() {
      return this.name + this.age + '运行中...';
  };
  alert(box2.run());

工厂方式 为了解决多个类似对象声明的问题,我们可以使用一种叫做工厂模式的方法,这种方法 就是为了解决实例化对象产生大量重复的问题。

function createObject(name, age) { //集中实例化的函数
       var obj = new Object();
       obj.name = name;
       obj.age = age;
       obj.run = function() {
           return this.name + this.age + '运行中...';
       };
       return obj;
   }
   var box1 = createObject('Lee', 100); //第一个实例
   var box2 = createObject('Jack', 200); //第二个实例
   alert(box1.run());
   alert(box2.run()); //保持独立
   
   alert(typeof box1); //Object
   alert(box1 instanceof Object); //true

缺点: 工厂模式解决了重复实例化的问题,但还有一个问题,那就是识别问题,因为根本无法 搞清楚他们到底是哪个对象的实例

构造函数方式

即解决了重复实例化的问题,又解决了对象识别的问题

使用了构造函数的方法,和使用工厂模式的方法他们不同之处如下:

1.构造函数方法没有显示的创建对象(new Object());

2.直接将属性和方法赋值给 this 对象;

3.没有 renturn 语句。

 function Box(name, age) { //构造函数模式
      this.name = name;
      this.age = age;
      this.run = function() {
          return this.name + this.age + '运行中...';
      };
  }
  var box1 = new Box('Lee', 100); //new Box()即可
  var box2 = new Box('Jack', 200);
  alert(box1.run());
  alert(box1 instanceof Box); //很清晰的识别他从属于 Box

关于 this 的使用,this 其实就是代表当前作用域对象的引用。如果在全局范围 this 就代 表 window 对象,如果在构造函数体内,就代表当前的构造函数所声明的对象。

原型

通过prototype方式添加原型

  1. 我们创建的每个函数都有一个 prototype(原型)属性,这个属性是一个对象,它的用途是 包含可以由特定类型的所有实例共享的属性和方法。
 function Box() {} //声明一个构造函数

   Box.prototype.name = 'Lee'; //在原型里添加属性
   Box.prototype.age = 100;
   Box.prototype.run = function() { //在原型里添加方法
       return this.name + this.age + '运行中...';
   };
   var box1 = new Box();
   var box2 = new Box();
   // alert(box1.run == box2.run); //true,方法的引用地址保持一致

原型模式声明中,多了两个属性,这两个属性都是创建对象时自动生成的。proto 属性是实例指向原型对象的一个指针 console.log(box1.__proto__)

image.png 它的作用就是指向构造函数的原型属性 constructor。 通过这两个属性,就可以访问到原型里的属性和方法了。 console.log(box1.__proto__.constructor)

image.png console.log(Box.prototype.constructor)

image.png 判断一个对象是否指向了该构造函数的原型对象, alert(Box.prototype.isPrototypeOf(box1));

如何判断属性是在构造函数的实例里, 还是在原型里? 可以使用 hasOwnProperty() 函数 alert(box.hasOwnProperty('name')); //实例里有返回 true,否则返回 false

原型模式的执行流程:

1.先查找构造函数实例里的属性或方法,如果有,立刻返回;

2.如果构造函数实例里没有,则去它的原型对象里找,如果有,就返回;

image.png

字面量方式创建原型

问题: 字面量创建的方式使用 constructor 属性不会指向实例,而会指向 Object

答: 字面量方式为什么 constructor 会指向 Object?因为 Box.prototype={};这种写法其实 就是创建了一个新对象。而每创建一个函数,就会同时创建它 prototype,这个对象也会自 动获取 constructor 属性。所以,新对象的 constructor 重写了 Box 原来的 constructor,因此会 指向新对象,那个新对象没有指定构造函数,那么就默认为 Object。

   Box() {};
   Box.prototype = { //使用字面量的方式
       name: 'Lee',
       age: 100,
       run: function() {
           return this.name + this.age + '运行中...';
       }
   };

解决方式:原型的声明是有先后顺序的,所以,重写的原型会切断之前的原型。

 Box.prototype = { constructor : Box, //直接强制指向即可 };

原型模式创建对象的缺点:

原型模式创建对象也有自己的缺点,它省略了构造函数传参初始化这一过程,带来的缺 点就是初始化的值都是一致的。而原型最大的缺点就是它最大的优点,那就是共享。 原型中所有属性是被很多实例共享的,共享对于函数非常合适,对于包含基本值的属性 也还可以。但如果属性包含引用类型,就存在一定的问题:

function Box() {};
    Box.prototype = {
        constructor: Box,
        name: 'Lee',
        age: 100,
        family: ['父亲', '母亲', '妹妹'], //添加了一个数组属性
        run: function() {
            return this.name + this.age + this.family;
        }
    };
    var box1 = new Box();
    box1.family.push('哥哥'); //在实例中添加'哥哥'      alert(box1.run());
    var box2 = new Box();
    alert(box2.run()); //共享带来的麻烦,也有'哥哥'了

解决方式:

  1. 以组合构造函数+原型模式 (解决了传参和引用共享的问题)
   function Box(name, age) { //不共享的使用构造函数
      this.name = name;
      this.age = age;
      this.family = ['父亲', '母亲', '妹妹'];
  };
  Box.prototype = { //共享的使用原型模式
      constructor: Box,
      run: function() {
          return this.name + this.age + this.family;
      }
  };

继承

ECMAScript 只支持继承,不支持接口实现,而实现 继承的方式依靠原型链完成。 (1)原型继承 缺点:比如字面量重写原型会中断关系,使用引用类型的原型,并且子类型还无法给超类型传递参数。

 function Box() { //Box 构造
       this.name = 'Lee';
   }

   function Desk() { //Desk 构造
       this.age = 100;
   }
   Desk.prototype = new Box(); //Desc 继承了 Box,通过原型,形成链条
   var desk = new Desk();
   alert(desk.age);
   alert(desk.name); //得到被继承的属性

2.对象冒充继承的方式(为了解决引用共享和超类型无法传参的问题) 缺点: 借用构造函数虽然解决了刚才两种问题,但没有原型,复用则无从谈起。

function Box(age) {
   this.name = ['Lee', 'Jack', 'Hello']
   this.age = age;
   }
   function Desk(age) {
   Box.call(this, age); //对象冒充,给超类型传参
   }
   var desk = new Desk(200);
   alert(desk.age);
   alert(desk.name);
   desk.name.push('AAA'); //添加的新数据,只给 desk
   alert(desk.name);

3.组合继承(原型链+借用构造函数)

function Box(age) {
      this.name = ['Lee', 'Jack', 'Hello']
      this.age = age;
  }
  Box.prototype.run = function() {
      return this.name + this.age;
  };

  function Desk(age) {
      Box.call(this, age); //对象冒充
  }
  Desk.prototype = new Box(); //原型链继承
  var desk = new Desk(100);
  alert(desk.run());

4.原型式继承 都es6还用这个干啥

5.寄生式继承 都es6 class了面试还问,脑子有坑