__proto__和prototype

前言

该系列文章将带你全面理解js对象和原型链,并用es5去实现类以及认识es6中class的美妙。该系列一共有3篇文章:javascript

  • __proto__prototype来深刻理解JS对象和原型链
  • javascript实现类与继承
  • 初始ES6class

此篇为第一篇__proto__prototype来深刻理解JS对象和原型链java

prototype__proto__

何为原型

引用《JavaScript权威指南》的一段描述es6

Every JavaScript object has a second JavaScript object (or null ,
but this is rare) associated with it. This second object is known as a prototype, and the first object inherits properties from the prototype.
复制代码

翻译过来就是:每个JS对象必定关联着另一个JS对象(也许是null,可是它必定是惟一的)。这个另外的对象就是所谓的原型对象。每一个对象从它的原型对象中继承属性和方法。浏览器

若是你是初学者,这句话确定很绕吧(不过它确实描述得很精辟)。不要紧,你只须要先把握如下两点就行了:bash

  • 在JS里,万物皆对象。方法(Function)是一个对象,方法的原型(Function.prototype)是对象。函数

  • JS有三种构造对象的方式ui

    • 经过对象字面量this

      var person1 = {
          name: 'Jzin',
          sex: 'male'
      }
      复制代码
    • 经过构造函数es5

      function Person(name, sex) {
          this.name = name;
          this.sex = sex;
      }
      var person1 = new Person('Jzin', 'male');
      复制代码

      所谓构造函数就是:能够经过它来new出一个对象实例的函数。一般构造函数里都用了this,由于这样子才会给调用它的对象绑定属性。spa

    • 由函数Object.create构造

      var person1 = {
          name: 'Jzin',
          sex: 'male'
      }
      var person2 = Object.create(person1);
      复制代码

    这三种方法的异同到后面还会继续分析,如今你只需掌握如何构建对象就好啦。

以上两点就是这小结你要掌握的东西:

  • 万物皆对象的思想
  • 如何构造对象

至此,我还没介绍什么是原型,不过不要紧,咱们先看看原型的分类,慢慢你就会理解了。

原型的分类

JS的原型分红两类:隐式原型和显示原型

显式原型(explicit prototype property)

当你建立一个函数时,JS会为这个函数(别忘了:JS一切皆对象)自动添加prototype属性,这个属性的值是一个对象,也就是原型对象(即函数名.prototype)。这个对象并非空对象,它拥有constructor属性,属性值就是原函数。固然,你也能够本身在原型对象中添加你须要的属性(即函数名.prototype.属性名=属性值).

那么原型对象(prototype)的做用是什么呢?能够用原型对象来实现继承,即经过函数构造出来的实例能够直接访问其构造函数的原型对象中的属性。可能有点绕,可是读到后面你就会理解啦。

须要注意的是:

  • 显式原型(prototype)只有函数才拥有。咱们后面讲的隐式原型则是全部对象都有。
  • 经过Function.prototype.bind方法构造出来的函数是个例外,它没有prototype属性。
隐式原型( implicit prototype link)

JavaScript中任意对象都有一个内置属性[[prototype]],在ES5以前没有标准的方法访问这个内置属性,可是大多数浏览器都支持经过__proto__来访问。如今,所谓的隐式原型就是__proto__ 了。

  • 隐式原型的指向

    隐式原型指向建立这个对象的函数的prototypeObject.create函数构造出来的实例有点例为,后面会说明。其实也不是例为,只是它通过了必定的封装)。看下面的例子

    function person(name) {
        this.name = name;
    }
    person.prototype.class = 'Human';
    var person1 = new person('Jzin');
    
    console.log(person1.__proto__); //person { class: 'Human' }
    console.log(person.__proto__);  //[Function]
    复制代码

    person1__proto__很容易理解:它是由person方法构造的实例,它的__proto天然就是person.prototype

    person__proto__呢?其实每个方法的构造方法都是Function方法,也就是全部方法的__proto__都是Function.prototype。若是如今还不理解也不要紧,后面会有一副图帮你理解。

  • 隐式原型的做用

    • 构成原型链,一样用于实现基于原型的继承。举个例子,当咱们访问obj这个对象中的x属性时,若是在obj中找不到,那么就会沿着__proto__依次查找。这也是protorype能够实现继承的缘由。
    • 能够用来判断一个对象(L)是不是某个函数(R)的实例:只需判断L.__proto__.__proto__ ..... === R.prototype这个是否为真就好了。这也是instanceof运算符的原理。后面会讲到。

