你的位置:首页 > Java教程

[Java教程]JavaScript筑基篇(三)


说明

JS中原型和原型链是很重要的知识点,本文内容则是我对于它的理解。建议读本文时已经有了一点的JS基础。

目录

  • 前言
    • 参考来源
    • 前置技术要求
    • 楔子
  • 起由
    • null开天辟地
  • 前因后果
    • 函数对象、实例对象与原型对象
    • constructor、__proto__与prototype
  • 原型与原型链
    • 区分原型对象与原型链
    • 原型链的作用
    • 原型链的流程与示例

前言

参考来源

前人栽树,后台乘凉,本文参考了以下来源

  • 汤姆大叔:强大的原型和原型链
  • JS原型对象与原型链
  • Js中Prototype、__proto__、Constructor、Object、Function关系介绍
  • 关于JS中的constructor与prototype
  • SF:JS中先有函数还是先有对象

前置技术要求

阅读本文前,建议先阅读以下文章

  • JavaScript变量、值与对象
  • JavaScript数据类型

楔子

学习是有瓶颈的,JS学习也同样,基本上JS的学习中,到了原型与原型链这一步,就会遇到瓶颈,只有真正理解它,才能跨过去,进入新的领域

曾经看过很多网上关于JS原型的介绍,基本上每完整的看完一篇,就会“噢,恍然大悟的样子”。然后仔细想想,发现自己并没有真正的理解。所以这里将自己对应原型的理解写下来,希望能帮助他人快速理解原型。

起由

简单的描述下,原型是什么,如何诞生的。

null开天辟地

"无,名天地之始;有,名万物之母" -引自 《道德经》

  • (无)在JS中,本来也是什么都没有的,只有一个 null
  • (道生一)然后出现了Object.prototype对象
  • (一生二)然后基于Object.prototype产生出了Function.prototype对象

    注意,JS中所有对象都不是函数构造出来的,对象是由"JavaScript运行时环境"以原型对象为模板,直接产生出来的,构造函数只是以新生的对象为this,做一些初始化操作。(-引自参考来源)

  • (二生三)然后基于两个prototype,产生出了两个构造器Object和Function。这时候出现了函数对象的概念,实例对象,原型对象的概念
    • Object,Function都属于函数对象
    • new Object()、new Function()出来的是实例对象

      所以,后面我们一般会认为实例对象是由函数对象构造出来的

    • Object.prototype,Function.prototype是原型,原型对象的作用就是以它为模板产生其它对象,它也和普通实例对象一样,拥有constructor,__proto__属性

      原型对象之所以要有constructor,__proto__属性,可以认为是在产生实例对象时,方便实例对象指向具体的构造函数以及完成一个原型链的作用。

    参考 函数对象、实例对象用原型对象

    参考 constructor、__proto__与prototype

  • (三生万物)然后基于前面的Object,Object.prototype,Function,Function.prototype,产生出了其它各种对象

    可以先简单的理解以上步骤为JS对象的诞生过程(排除基本型的值,因为它们并不属于对象-没有对象的这些特性)

    当然,里面具体Object.prototype、Object、Function.prototype这些内置对象的产生过程是很复杂的,上述只是为了便于理解的一种简单粗暴的概念。

前因后果

函数对象、实例对象与原型对象

再开始理解原型之前我们得先明确一个概念:"函数对象","实例对象","原型对象"

  • 原型对象是对象的原型。原型对象有constructor,__proto__属性

    可以认为所有对象都是原型产生的(万物之母可以认为是Object.prototype)。

  • 函数对象有 prototype,__proto__属性

    如JS内置的Object,Function对象都是函数对象。其中prototype的值就就是它对应的原型对象

  • 实例对象有 constructor,__proto__属性

    如new Object(),new Function()出来的都是实例对象。其中constructor指向它的构造函数,__proto__指向产生它的原型

  • 关系如图

constructor、__proto__与prototype

