js原型及原型链

Posted fermin

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了js原型及原型链相关的知识,希望对你有一定的参考价值。

js对象与其它面向对象编程语言的差异

--  JAVA与C++等语言中的对象,是先构造一个类抽象事物,再通过类实例化一个个对象。但javascript中,中不区分类和实例的概念,而是通过原型(prototype)来实现面向对象的封装,继承和多态,从而实现面向对象编程。

什么是面向对象编程?

  • 将现实世界中各种复杂的关系,抽象为一个个对象,由对象的之间的分工和合作,完成对真实世界的模拟
  • 具有灵活性,代码可重用性,模块性等特点,容易维护和开发,适合多人合作的大型软件项目

什么是js原型?

  • 在JavaScript中,原型也是一个对象,原型所有的属性和方法都会被构造函数的实例继承
  • JavaScript的对象中都包含了一个__proto__内部属性,这个属性指向的就是该对象的原型

什么是构造函数?

  • 专门用来生成对象的函数,内部使用了this对象,对构造函数使用new运算符,就能生成实例,并且this变量会绑定在实例对象上
  • 它描述了一些对象的基本结构,是一个模板 ,使生成的对象有相同的结构
  • 以区别普通函数,构造函数名字首字母大写
  • 构造函数中,若return的是原始数据,new命令会忽略该语句返回this对象;若return的是一个和this无关的新对象,new返回的会是该新对象。

什么是原型链?

--  构造函数有prototype属性,会指向一个原型对象,这样通过构造函数生成的实例就会继承来自原型对象的属性和方法,而原型对象又是它所属构造函数形成的实例,会继承来自它的原型对象的属性和方法,这样层层继承,直到原型顶部为null为止,这样原型层层连接起来,就构成了原型链。 如下图所示

技术分享图片

prototype和__proto__区别?

  • 所有的对象,都有__proto__属性,这个属性指向该对象的原型
  • 所有函数对象,除了__proto__属性之外,还有prototype属性,该属性用来设置实例对象的__proto__属性(函数对象作为构造函数创建实例时,函数的prototype属性会赋值给实例属性__proto__指向原型) (构造函数.prototype === 实例对象.__proto__)
  • 所有原型对象都有"constructor"属性,该属性指向  创建了基于该原型的实例对象的  构造函数

什么是封装

--就是将属性和方法封装成一个对象,隐藏属性和方法的实现细节,仅对外公开接口。

js如何封装

简单的封装--原始模式生成实例对象

缺点:

  • 生成实例代码重复过多
  • 实例与原型之间关联不大

eg:

 

<script>
   // 创建一个cat原型对象
   // 把两个属性封装在一个对象里面
    var Cat = {    
      name: ‘‘,
      color: ‘‘    
    };
   // 生成两个实例对象
   var cat1 = {};       // 创建一个空对象   
   cat1.name = "大毛";  // 按照原型对象的属性赋值
   cat1.color = "黄色";    
   var cat2 = {};      
   cat2.name = "二毛";      
   cat2.color = "黑色";
</script>

 

原始模式--用函数生成实例对象

优点:

  • 改进了原始模式的一些代码重复的问题

缺点:

  • 实例与原型之间关联仍不大

eg:

<script>
   function Cat(name, color) {    
     return {      
       name: name,
      color: color     
     };     
   }
   var cat1 = Cat("大毛","黄色"); //调用函数生成实例对象
   var cat2 = Cat("二毛","黑色");
</script>

构造函数模式

优点:

  • 解决了原始模式代码重复的问题
  • 生成的实例对象与原型有着紧密的联系

缺点:

  • 共有不变的属性和方法,在每个实例中都会再次生成,会多占用一些内存。
    <script>
        function Cat(name, color) {    
          this.name = name;    
          this.color = color;  
        }
        var cat1 = new Cat("大毛", "黄色");    
        var cat2 = new Cat("二毛", "黑色");    
        alert(cat1.name); // 大毛    
        alert(cat1.color); // 黄色
        alert(cat1.constructor === Cat); //true 
        alert(cat2.constructor === Cat); //true
        alert(cat1.hasOwnProperty("constructor")); //false; 
        alert(cat1.__proto__.hasOwnProperty("constructor"));
        //true //constructor属性在原型中指向构造函数
    </script>

 

 Prototype模式