一张图带你形象理解__proto__prototype

先上图,若是图片显示不了能够点击这里:传送门

咱们来理解一下这幅图:

  1. 构造函数Foo()
    • 构造函数Foo的原型属性prototype指向了它的原型对象Foo.prototype。原型对象Foo.protoype中有默认属性constructor指向了原函数Foo
    • 构造函数Foo建立的实例f2,f1__proto__指向了其构造函数的原型对象Foo.prototype,因此Foo的全部实例均可以共享其原型对象的属性。
    • 构造函数Foo实际上是Function函数建立的实例对象,因此它的__proto__就是Function函数的原型对象Function.prototype
    • 构造函数Foo的原型对象实际上是Object函数建立的实例对象,因此它的__proto__就是Object函数的原型对象Object.prototype
  2. Funtion函数
    • 你所写的全部函数,其实都是Function函数构造的实例对象。因此全部函数的__proto__都指向Fucntion.prototype
    • Function函数对象是由它自己建立(姑且能够这么理解),因此Function.__proto__d等Function_prototype
    • Function函数的原型对象实际上是Object函数建立的实例对象,因此它的__proto__就是Object函数的原型对象Object.prototype
  3. Object函数
    • Object函数实际上是Function函数建立的实例对象,因此它的__proto__就是Function函数的原型对象Function.prototype
    • 须要注意的是:Object.prototype__proto__是指向null的!!!

相信你经过这幅图,已经对原型有本身的理解了,咱们来总结一下:

  • 对象有__proto__属性,指向该对象的构造函数的原型对象。
  • 方法除了有__proto__属性,还有prototype属性,prototype指向该方法的原型对象。

深刻理解__proto__的指向

相信通过上面的介绍,你已经能很好地掌握__proto__的指向了。本节经过一些实际的例子让你更加深刻地理解__proto__的指向。

在一开始,咱们了解了构造对象的三种方式:(1)对象字面量的方式 (2)new的方式 (3)ES5中的Object.create()。其实,这三种方式在我看来都是一种的,即经过new来构建。为何这么说呢?咱们来仔细分析分析:

  1. 经过字面量构造对象

    var person1 = {
        name: 'Jzin',
        sex: 'male'
    }
    复制代码

    其实这种方式只是为了开发人员更方便建立对象的一个语法糖(语法糖:顾名思义,就是很甜的糖,通过代码封装,让语法更加人性化,实际的内部实现是同样的)。

    上面也就等价于:

    var person1 = new Object();
    person1.name = 'Jzin';
    person1.sex = 'male';
    复制代码

    person1Object函数构造的对象,因此person1.__ptoto__就指向Object.prototype

    也就是说,经过对象字面量构造出来的对象,其__proto__都是指向Object.prototype

  2. 经过构造函数

    function Person(name, sex) {
        this.name = name;
        this.sex = sex;
    }
    var person1 = new Person('Jzin', 'male');
    复制代码

    经过new操做符调用的函数就是构造函数。由构造函数构造的对象,其__proto__指向其构造函数的原型对象。

    在本例中,person1.__proto__就指向Person.prototype

  3. 由函数Object.create构造

    var person1 = {
        name: 'Jzin',
        sex: 'male'
    }
    var person2 = Object.create(person1);
    复制代码

    由函数Object.create(obj)构造出来的对象,其隐式原型有点特殊:指向obj.prototype。在本例中,person2.__proto__指向person1

    这是为何呢?咱们来分析一下。在没有Object.create函数的日子里,为了实现这一功能,咱们须要这样子作:

    Object.create = function(p) {
        function F(){}
        F.prototype = p;
        return new F();
    }
    var f = Object.create(p);
    复制代码

    这样子也就是实现了其功能,分析以下:

    // 如下是用于验证的伪代码
    var f = new F();	//var f = Object.create(p);
    // 因而有
    f.__proto__ === F.prototype	//true
    // 又由于
    F.prototype === p;	//true
    // 因此
    f.__proto__ === o	//true
    复制代码

    所以由Object.create(p)建立出来的对象它的隐式原型指向p。