在我们有了上述概念后,再来分析JS中的constructor,__proto__与prototype

      //Object,Function都是函数对象      var 实例对象 = new 函数对象();        //Person也是函数对象      function Person(name) {        this.name = name;        this.say = function() {          console.log(this.name);        }      };      var one = new Person('test');            console.log(Person.prototype);//{constructor:Person函数,__proto__:Object.prototype}      console.log(Person.prototype.constructor === Person);//true      console.log(Person.prototype.__proto__ === Object.prototype);//true      console.log(Person.prototype.__proto__ === Function.prototype);//false      console.log(Person.__proto__.__proto__ === Object.prototype);//true      console.log(Person.__proto__.constructor === Function);//true      console.log(Person.__proto__.constructor.prototype === Object.__proto__);//true      console.log(Person.__proto__.constructor.__proto__ === Object.__proto__);//true      console.log(Object.__proto__ === Person.__proto__);//true      console.log(Person.__proto__.constructor.prototype === Person.__proto__);//true,相当于自己构建了自己...            console.log(one.prototype); //undefined      console.log(one.constructor === Person); //true      console.log(one.__proto__ === Person.prototype); //true          console.log(Object.prototype.constructor);//Object函数:function Object() { [native code] }            console.log(Function.prototype.__proto__ === Object.prototype);//true      console.log(Function.prototype.constructor === Function);//true                  console.log(Object.__proto__);//Function.prototype      console.log(Function.prototype);//Function.prototype      console.log(Function.__proto__);//Function.prototype      console.log(Object.prototype.__proto__);//undefined            console.log(Function.prototype === Function.__proto__);//true      console.log(Object.__proto__ === Function.__proto__);//true      console.log(Object.__proto__ === Function.prototype);//true      console.log(Function.prototype.constructor === Function);//true      console.log(Function.__proto__.constructor === Function);//true                  console.log(Object.__proto__ === undefined);//false      console.log(Object.prototype.__proto__ === undefined);//true,原型链的尽头为null            var two = {};      console.log(two.constructor === Object);//true      console.log(two.prototype);//undefined      console.log(two.__proto__ === Object.prototype);//true				

如上代码所示,有如下总结

  • 函数对象Person有一个prototype属性,有一个__proto__属性。prototype属性的值是一个prototype对象。prototype有一个constructor属性(为了方便称为 $constructor),有一个__proto__属性(为了方便称为 $__proto__)。

    $constructor属性的值是一个constructor对象。而这个constructor对象恰恰就是这个函数对象本身(Person本身)。

    $__proto__属性的值是Object.prototype(相当于就是函数对象Object的prototype属性)原型链就是基于__proto__字段不断往上找,直到遇到null为止

    __proto__属性的值是Function.prototype,Function.prototype和其它原型对象一样,有一个__proto__属性和constructor属性

    • __proto__属性的值是Object.prototype
    • constructor的值是一个constructor对象。这个对象即为Function
      • Function对象和Object对象一样,也有它的prototype和__proto__。Function的prototype与__proto__的值都是Function.prototype

    注意,__proto__属性的名称并不规范,如Chrome中叫__proto__,但IE中不一定叫这个名字,但是我们一般习惯把它叫成__proto__(但注意是__proto__并不是_proto_)

  • 实例对象one没有prototype属性(所以one.prototype===undefined)。示例对象one有一个constructor属性(为了方便称为 $constructor),有一个__proto__属性(为了方便称为 $__proto__)。

    $constructor属性的值是一个constructor对象。而这个constructor对象恰恰就是这个函数对象本身(Person本身)。

    $__proto__属性的值是Person.prototype(相当于就是函数对象Person的prototype属性)所以现在就构成了一个原型链 one.__proto__ ->Person.prototype;Person.prototype.__proto__->Object.prototype;Object.prototype.__proto__ ->null

  • 如上图中,可以清晰的看到Person函数对象的实例one是由Person构造的,所以one.constructor===Person。同样,普通的object对象two是由Object构造的,所以two.constructor===Object

    由此可以看出,Function对象 原型链上有Function.prototype和Object.prototype,所以 Function是Function类型的,也是Object类型的。另外Object对象的原型链上有Function.prototype和Object.prototype,所以Object是Function类型的,也是Object类型的

    从上,我们还可以得到一个概念: "Object.prototype是所有对象的原始原型对象(所有对象的原型链最终都会指向它);Function.prototype是所有函数对象的原始原型对象(所有函数对象的原型链最终都会指向它),而Function.prototype的原型链指向Object.prototype"

    注意,Object,Function对象的产生是很复杂的,里面甚至涉及到了自己构建自己,这里只是简化版本,详情请参考MDN...