优点:

  • 解决了原始模式代码重复的问题
  • 生成的实例对象与原型有着紧密的联系
  • 不变的属性和方法,在原型中被共享,减少了内存的占用,提高了运行效率

 

将不变的属性和方法,直接定义在prototype对象上

<script>
    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(); // 吃鱼
    alert(Cat.prototype.isPrototypeOf(cat1)); //true 
    alert(Cat.prototype.isPrototypeOf(cat2)); //true
    alert(cat1.hasOwnProperty("name")); // true
    alert(cat1.hasOwnProperty("type")); // false 
    alert("name" in cat1); // true
    alert("type" in cat1); // true
</script>

 

继承:子类可以使用父类的所有功能,并且对这些功能进行扩展。继承的过程,就是从一般到特殊的过程。

 

使用构造函数继承:

一  构造函数绑定

--用call或apply方法,将父对象的构造函数绑定在子对象,实现对父对象属性的继承

<script>
    function Animal() {    
      this.type = "动物";  
    }

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

    function Cat(name, color) {      
      Animal.apply(this);    
      this.name = name;      
      this.color = color;     
    }     
    var cat1 = new Cat("大毛", "黄色");   
    alert(cat1.type); // 动物
    alert(cat1.hasOwnProperty(type)); //true type已是实例中自身的属性
</script>

二、 prototype模式

 

<script>
    function Animal() {    
      this.type = "动物";  
    }

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

    Cat.prototype = new Animal();
    // 替换prototype对象(原先值被删除,被赋予一个新值)
    // 此时Cat.prototype.constructor也变为Animal
    Cat.prototype.constructor = Cat; 
    // 将constructor属性指回原来的构造函数
    // 防止继承链的紊乱  
    var cat1 = new Cat("大毛", "黄色");   
    alert(cat1.type); // 动物
    alert(cat1.hasOwnProperty(type)); 
    //false  //type继承Animal一个实例属性,
    //Animal的一个实例为cat1原型
    alert(Animal.isPrototypeOf(cat1));        //false
    alert(Cat.prototype.isPrototypeOf(cat1)); //true
</script>

 

三  直接继承prototype

优点:效率比较高(不用执行和建立Animal的实例了),节省内存

缺点:Cat.prototype和Animal.prototype现在指向了同一个对象,那么任何对Cat.prototype的修改,都会反映到Animal.prototype。

<script>
    function Animal() {}
    Animal.prototype.type = "动物";
    function Cat(name, color) {       
      this.name = name;       
      this.color = color;     
    }
    Cat.prototype = Animal.prototype;
    //不用new Animal();
    Cat.prototype.constructor = Cat;  
    //隐士改变Animal的构造函数变Cat
    var cat1 = new Cat("大毛", "黄色");   
    alert(cat1.type); // 动物
    alert(cat1.hasOwnProperty(type)); //false  
    alert(Animal.prototype.isPrototypeOf(cat1)); //true
    alert(Animal.prototype.constructor); // Cat(){} 
    //确认Animal的构造函数变为Cat
</script>

四、 利用空对象作为中介

<script>
    function Animal() {}
    Animal.prototype.type = "动物";
   
    function Cat(name, color) {       
      this.name = name;       
      this.color = color;      
    }
    
    function extend(Child, Parent) {   // YUI库如何实现继承的方法
      var F = function () {};  // 创建一个空函数对象  
      F.prototype = Parent.prototype;   
      Child.prototype = new F();    
      Child.prototype.constructor = Child;     
      Child.uber = Parent.prototype; 
      // 备用属性指向父对象的prototype属性
      // 实现继承的完备性     
    }
    
    extend(Cat, Animal);  
    var cat1 = new Cat("大毛", "黄色");   
    alert(cat1.type);     // 动物
    alert(cat1.hasOwnProperty(type));  // false  
    alert(Animal.prototype.isPrototypeOf(cat1)); // true
    alert(Animal.prototype.constructor); // Animal(){} 
