美文网首页React NativeWeb前端之路前端笔记
不可不知的JavaScript面向对象

不可不知的JavaScript面向对象

作者: 全凭一口仙气儿活着 | 来源:发表于2017-05-06 19:39 被阅读3820次

    很开心,最近收获了很多知识,而且发现很多东西,以前理解的都是错的或者是肤浅的,还以为自己真的就get到了精髓,也很抱歉会影响一些人往错误的道路上走,不过这也告诉了我们,看任何一篇文章都不能盲目的去相信,要实践验证再验证。今天就重新整理一下,我对面向对象的理解,之所以叫这个文集叫升职记,是想表达我们对事物的理解总是在不断改变,提升。抛砖引玉,希望能带来一些新的感悟。

    对象的创建:

    1 创建一个对象
    var obj = new Object(); 
    obj.name = 'haha';
    obj.showName = function(){ 
      alert(obj.name);
    }
    obj.showName();
    

    <strong>缺点</strong>:当我们想创建多个面向对象的时候,重复代码过多,需要封装,所以有了工厂方法。

    2 工厂方式

    function CreatePerson(name){ 
       var obj = new Object();   //原料
       obj.name = name;         //加工
       obj.showName = function(){
         alert(this.name);
     } 
       return obj;//出厂
    }
    var p1 = CreatePerson('haha');
    p1.showName();
    var p2 = CreatePerson('hehe');
    p2.showName();
    //其实就是简单的封装函数,整个过程像工厂的流水线,所以叫工厂方式
    

    <strong>缺点</strong>:无法识别创建的对象的类型。因为全部都是Object,没有区分度,不像Date、Array等,因此出现了构造函数模式。

    3 构造函数模式

    
    function CreatePerson(name){ 
       this.name = name; 
       this.showName = function(){ 
         alert(this.name);
       } 
    } 
    var p1 =new CreatePerson('haha'); 
    p1.showName();
    var p2 = new CreatePerson('hehe'); 
    p2.showName();
    

    我们通过这二个方面来改变:
    1 <strong>函数名首字母大写</strong>
    这是为了区别于普通的函数,构造函数本身其实就是普通的函数,只是我们专门用它来实现了构造的功能,所以专门起了一个名字叫构造函数,任何函数都可以成为构造函数,这取决于你调用函数的方式,当使用了New的方式调用就成了构造函数。
    2 <strong>New 关键字调用</strong>
    调用函数的时候用了 New关键字,那么New到底做了什么?用不用New有什么区别?再来看下面的例子

    function CreatePerson(name){
       this.name = name;
       this.showName = function(){
         alert(this.name); 
       }; 
        console.log(this);
    } 
    new CreatePerson('haha'); //CreatePerson
    CreatePerson('haha');  //window
    

    我们会发现当用New去调用一个函数的时候,this的指向会不一样。其实New主要做了下面这些事,不过下面写的只是大概的行为,并不是内部源码。

    function CreatePerson(name){ 
       var obj = {}; //声明一个空对象obj 
       obj._proto_= CreatePerson.prototype;
       //把这个对象的_proto_属性指向构造函数的原型对象,这样obj就可以调用CreatePerson原型对象下的所有方法 ,这里原型先知道结论,下面会讲。
        CreatePerson.apply(obj);   //用apply方法让this指向obj对象
        this.name = name;   //obj对象添加属性,方法
        this.showName = function(){ 
           alert(this.name);
          }; 
        return obj;//返回这个对象
    }
    

    函数构造模式存在的问题:

    alert(p1.showName==p2.showName);//false
    

    <strong>缺点:</strong>可见这两个对象并不是共用一个方法,每new一次,系统都会新创建一个内存,这两个对象各自有各自的地盘,但他们具有相同的功能,还不共用,肯定不是我们所希望的。所以就有了下一种方法,原型+构造模式

    4 原型+构造模式

    原型:每个函数都有一个prototype属性,它是一个对象,也称作原型对象,我们可以把方法和属性写在它上面(不过原型对象不仅仅有我们写的属性和方法,还有别的,下面会介绍),而通过这个函数创建出来的实例对象,都能共享这个原型对象下的方法和属性。所以我们只需要把想要共享的东西放在函数的prototype下,不想共享的东西通过构造函数来创建就可以了。
    看个栗子(原型+构造)

    function CreatePerson(name){ 
      this.name = name;
    }
    CreatePerson.prototype.showName = function(){ 
       alert(this.name);
    }
    var p1 =new CreatePerson('haha');
    p1.showName();
    var p2 = new CreatePerson('hehe');
    p2.showName();
    alert(p1.showName==p2.showName);//true
    

    测试为true,可见showName()方法是共享的,也就是说他们共用一个内存,更进一步的说它们存在引用关系,也就是说你更改了p1的showName也会影响p2的showName。

    _proto_属性:
    同一个函数造出来的实例对象能共享这个函数的prototype下的方法和属性,但是它是如何做到的呢?这里要出场的就是_proto_属性.
    每个实例化对象都有_proto_属性,它是一个指针,指向函数的prototype,也就是保存了它的地址。(JS中任何对象的值都是保存在堆内存中,我们声明的变量只是一个指针,保存了这个对象的实际地址,所以有了地址就能找到对象),
    所以总得来说,每个实例化对象都有_proto_属性,保存了构造函数的原型对象的地址,通过这个属性就可以拥有原型对象下的所有属性和方法,_proto_属性实际就是实例化对象和原型对象之间的连接

    原型链
    每个函数都可以成为构造函数,每个函数都有原型对象,每个原型对象也可以是一个实例化对象,比如,你创建了一个函数fun,它是构造函数function的实例化对象,而function的原型对象,又是Object的实例对象。所以fun有个_proto_属性可以访问到function的原型对象,function原型对象也是个实例对象,也有个_proto_属性,可以访问到Object的原型对象,所以通过_proto_属性,就形成了一条原型链。每个实例化对象都可以访问到链子上方的方法和属性,所以fun是可以访问Object原型对象下的方法和属性的。实际上所有对象都可以访问到Object的原型对象。

    原型链的访问规则:先在自身的下面寻找,再去一级一级的往原型链上找。
    如下:

    function Aaa(){}
    Aaa.prototype.num = 3;
    var a1 = new Aaa();
    a1.num =10;
    alert(a1.num); //10
    

    原型对象
    原型对象下可能有三种属性:
    1 原型对象所带方法和属性 2 constructor 3_proto_属性
    constructor:构造函数属性,每个函数的原型对象都有的默认属性,指向函数。
    每个实例化对象本身是没有constructor属性的,他们下面默认只有一个_proto_属性,用来连接原型对象,而和构造函数本身是没有直接的联系的。所以它的constructor是访问的原型对象上的。所以当原型对象的constructor变化了,实例化对象的constructor也会改变。但是如果这个对象本身既是原型对象,又是实例化对象,那就拥有了constructor属性,无需从原型对象上面访问。**

    看下面的例子,来验证我们所说的:

    function CreatePerson(name){ 
       this.name = name;
    }
    CreatePerson.prototype.showName = function(){ 
       console.log(this.name);
     };
    var p1 =new CreatePerson('haha');
    p1.showName();
    console.log(p1.constructor); // CreatePerson 来自CreatePerson.prototype
    
    console.log(CreatePerson.prototype); 
    // {showName:{},constructor:CreatePerson,__proto__:Object.prototype}
    //可见,原型对象保存了
          1 自身添加的方法,
          2 构造函数constructor 
          3 _proto_(和上一层构造函数原型对象的连接)
    
    console.log(CreatePerson.prototype.__proto__===Object.prototype);
    // true 这个原型对象本身又是object的实例化对象,所有_proto_指向Object的原型对象
    
    console.log(CreatePerson.prototype.__proto__===Object);
    // false 可见是和构造函数下原型对象的连接,不是构造函数
    
    console.log(CreatePerson.prototype.constructor);
    //CreatePerson CreatePerson.prototype是Object实例化对象,也是原型对象,所以自身拥有constructor属性
    
    console.log(Object.prototype.__proto__); 
    // null 原型链的终点是null
    
    console.log(CreatePerson.__proto__); //function.prototype
    // CreatePerson本身既是构造函数又是function的实例化对象,拥有_proto_属性,指向function的原型对象
    
    console.log(CreatePerson.constructor); 
    // function 继承自function.prototype
    
    console.log(CreatePerson.prototype instanceof CreatePerson ) 
    //验证是否在一条原型链上 false
    
    

    字面量法定义原型
    为了创建对象的代码更方便,你一定见过这样的代码,就是字面量法:

    function Aaa(){}
    Aaa.prototype = { 
      showName:function(){},
      showSex:function(){}
    }; 
    var a1 = new Aaa();
    console.log(Aaa.prototype);
    //{showName:function(){},_proto_} 
    //你会发现constructor不见了,因为这种方式相当于重新赋值了Aaa.prototype 
    
    console.log(Aaa.prototype.constructor);
    //Object 因为自身没有了constructor属性,就去上级原型对象找,找到了Object
    console.log(a1.constructor );
    //Object 也变了,验证了它是访问的原型对象上的
    

    因此我们在写的时候需要修正一下原型的指向:

    function Aaa(){}
    Aaa.prototype = { 
    constructor:Aaa, 
    num1:function(){alert(10);}
    }
    var a1 = new Aaa();
    a1.constructor    // Aaa
    

    理解了这些,以后的继承就很好理解了,未完待续。如有错误,欢迎纠正。

    相关文章

      网友评论

      • atmel:赞,说的通俗易懂:smile: ,但是个人觉得有一个小点不是很严谨,【每个函数都有原型对象】,Function.prototype也是一个函数,但是此函数就没有prototype对象
      • Kuro233333:讲得太好了,人又长得好看,粉了粉了!
      • unfind:写的很好,将复杂的东西简单化了。
      • Ni_c746:写得很明白,之前一直觉得原型继承难以理解,这次看完感觉理解得很彻底。加油加油!
      • 3d998a98ec8e:目前看过的最清晰易懂的讲解!赞!
      • 7308916fa705:上班时间,偷偷过来学习是不是不太好!
        全凭一口仙气儿活着:@一缕风88532 哈哈
      • 9479120912a5:继续,坐等更新
        全凭一口仙气儿活着: @娄亚辉 谢谢支持😁
      • 其实杰伦:小姐姐写的很好啊 清晰明了:smirk:
        全凭一口仙气儿活着: @其实杰伦 😁

      本文标题:不可不知的JavaScript面向对象

      本文链接:https://www.haomeiwen.com/subject/kjkytxtx.html