美文网首页程序员@IT·互联网
javascript之原型和原型链

javascript之原型和原型链

作者: 飘落的枫 | 来源:发表于2017-09-26 17:20 被阅读182次

    由于接触项目不是很大,所以很少用到js一个流弊的技术栈:继承,对,就是我们常说的prototype,原型及由原型组成的一个链,原型链
    一直想分析为什么利用prototype就可以实现js的继承了!!!,接下来小伙伴们就和我一起来探讨这个比较深奥的问题哈

    一.第一步还是先来看原型的概念:

    1、Prototype:
    对该对象的对象原型的引用。对于所有的对象,它默认返回 Object 对象的一个实例。(来自w3c)
    尼玛,这个看的有的晕,把句子拆开一句一句分析:(1)明确了Prototype是一个引用。(2)这个引用指向的就是原型对象
    2.proto
    针对js对象提供的一个指向原型的指针,该属性并不标准,实际工作中并不会用到,所以称其为隐藏的原型

    二、prototype的作用

    针对上面的概念,我们用代码来演示一遍,来进行进一步的理解

    function Demo(){
       
    }
    Demo.prototype.name = "xiaoming";
    var demo = new Dem0();
    demo.name //xiaoming
    demo.__proto__;//{name: "xiaoming", constructor: ƒ}
    

    如上我们定义了一个构造函数Demo(也就是js中的类,为了区分普通函数我们一般将构造函数首字母进行大写),每一个函数在定义的时候都会拥有自己的一个prototype属性(除Function.prototype.bind),而该属性指向的是自己的原型对象,即Demo.prototype;
    我们首先定义了一个构造函数Demo,然后在其原型对象上定义了一个属性name并且赋值为xiaoming(注意:在一个原型对象中会默认有两个属性,一个是construct,一个是proto,后面会针对这两个属性来细讲)。
    然后执行new操作进行对象的实例化(接下来也会对new这个过程进行拆分),结果demo.name可以访问到在构造函数原型上定义的属性,如何访问到的了?
    我们输出demo.proto结果指向了一个对象,该对象和Demo.prototype对象一样,然后我们来证明一下,两者是否真的为同一个对象,执行demo.proto === Demo.prototype 结果返回 true,看来两者是一样的,即实列化的对象demo可以通过proto来访问Demo构造函数上的原型对象,而prototype和proto就是所谓的原型指针(原型),他们所指向的就是原型对象。
    解释prototype后,我们来看看prototype在的作用,看如下代码:

    function Car(color,size){
      this.color = color;
      this.size = size;
    }
    Car.prototype.action = function(){//共用的属性放到原型上
       console.log('run....')
    }
    var bmw = new Car('blue',200);//定义了一辆车
    var audi = new Car('white',230);//定义了另一辆车
    bmw.action  === audi.action //true
    bmw.__proto__ === audi.__proto__ //true
    audi.__proto__ === Car.prototype // true
    
    为什么要把共用的东西放到prototype上面了?

    1.节省内存
    在进行实列化的时候都会将构造函数里的属性都执行一边,意思就是每进行一次new操作,就会对属性进行一次初始化,这样就会占用相应的内存空间,而将公共的属性放到prototype上面,就只在函数初始话的时候开辟了一个内存,在new的过程中不会重新开辟新的地址(题外话:那么这个构造函数在实例话后算不算是形成了一个闭包了?)
    2.实列话的对象可以天然的继承(哈哈,不需要你去进行任何操作)
    因为通过构造函数实列话后的对象,它的proto指向的就是该构造函数的原型对象,所以无论你实列化多少个 他们的proto都是指向该构造函数的原型对象,即它们都继承了该属性
    为了加强理解,我们在看一个例子

     function OldCar(){
         this.color = "black";
      }
    function Car(color,size){
      this.color = color;
      this.size = size;
    }
    Car.prototype = new OldCar();
    Car.prototype.action = function(){//共用的属性放到原型上
       console.log('run....')
    }
    var car1 = new Car();
    car1._proto_ === Car.prototype //true
    

    //看下car1._proto指向的具体内容

    1.png

    好奇怪,怎么这个原型指向的构造函数变成了OldCar ,不应该是Car吗?
    Car.prototype = new OldCar();这个操作后,就会Car原型指针指向了OldCar实列化后的对象上,所以访问car1._proto就相当于访问OldCar的实列,所以car1._proto.constructor 就是OldCar了

    聊聊new、constructor .原型链

    上面说的也许听起来不是很明白,接下来,我们将上面出现的几个名词进行解释,这样就能对原型有一个深入的理解,所以接下来将会解释 construct 、proto和prototype的关系 ,以及new的过程中内部具体发生了什么,然后看一个代码实列。
    (1)constructor
    w3c定义:constructor 属性返回对创建此对象的数组函数的引用。
    还是一如既往的不好理解,不要担心,还是老办法拆开一句一句理解
    首先constructor 是一个属性(即原型对象中存在的一个属性),然后这个属性指向的是创建该对象的函数。

    function Person(){
    }
    Person.prototype.age = 38;
    var person = new Person();
    person._proto_.constructor ;//返回的是Person这个函数(因为person._proto_===Person.prototype所以两者的原理是一样的的)
    //看看下面的构造函数指向谁
    function Father(){
    }
    Father.prototype.name = "jhon";
    function Son(){
    }
    Son.prototype = new Father();
    var son = new Son();
    son.__proto__.constructor//  function Father(){}
    

    上面的列子很好的说明了constructor指向的是创建该对象的函数,因为Son函数的原型指向的是 father这个对象实列,所以返回的constructor即为在father上查找constructor 所以返回值为Father函数
    (2)new的过程中发生了啥
    1、在每次我们定义对象的时候,我们会使用一个new操作符去实现一个对象的实列化,即使我们平时使用 var demo = {},这种使用字面量来声明一个对象时。其实它内部仍然使用了new操作符

    var demo = {};
    //相当于 var demo = new Object();
    

    2、接下来我们来剖析剖析new的过程中到底发生了 什么,看如下代码

    function Demo(){
     this.name = "xiaoming";
    }
    //直接调用
    var demo = Demo();
         demo//undefined,这个很好理解,如果一个函数中没有返回值时默认返回的为undefined
    //进行实例化
    var demo = new Demo();//注意两者直接仅仅是多了一个new操作符
    demo//Demo {name: "xiaoming"};返回的是一个对象
    
    //将上面的函数进行更改,给他一个返回值看看有什么变化
    function Demo(){
     this.name = "xiaoming";
     return age = 3
    }
    //直接调用
    var demo = Demo();
    demo// 3
    //实例化
    var demo = new Demo();
    demo//Demo {name: "xiaoming"};返回的仍然是一个对象
    //在把返回值变为一个对象 看看有木有变化
    function Demo(){
          this.name = "xiaoming";
         return{
           age:3
         } 
    }
    //直接调用
    var demo = Demo();
    demo// {age: 3}返回的是一个对象,木有问题
    //实例化
    var demo = new Demo();//这个会返回啥,是仍然不变还是已经变化了?
    demo//{age: 3}返回的对象发生了变化
    
    通过上面的几个例子我们可以得出如下结论:

    (1)、如果构造函数没有返回值时,直接执行时,返回的为undefined,进行new操作符后返回的为一个对象。
    (2)、如果构造函数返回值为一个原始值时,进行new操作符后,返回值和(1)中返回的一样
    (3)、如果构造函数返回值为一个引用值是(比如对象,数组)进行new操作符后,返回值和直接执行这个函数返回的值一样
    3、看看new的过程中this的指向

    function Demo(){
        this.name = "jhon";
        this.age =18;
       console.log("当前的this指向的是"+this);
     }
    //直接执行函数
    Demo();//当前的this指向的是[object Window],指向的为window
    //new实例化
    var demo = new Demo();
    demo//指向的为当前的实例化后返回的对象 Demo {name: "jhon"}
    

    也就是说在new的过程中,构造函数将this指向调用者。。。这个new就先聊这么多,嘎嘎,也只是提到了一些基本的。

    (3)原型链
    1、在上面我们已经知道了这两个都是原型的引用,只不过prototype是针对函数,而proto指的是对象(除null,undefined.和自定义创建的空对象,因为以上对象是没有proto属性的)
    在js中,如果在访问一个对象属性的时候,在当前实列下没有找到该属性,那么他就会沿着proto原型去到上一层去查找

    2.png
    我们看到father这个实列是没有age属性的,但是我们却可以访问到age这个属性,这是因为当没有在当前实列下该属性,就会通过proto去向上进行查找。
    说道这里就可以说说什么是原型链了,如上图所示,在father.proto对像中还存在一个proto,而这个proto指向的就是最原始的对象Object,而这个Object.proto指向的为null
    3.png
    这样就形成了一个原型查找的链,即所谓的原型链,这就是js对象在进行属性和方法进行查找时所采用的一个方法,沿着原型链从底层一层一层向上查找。
    这是我的理解,也许理解的不到位,说实话要将这个讲清楚,不是区区这点内容就可以的,这里相当于自己的一个总结,互相学习,互相促进~~~

    ...

    相关文章

      网友评论

        本文标题:javascript之原型和原型链

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