</script>

 

 

五、 拷贝继承

--  把父对象的所有属性和方法,拷贝进子对象,去实现继承

 

<script>
    function Animal() {}
    Animal.prototype.type = "动物";
   
    function Cat(name, color) {       
      this.name = name;       
      this.color = color;     
    }
//父对象的prototype属性一一拷贝给子对象的prototype属性中
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.type); // 动物 alert(cat1.hasOwnProperty(type)); //false alert(Animal.prototype.isPrototypeOf(cat1)); //false alert(Cat.prototype.isPrototypeOf(cat1)); //true alert(Animal.prototype.constructor); // Animal(){}

</script>

 

非构造函数的继承

将两个非构造函数的普通对象形成继承关

object()方法:

<script>
    //将子对象的prototype属性,指向父对象
    //让子对象与父对象连在一起。
    function object(o) { 
      function F() {}
      F.prototype = o;
      return new F();
    }
    var Chinese = {
      nation: 中国
    };

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

 

<script>
    function SuperType() {
      this.colors = ["red", "blue", "green"];
    }
    function SubType() {
      SuperType.call(this);   //继承了SuperType
    }
    var instance1 = new SubType();
    instance1.colors.push("black");   //实例1中,属性color加一属性值black
    alert(instance1.colors);          //"red,blue,green,black"
    var instance2 = new SubType();
    alert(instance2.colors);          //"red,blue,green"
</script>

 

构造函数模式的一些问题:

  • 共有不变的属性和方法,在每个实例中都会再次生成,会多占用一些内存
  • 可以将共有的属性和方法,在内存中只生成一次,然后所有实例都指向那个内存地址

 

 浅拷贝:只拷贝对象中基本类型的数据 

<script>
    function extendCopy(p) {    
      var c = {};    
      for (var i in p) {      
        c[i] = p[i]; 
        //复制成另一对象的一副本,且对象中的属
        //性若是地址也会相同的复制过来,造成一些属性对象的享
//可能会影响到父对象属性, 
}     c.uber = p;     return c;   } var Chinese = { nation: 中国 }; var Doctor = extendCopy(Chinese);    Doctor.career = 医生;   alert(Doctor.nation); // 中国 </script>

 

深拷贝:

<script>
    function deepCopy(c, p) {   
      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 Chinese = {
      nation: 中国
    };

    var Doctor = deepCopy(Chinese)
</script>

 

原型链继承的一些问题:

  • 当原型变成另一个类型的实例时,该原型会拥有上一类型的属性,会共享下去
  • 创建子类型实例时,没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。

eg:

 

<script>
    function SuperType() {
      this.colors = ["red", "blue", "green"];
    }
    function SubType() {}
    //继承了SuperType
    SubType.prototype = new SuperType();
    var instance1 = new SubType();
    instance1.colors.push("black");
    alert(instance1.colors); //"red,blue,green,black"
    var instance2 = new SubType();
    alert(instance2.colors); //"red,blue,green,black"
    //这样SubType.prototype会有一个colors属性,这样所有的SubType会共享这一实例,造成一些错误,你不知道还好,一直到
</script>

六、 Prototype模式的验证方法

  •  isPrototypeOf() 判断某个proptotype对象和某个实例之间的关系。  //SubType.prototype.isPrototypeOf(incetence1); //true
  •  hasOwnProperty()   判断某一个属性到底是本地属性,还是继承自prototype对象的属性。// incetence1.hasOwnProperty("color"); // true
  • in运算符可以用来判断,某个实例是否含有某个属性,不管是不是本地属性。"color" in inctence1  //true 

 






以上是关于js原型及原型链的主要内容,如果未能解决你的问题,请参考以下文章

js原型及原型链

js原型及原型链

对js原型链及继承的理解:__proto__&prototpye

关于JS面向对象中原型和原型链以及他们之间的关系及this的详解

关于JS面向对象中原型和原型链以及他们之间的关系及this的详解

js原型链继承及调用父类方法