# 对象的特性

  1. 封装
  2. 继承

# 封装

封装: 将对象的所有组成部分组合起来,尽可能的隐藏对象的部分细节,使其受到保护,只提供有限的接口与外部发生联系。

例如同时生成多个相同的对象,可以将生成对象的过程打包直接调用,这个过程就是封装

优点:

  1. 安全,使用时无法看到具体实现细节,只需要直接调用
  2. 便于修改操作

# 封装方法

  • 工厂函数(不推荐使用)

    将创建对象并赋值的过程封装成一个函数

    function person(name,sex){
        var person = {};
        person.name = name;
        person.sex = sex;
        person.say = function(){
            alert("说话");
        }
        return person;
    }
    var zhangsan = person("张三","man");
    alert(zhangsan.name);
    
  • 构造函数(每创建一个对象,会把相同的代码存储到内存中,会造成对内存的浪费)

    function person(name,sex){
        this.name = name;
        this.sex = sex;
        this.say = function(){
            alert("说话");
        }
    }
    var lisi = new person("李四","boy");
    alert(lisi.sex);
    

每次使用工厂函数或构造函数,内部的属性和方法都会在内存中重新开辟一个控件存储生成的对象,导致某些相同的方法或属性被重复保存,占用内存。

  • prototype方法(会把共享的方法或属性放到代码段中来存储,它不能共享对象)
    实例一:

    person.prototype.eat=function(){
        alert("吃饭");
    }
    var lisi=new person("李四","boy");
    lisi.eat();
    

    实例二:

    person.prototype.aaa = {name:"王五"};
    var lisi = new person("李四","boy");
    var zhaoliu = new person("赵六","boy");
    alert(lisi.aaa.name = "xiaosi"); //xiaosi
    alert(zhaoliu.aaa.name);  //xiaosi 将原型上的属性值一起改了
    
  • 混合函数 最佳的一种方法,构造函数与prototype的结合,根据实际情况考虑

    私有属性方法放到构造函数中,共有方法放到原型中

    function person(user, sex){
      this.user = user
      this.sex = sex
    }
    person.prototype = {
      coding: funciton(){
        alert("写代码")
      }
    }
    

    对象在内存中存储方式:
    对象的内存图示

# 对象的继承

继承:一个对象拥有另一个对象的属性与方法

  • 父类(基类):被继承的对象,
  • 子类:继承的对象。

优点:
提高代码的重用性,提高代码的逻辑性与可维护性。

# 继承方式

# 原型继承(将父类的实例作为子类的原型)

原理:对象访问属性方法会遵循 "构造函数 -> 原型"的顺序,所以将父类的实例放到子类原型时, 子类实例化出的对象就可以访问到原型上父类的内容,从而实现了继承。

function Animal(){
  eat: function(food){
    alert(food)
  }
}
function Cat(){ }
Cat.prototype = new Animal();    // 将父类(Animal)的实例作为子类(Cat)的原型
var tom = new Cat();

cat.eat('fish')     // 弹出 'fish'

console.log(tom instanceof Animal);     //true
console.log(tom instanceof Cat);        //true

# 对象冒充继承

  1. call
    格式:fun.call(obj2,参数1, 参数2...)
    本质上来说,call方法实际上就是要改变fun函数内的this指向。
function Animal () {
  this.eat = function(food){
    alert(food)
  }
}
Animal.prototype.say = function(sound){
    alert(sound)
}
function Cat () {
    this.name = "tom";
    Animal.call(this)
}

var cat = new Cat ();
cat.eat('鱼');  //OK
cat.say("喵喵"); //Error 对象冒充只能继承构造函数上的属性与方法

对象冒充继承方式只能继承构造函数上的属性与方法,不能继承原型上属性与方法。 2. apply
用法基本与call相同,函数的参数通过数组传递
格式:fun.apply(obj2,[参数1, 参数2...])

function Animal () {
  this.eat = function(food){
    alert(food)
  }
}
Animal.prototype.say = function(sound){
    alert(sound)
}
function Cat () {
    this.name = "tom";
    Animal.apply(this)
}

var cat = new Cat ();
cat.eat('鱼');  //OK
cat.say("喵喵"); //Error 对象冒充只能继承构造函数上的属性与方法

# ES6类继承

通过extends关键字实现类与类之间的继承,然后实例化子类,来实现继承。详见下一节

# 原型链

# 原型链

继承是面向对象编程中讨论最多的话题。很多面向对象语言都支持两种继承:接口继承和实现继承。 前者只继承方法签名,后者继承实际的方法。接口继承在 ECMAScript 中是不可能的,因为函数没有签 名。实现继承是 ECMAScript 唯一支持的继承方式,而这主要是通过原型链实现的。

通过原型链访问对象属性与方法顺序为:构造函数-->构造函数原型-->原型链

原型链:
当访问对象的属性或方法时,该属性或方法会在对象本身调用,对象本身没有则去对象本身的构造函数调用,本身构造函数没有则去父类的构造函数调用、父类的原型...以此类推,直到寻找至Object、以及Object的原型、null。最后属性不存在时会得到undefined,方法不存在则会报错。

ECMA-262 把原型链定义为 ECMAScript 的主要继承方式。其基本思想就是通过原型继承多个引用 类型的属性和方法。重温一下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型有 一个属性指回构造函数,而实例有一个内部指针指向原型。如果原型是另一个类型的实例呢?那就意味 着这个原型本身有一个内部指针指向另一个原型,相应地另一个原型也有一个指针指向另一个构造函 数。这样就在实例和原型之间构造了一条原型链。这就是原型链的基本构想。

  Object.prototype.say=function(){
      alert("Object的方法");
  }
  function person(){
      this.say=function(){
          alert("person的方法");
      }
  }
  person.prototype.say=function(){
      alert("person原型的方法");
  }
  function student(){
      this.say=function(){
          alert("student的方法");
      }
  }
  student.prototype=new person();
  var xiaoming=new student();
  xiaoming.say=function(){
      alert("xiaoMing的方法");
  }
  xiaoming.say();