经过上面的分析,相信你对原型又进一步理解啦。咱们再来几题玩玩。

  1. 构造函数的显式原型的隐式原型

    • 内建对象(built-in object)的的隐式原型

      好比Array()Array.prototype.__proto__指向什么?

      Array.prototype.__proto__ === Object.prototype //true
      复制代码

      好比Function()Function.prototype.__proto__指向什么?

      Function.prototype.__proto__ === Object.prototype //true
      复制代码

      根据上面那幅图,这些也很简单啦。

  2. 自定义对象

    • 默认状况下

      function Foo(){}
      var foo = new Foo()
      Foo.prototype.__proto__ === Object.prototype //true 
      foo.prototype.__proto__ === Foo.prototype //true 
      复制代码

      理由,就没必要解释了吧

    • 其余状况

      1. function Bar(){}
        function Foo(){}
        //这时咱们想让Foo继承Bar
        Foo.prototype = new Bar()
        
        Foo.prototype.__proto__ === Bar.prototype //true
        console.log(Foo.prototype.constructor);	//[Function: Bar]
        复制代码
      2. function Foo(){}
        //咱们不想让Foo继承谁,可是咱们要本身从新定义Foo.prototype
        Foo.prototype = {
          a:10,
          b:-10
        }
        //这种方式就是用了对象字面量的方式来建立一个对象,根据前文所述 
        Foo.prototype.__proto__ === Object.prototype
        console.log(Foo.prototype.constructor);	//[Function: Object]
        复制代码

      注意:以上两种状况都等于彻底重写了Foo.prototype,因此Foo.prototype.constructor也跟着改变了,因而constructor这个属性和原来的构造函数Foo也就切断了联系。

instanceof

instanceof的左值通常是一个对象,右值通常是一个构造函数,用来判断左值是不是右值的实例。instanceof操做符的内部实现机制和隐式原型、显式原型有直接的关系,它的内部实现原理是这样的:

//设 L instanceof R 
//经过判断
 L.__proto__.__proto__ ..... === R.prototype ?
//最终返回true or false
复制代码

也就是沿着L的__proto__一直寻找到原型链末端,直到等于R.prototype为止。知道了这个也就知道为何如下这些奇怪的表达式为何会获得相应的值了

Function instanceof Function //true
Function instanceof Object // true 
Object instanceof Function // true 
Object instanceof Object // true
Number instanceof Number //false
Number instanceof Function //true
Number instanceof Object //true
复制代码

你发现没有:这就是原型链啊!!!

L1.__proto__指向R1.prototype

R1.prototype.__proto__指向R2.prototype

...

Rn.prototype.__proto__指向Object.prototype

Object.prototype.__proto__指向null

这样子就把原型串起来啦,也就是实现了继承。也就是为何全部对象都要toString方法,由于这个方法在Object.prototype上面啊啊啊啊。

总结

至此,相信你已经彻底理解原型和原型链了。固然,只是理解不实践是没用的。在下一篇,咱们将利用原型来实现类与继承。

相关文章
相关标签/搜索