原型与原型链

区分原型对象与原型链

原型对象

  • 如前面提到的JS内置对象Object.prototype,Function.prototype等就是原型对象,原型对象的作用是可以以它为原型产生其它对象。每一个原型对象都有一个隐藏的属性(如在chrome中是__proto__,不同浏览器实现不同),这个属性的值是另一个原型对象

原型链

  • 原型链是一个概念
  • 每一个JS的实例对象都有一个__ptoto__属性,这个属性指向产生它的原型对象,然后就像前面提到的,每一个原型对象也有一个__proto__属性,指向与产生它的原型
  • 就这样,从实例->原型1->...->原始原型(Object.prototype)->null。这样就组成了一条链,这个就是原型链
  • JavaScritp引擎在访问对象的属性时,如果在对象本身中没有找到,则会去原型链中查找,如果找到,直接返回值,如果整个链都遍历且没有找到属性,则返回undefined

原型链的作用

原型链的一个最大的作用就是,可以基于它,实现JS中的继承。

因为JS在ES6之前,是没有Class这个概念的,只能通过原型链来进行实现。

原型链的流程与示例

原型链的原理就是基于__proto__属性不断的往上查找,下面介绍下一些原型链的用法示例

示例一

      var base = {        name: 'base',        say: function(){          console.log(this.name);        }      };      var one = {        name: 'one',        __proto__:base      };      var two = {        __proto__:base      };      console.log(one.name);//one      one.say();//one      console.log(two.name);//base      two.say();//base    		

代码分析:

  • 以上代码中的base是由Object.prototype产生的,所以base.__proto__的值为Object.prototype
  • one和two原本也是由Object.prototype产生的,所以本来__proto__也是指向Object.prototype的,但是这里手动修改了这个指向,变为指向base了
  • 所以就有了两个原型链:(one -> base ->Object.prototype),(two -> base -> Object.prototype)
  • 然后根据原形链的规则,现在本对象上找属性,没有的话再根据原形链指向一层一层往上找,直到找到null返回undefined为止。

    所以才会有以上的输出结果。one.name是one自身的属性,one.say()是上一级原型链base的属性,two.name,say()都是上一级base的属性。

  • 可以总结为如图所示(去除__ptoto__之外的干扰因素)

示例二

      function Base(name){        this.sex = 0;        this.name = name || 'base';        this.hello = function(){          console.log("hello " + name);        };      }      Base.prototype.say = function(){        console.log('name:'+this.name);      };      function Extend(name,num){        //让Base能初始化属性        Base.call(this,name);        this.num = num || 0;      }      //注意,这里是new Base()而不是Base.prototype      //因为new Base()是新建了一个对象,这样可以不会影响到Base.prototype      //否则如果直接操作Base.prototype,会污染Base.prototype      Extend.prototype = new Base();      //前面由于将prototype变为了new Base()所以构造方法默认是Base的      //这里需要手动替换回来      Extend.prototype.constructor = Extend;      var one = new Extend('one',2);            console.log(Extend.__proto__);      console.log(one instanceof Extend);//true      console.log(one instanceof Base);//true      console.log(one.constructor === Extend);//true      console.log(one.__proto__ === Extend.prototype);//true                  console.log(one.name);//one      console.log(one.sex);//0      console.log(one.num);//2      one.say();//name:one      one.hello();//hello one		

代码分析:

  • 上述代码在进行原型链修改前,有如下原型链

    一条链为:new Extend() -> Extend.prototype ->Object.prototype -> null

    一条链为: new Base() -> Base.prototype ->Object.prototype -> null

    一条链为: Extend -> Function.prototype ->Object.prototype -> null

    一条链为: Base -> Function.prototype ->Object.prototype -> null

  • 修改原型后,有了一条完整的继承链(针对于实例对象而言,相当于上述的第一条拓展了)

    new Extend() -> Extend.prototype -> Base.prototype ->Object.prototype -> null

  • 而根据原形链,所以上述的代码会有这些输出

    one是Extend的实例对象,所以one自身找不到时,会沿着原型链往上找,知道原型链的尽头都没有找到,则返回null

  • 可以总结为如图所示(去除干扰因素)