美文网首页前端攻城狮工具前端
最详尽的 JS 原型与原型链终极详解,没有「可能是」。(一)

最详尽的 JS 原型与原型链终极详解,没有「可能是」。(一)

作者: Yi罐可乐 | 来源:发表于2016-08-20 13:22 被阅读66239次

    第二篇已更新,点击进入
    第三篇已更新,点击进入

    三篇文章都更新完毕,完整的剖析了 JS 原型与原型链,希望通过这些教程能让你对 Javascript 这门语言理解的更透彻!


    一. 普通对象与函数对象

    JavaScript 中,万物皆对象!但对象也是有区别的。分为普通对象和函数对象,Object 、Function 是 JS 自带的函数对象。下面举例说明

    var o1 = {}; 
    var o2 =new Object();
    var o3 = new f1();
    
    function f1(){}; 
    var f2 = function(){};
    var f3 = new Function('str','console.log(str)');
    
    console.log(typeof Object); //function 
    console.log(typeof Function); //function  
    
    console.log(typeof f1); //function 
    console.log(typeof f2); //function 
    console.log(typeof f3); //function   
    
    console.log(typeof o1); //object 
    console.log(typeof o2); //object 
    console.log(typeof o3); //object
    

    在上面的例子中 o1 o2 o3 为普通对象,f1 f2 f3 为函数对象。怎么区分,其实很简单,凡是通过 new Function() 创建的对象都是函数对象,其他的都是普通对象。f1,f2,归根结底都是通过 new Function()的方式进行创建的。Function Object 也都是通过 New Function()创建的

    一定要分清楚普通对象和函数对象,下面我们会常常用到它。

    二. 构造函数

    我们先复习一下构造函数的知识:

    function Person(name, age, job) {
     this.name = name;
     this.age = age;
     this.job = job;
     this.sayName = function() { alert(this.name) } 
    }
    var person1 = new Person('Zaxlct', 28, 'Software Engineer');
    var person2 = new Person('Mick', 23, 'Doctor');
    

    上面的例子中 person1 和 person2 都是 Person 的实例。这两个实例都有一个 constructor (构造函数)属性,该属性(是一个指针)指向 Person。 即:

      console.log(person1.constructor == Person); //true
      console.log(person2.constructor == Person); //true
    

    我们要记住两个概念(构造函数,实例):
    person1 和 person2 都是 构造函数 Person 的实例
    一个公式:
    实例的构造函数属性(constructor)指向构造函数。

    三. 原型对象

    在 JavaScript 中,每当定义一个对象(函数也是对象)时候,对象中都会包含一些预定义的属性。其中每个函数对象都有一个prototype 属性,这个属性指向函数的原型对象。(先用不管什么是 __proto__ 第二节的课程会详细的剖析)

    function Person() {}
    Person.prototype.name = 'Zaxlct';
    Person.prototype.age  = 28;
    Person.prototype.job  = 'Software Engineer';
    Person.prototype.sayName = function() {
      alert(this.name);
    }
      
    var person1 = new Person();
    person1.sayName(); // 'Zaxlct'
    
    var person2 = new Person();
    person2.sayName(); // 'Zaxlct'
    
    console.log(person1.sayName == person2.sayName); //true
    

    我们得到了本文第一个「定律」:

    每个对象都有 __proto__ 属性,但只有函数对象才有 prototype 属性
    

    那什么是原型对象呢?
    我们把上面的例子改一改你就会明白了:

    Person.prototype = {
       name:  'Zaxlct',
       age: 28,
       job: 'Software Engineer',
       sayName: function() {
         alert(this.name);
       }
    }
    

    原型对象,顾名思义,它就是一个普通对象(废话 = =!)。从现在开始你要牢牢记住原型对象就是 Person.prototype ,如果你还是害怕它,那就把它想想成一个字母 A: var A = Person.prototype


    在上面我们给 A 添加了 四个属性:name、age、job、sayName。其实它还有一个默认的属性:constructor

    在默认情况下,所有的原型对象都会自动获得一个 constructor(构造函数)属性,这个属性(是一个指针)指向 prototype 属性所在的函数(Person)

    上面这句话有点拗口,我们「翻译」一下:A 有一个默认的 constructor 属性,这个属性是一个指针,指向 Person。即:
    Person.prototype.constructor == Person


    在上面第二小节《构造函数》里,我们知道实例的构造函数属性(constructor)指向构造函数person1.constructor == Person

    这两个「公式」好像有点联系:

    person1.constructor == Person
    Person.prototype.constructor == Person
    

    person1 为什么有 constructor 属性?那是因为 person1 是 Person 的实例。
    那 Person.prototype 为什么有 constructor 属性??同理, Person.prototype (你把它想象成 A) 也是Person 的实例。
    也就是在 Person 创建的时候,创建了一个它的实例对象并赋值给它的 prototype,基本过程如下:

     var A = new Person();
     Person.prototype = A;
    

    结论:原型对象(Person.prototype)是 构造函数(Person)的一个实例。


    原型对象其实就是普通对象(但 Function.prototype 除外,它是函数对象,但它很特殊,他没有prototype属性(前面说道函数对象都有prototype属性))。看下面的例子:

     function Person(){};
     console.log(Person.prototype) //Person{}
     console.log(typeof Person.prototype) //Object
     console.log(typeof Function.prototype) // Function,这个特殊
     console.log(typeof Object.prototype) // Object
     console.log(typeof Function.prototype.prototype) //undefined
    

    Function.prototype 为什么是函数对象呢?

     var A = new Function ();
     Function.prototype = A;
    

    上文提到凡是通过 new Function( ) 产生的对象都是函数对象。因为 A 是函数对象,所以Function.prototype 是函数对象。

    那原型对象是用来做什么的呢?主要作用是用于继承。举个例子:

      var Person = function(name){
        this.name = name; // tip: 当函数执行时这个 this 指的是谁?
      };
      Person.prototype.getName = function(){
        return this.name;  // tip: 当函数执行时这个 this 指的是谁?
      }
      var person1 = new person('Mick');
      person1.getName(); //Mick
    

    从这个例子可以看出,通过给 Person.prototype 设置了一个函数对象的属性,那有 Person 的实例(person1)出来的普通对象就继承了这个属性。具体是怎么实现的继承,就要讲到下面的原型链了。

    小问题,上面两个 this 都指向谁?

      var person1 = new person('Mick');
      person1.name = 'Mick'; // 此时 person1 已经有 name 这个属性了
      person1.getName(); //Mick  
    

    故两次 this 在函数执行时都指向 person1。


    2017-10-27 更新:
    下面的评论有喷子喷我:「null 没有 _proto_」,下面解释一下:
    null 是一个独立的数据类型,为什么typeof(null)的值是"object"?

    1. null不是一个空引用, 而是一个原始值, 参考ECMAScript5.1中文版** 4.3.11节; 它只是期望此处将引用一个对象, 注意是"期望", 参考 null - JavaScript**.
    2. typeof null结果是object, 这是个历史遗留bug, 参考 typeof - JavaScript**
    3. 在ECMA6中, 曾经有提案为历史平凡, 将type null的值纠正为null, 但最后提案被拒了. 理由是历史遗留代码太多, 不想得罪人, 不如继续将错就错当和事老, 参考 harmony:typeof_null [ES Wiki]
      作者:克荷林链接:https://www.zhihu.com/question/21691758/answer/987822

    写个教程不容易,遇到喷子真心不爽。喷子不管那么多,哪怕文章是免费看的。他们只要抓住你一点点的小失误,就兴奋的噼里啪啦一顿喷。


    别着急走,关于原型与原型链还有第二篇第三篇呢~
    第二篇已更新,点击进入
    第三篇已更新,点击进入


    本文部分参照了 JS原型与原型链终极详解 (感谢原作者)(侵立删)
    和《JavaScript 高级程序设计》第三版

    希望通过这一系列课程,能帮助你彻底搞明白 JavaScript 的原型与原型链 :)

    相关文章

      网友评论

      • 16cce8a9ea44:金融软件系统开发,区块链平台搭建,公司技术开发人员50多人,有意微18538590356
      • 8cd19aa0347e:楼主写的很好,很详细
        有个问题 三、原型对象里
        Person.prototype.constructor == Person 我打印出来的值不是true 这有点懵了..
        8cd19aa0347e:@Yi罐可乐 就是手敲的:cry:
        Yi罐可乐:@犹豫的炸酱面 别复制粘贴,手动敲,别敲错了
      • 冉成:我是前端小白,来希望学到点东西。也没法去考究题主写的是否完全正确。刚入门,看到下面有一些讨论。这些都是良性讨论,共同解决疑惑挺好的。只是希望作者可以拿出肯定的东西来,因为像我这种小白,很可能先入为主把你的当做真理记下来了。如果你有一些错误就会对新入门的有一些误导。个人看法。还是很谢谢作者能码这么多东西免费给想学习的人看。
        Yi罐可乐:@冉成 以《JS高级程序设计》为准
      • Dailoge:博主的写的挺好的,但是下面这个结论还是不敢苟同的
        结论:原型对象(Person.prototype)是 构造函数(Person)的一个实例。
        岂不是Person.prototype的__proto__还是指向Person.prototype,然而并不是,应该指向的是Object.prototype, Person.prototype.__proto__ === Object.prototype //true
      • e86d339fb0b6:"先用不管什么是 __proto__ 第二节的课程会详细的剖析" 然后
        我们得到了本文第一个「定律」:
        每个对象都有 __proto__ 属性,但只有函数对象才有 prototype 属性;
        什么逻辑??呵呵
        Yi罐可乐:@我是一号粉刷匠 呵呵,看不懂了吧。
      • 岛在深海处:前半段还行,看到结论:原型对象(Person.prototype)是 构造函数(Person)的一个实例。这个太勉强,如果构造函数里有this.参数呢?实例中不就带有这些属性了,而函数中的this.属性是不会复制到函数原型对象中的。
        var A = new Person();
        Person.prototype = A;
        这两段代码是什么操作???
        岛在深海处:@露泪 对
        21019ee81ae4:@岛在深海处 应该该为:
        person.prototype=A.__proto__
      • 42a34b08cfd5:关于第二大点,
        “这两个实例都有一个 constructor (构造函数)属性,该属性(是一个指针)指向 Person“
        “ console.log(person1.constructor == Person);//true“
        “console.log(person2.constructor == Person); //true”
        我觉得这里的说法有点出入
        如果直接打印person1和person2对象,就会发现并没有发现有constructor属性。打印内容如下
        {age:28
        job:"Software Engineer"
        name:"Zaxlct"
        sayName:ƒ ()
        __proto__:Object}
        那为什么person1.constructor == Person 这个会是true,实际是因为在person1中没有找到constructor属性,顺着__proto__往上,找到了Person.prototype,而在这里才找到的constructor,而这个constructor是指向Person的,所以结果才会是true
        但是这并不能说是实例上有一个constructor属性,我觉得这容易产生误解。
        以上是个人观点,有错误请指正,谢谢。
      • f90349fff199:有很多收获,辛苦作者了。
      • 鱼猫_2e63:真的非常好
      • 随风而遇:知识梳理的有点乱,读到这句话时,我就看不下去了。
        《我们得到了本文第一个「定律」:

        每个对象都有 __proto__ 属性,但只有函数对象才有 prototype 属性》
        易_刺猬:那是你的问题。这里算是提前插入点。并不能代表整个文章结构混乱。
      • Aiibai:写的很好,有一个问题不太理解。
        function Person(){}
        Person.prototype instanceof Person //false
        构造函数的原型对象是构造函数的一个实例,这个有点不明白
        俊桑:@记得要微笑_ec3e Person.prototype 不是 Function么?
        c931734de59c:@Aiibai Person.prototype类型是object
        Person类型是function
      • bozhao:楼主 写的很好 不过我发现一个问题:
        楼主贴出的最后两张图
        new person();
        p应该是大写吧
        Yi罐可乐:@bozhao 谢谢指正
      • 幼兒園滑滑梯:错误的观点要误导别人,什么原型是构造函数的实例,还可以这样理解,自己的知识体系有问题,就别放出这种总结来了。作为新手看的一头雾水,还觉得自己总结的没问题似的。
        Yi罐可乐:@Charles_Wqc 收到
        8518dff9d5a6:其实我觉得楼主是想要解释为什么构造函数的原型对象也有constructor这个属性。不过我觉得楼主你这里说的的确有点问题。var A = new Person(); 这里的new在背后到底帮我们做了哪些事情?
        第一步: let A = { };
        第二步: A.__proto__ = Person.prototype;
        第三步: Person.cal(A);
        第四步: return A。
        并没有你在上面说的Person.prototype = A这么一步流程;如果要说是大致是这么个过程的话,其实也不对,因为他们两个是独立的关系[ 硬要说有关系的话,应该是A包含Person.prototypr ]。至于为什么原型对象上有constructor,我觉得没必要解释,或者可以看一下我在下面37楼评论的回复。它就是内置的默认属性,如果要搞事情的话,可能还会问:为什么对象都有__proto__属性?本来就是js语言内置的默认属性,该怎么解释呢?楼主可以在这个地方考虑修改一下,因为我一开始看到你这个表达式不得不说有点懵逼。。。
        Yi罐可乐:@幼兒園滑滑梯 呵呵,看不懂很难受吧。
      • d4121f113a42:结论:原型对象(Person.prototype)是 构造函数(Person)的一个实例。

        提问:原型对象 是 构造函数 的实例的公有属性与方法是否正确。如果正确的话,是否原型对象 仅仅是 构造函数的实例的一部分呢(公有部分)?
      • b8bfaf5cb6c6:老哥,请问一下
        person1.constructor == Person
        Person.prototype.constructor == Person
        这两个都是true,为什么person1 == Person.prototype;这个却是false呢,我还是稍微有点不明白。请指教一下,谢谢了。
        613f92fbcb55:person1.constructor 和 Person.prototype.constructor 是指针属性,只是同时指向 Person,并不是等于Person
        9b0e0448b1b5:person1.constructor == Person // true
        person2.constructor == Person // true
        person1 == person2 // false
        8518dff9d5a6:通俗一点理解,person1是构造函数Person的克隆体;而Person.prototype是构造函数Person的双腿。克隆体的是有Person构造出来的,所以克隆体的constructor属性指向的是本身(person1.constructor == Person);腿(Person.prototype)是也是构造函数造出来的一部分,所以他的constrcutor也指向构造函数【想不通的话就这么想:你的腿属不属于你?】所以答案也就出来了,你的腿和你的克隆体是等于的关系吗
      • 呵Sever:原型对象(Person.prototype)是 构造函数(Person)的一个实例。
        对这句话有点疑惑, Person.prototype仅仅是一个名为“原型”的对象,但应该不能说是Person的一个实例吧?
        实验:实例的__proto__指向这个“原型”,但Person.prototype里的__proto__并没有指向该“原型”
      • 68773376675b:打通了js之旅的任督二脉,哈哈哈哈
        7e36babcd1ac:@沧海Bryant 恐怕你已经走火入魔了吧?
      • 永恒不灭的梦想:非常感谢,讲的很清楚哦:monkey_face:
      • 花溪的小石头:大话说的。。结果我在里面发现一些问题,其次,许多名词是自己创造。
        所以这篇文章的这个标题还是算了吧。
        Yi罐可乐:@花溪的小石头 哦。
      • b9dfc4079317:在第一篇中这段话貌似写的有问题:
        在上面第二小节《构造函数》里,我们知道*实例的构造函数属性(constructor)指向构造函数 * :person1.constructor == Person。

        我的理解是:
        实例的构造函数属性指向原型的构造函数属性(constructor)指向的构造函数(Person)。

        你看后面有个例子与你说的观点是矛盾的:
        function Person(name) {
        this.name = name
        }
        // 重写原型
        Person.prototype = {
        getName: function() {}
        }
        var p = new Person('jack')
        console.log(p.__proto__ === Person.prototype) // true
        console.log(p.__proto__ === p.constructor.prototype) // false

        建议作者可以及时修改,不然不严谨,对我这样的小白还是有点儿误导作用的。文章挺不错字的,思路清晰。
        卧龙Tristan:好评论,赞赞赞!好文章,赞赞赞!
        Miracletjf:我觉得,你写的有点问题。你在重写原型的时候,也就把Person默认的的constructor指向了Object。你可以把constructor打出来看看。
        Yi罐可乐:@一路可口可乐 你的理解没错。我写的也没问题,你可以看一下《JS高级程序设计第三版》145页。
      • 90b2d8c2c0fc:博主你好 推荐一个小程序,发现->小程序中搜"立问",生成提问码放在文章底部,码字不易,给自己赚点鸡腿。
      • 哼哼哈哈好:上面有一定的误导性,
      • adf10d6126f0:console.log(person1.sayname == person2.sayname); //true
        这里写错了,sayName写成了sayname所以是true,正确应该是false,
        Yi罐可乐:谢谢指正!:clap:
      • 2f1fd6fcf967:var Person = function(name){
        this.name = name; // tip: 当函数执行时这个 this 指的是谁?
        };
        Person.prototype.getName = function(){
        return this.name; // tip: 当函数执行时这个 this 指的是谁?
        }
        var person1 = new person('Mick'); 这里在person中大小写打快了吧 'p'=>'P'
        person1.getName(); //Mick
      • duanduan2088:今天是2017.11.14,怎么是2017.11.27更新啦?
        Yi罐可乐:@android段段 额,笔误。。
      • ee2cdd1c4c57:感谢作者~
        我发现了一处笔误
        function Person() {}
        Person.prototype.name = 'Zaxlct';
        Person.prototype.age = 28;
        Person.prot```e```type.job = 'Software Engineer';
        Person.prototype.sayName = function() {
        alert(this.name);
        }
        :smiley:
        Yi罐可乐: @ee2cdd1c4c57 谢谢指正
      • d336894cc288:你说通过new function 出来的对象都是函数对象?
        function Person(name, age, job) {
        this.name = name;
        this.age = age;
        this.job = job;
        this.sayName = function() { alert(this.name) }
        }
        var person1 = new Person('Zaxlct', 28, 'Software Engineer');
        var person2 = new Person('Mick', 23, 'Doctor');

        这段代码的person1 是普通对象!!!

        怎么解释???
        王涛_bf19:@Yi罐可乐 为什么呢?
        Yi罐可乐:@天晴_ddbe 不需要解释。person 1 不是通过 new function 创建的对象。
      • BulletYuan:var a=function(b){
        this.b=b;
        console.log(this);
        console.log(this.b);
        }
        我用的这段在console里面打印,第一个this打印出来是window对象啊。
        是我没理解对吗?
        BulletYuan:@Yi罐可乐 嗯嗯 只是看到那里的时候 有点奇怪
        Yi罐可乐:@BulletYuan 就是 window 对象。关于this指向的问题不是本文讨论的重点,但却是前端工程师必须掌握的知识,百度查查吧。
      • 最后只剩躯壳:最近在看javascript原型,这篇文章对我很有帮助,感谢整理与分享。
      • 16fbe137f5ce:(上面的例子中 person1 和 person2 都是 Person 的实例。这两个实例都有一个 constructor (构造函数)属性。--------这个好像写的不对,person1 和 person2.其实并没有constructor 这个属性,这是属性是属于Person的原型对象的。只是person1 和 person2和可以继承原型对象的constructor这个属性。还有person1与Person2这两个实例其实与构造函数Person并没有直接关系,person1与Person2是与Person的原型对象才有直接关系。
        (我们要记住两个概念(构造函数,实例):
        person1 和 person2 都是 构造函数 Person 的实例
        一个公式:
        实例的构造函数属性(constructor)指向构造函数。)这个总结好像有问题......
        Yi罐可乐:@天降植物 :smile:
        ca9098f6b38a:大哥你说的 太对了 实例只有__proto__指针 指向构造函数的 prototype 而constructor属性也是其上的 只不过 依靠指针能访问 prototype 把构造函数和实例联系在一起:relaxed:
        Yi罐可乐:对,你说的没错,确实是这样。文章之所以这么说的目的只是为了方面理解。如果上去就说「person1 和 person2没有constructor 这个属性,这是属性是属于Person的原型对象的」这样可能对新手不太友好。
      • 515f271c2f3b:全部看完了,感觉对我原型和原型链的理解太有帮助了,66哒
        Yi罐可乐:@不开心你就吃冰棍啊 嘿嘿,还不赶快点赞!
      • Tang1024:非常欣赏作者这股子自信,可惜啦,我是一个男的哈:stuck_out_tongue_winking_eye:
        王不懂Sir:@戢鳞X 你们也是厉害呀
        Tang1024:@Yi罐可乐 come baby,试试...看咱俩谁掰弯谁:sunglasses:
        Yi罐可乐:我可以把你掰弯!
      • 4506f17ca4f6:只有函数对象才有 prototype 属性??
        是否该为:只有构造函数有prototype 属性!
        a38005ba6889:所有函数都有prototype属性,不要按照Java的构造函数去理解JS的构造函数。JS里的构造函数和普通函数一毛一样。只是隐藏了构造一个Object对象,并return的语句而已。
        Yi罐可乐: @鱼丸里粗面 不用改,两种表达,都对。
      • Hafei:写的好,这点一直懵圈
        Yi罐可乐:@Hafei 谢谢~
      • 9e23980a3134:“JavaScript 中,万物皆对象”——这句话不准确吧。《JS权威指南》里提到:javascript中除了字符串、数字、true、false、null和undefined外都是对象。
        Yi罐可乐:@Cindy_begins 不准确。可以看看《JS 高级程序设计》
        9e23980a3134:@Yi罐可乐 @Yi罐可乐 数值字面量在调用toString 方法时,JS引擎自动把数值字面量转换成Number()对象了。但是数值字面量本身还是数字类型吧?我不知道我这样理解对不对?
        Yi罐可乐: @Cindy_begins 为什么数字类型有toString 这个方法呢?有没有想过这个问题?
      • 4e424a774118:感觉你的这个说话,不准确:
        var A = new Person();
        Person.prototype = A;
        Yi罐可乐:对,这个肯定是不准确的,只是为了让初学者容易理解故意用这种方式写的。
      • a6dd346a9977:应该说所有引用类型都是对象!
      • 386694aeed70:关于原型对象就是构造函数的实例,这句改怎样理解?

        function Person(name){
        this.name = name
        }

        Person.prototype instanceof Person //false

        为什么说是构造函数的实例呢?:flushed:
      • 皮皮鲁与鲁西西:原型并不是构造函数的实例
        Yi罐可乐:@一只环环环 @孤狼之森 这么说的原因是加深理解,嘿嘿
        一只环环环:@Yi罐可乐 原型对象是在函数创建的时候自动获得的一个对象,并不是构造函数的一个实例。原型对象时构造函数的实例这句话感觉不妥当啊。而且如果照此理解,那么原型对象与该类型的实例就没有区别了哦
        Yi罐可乐:@孤狼之森 嗯,不过可以这么理解嘛。
      • 白今:为什么那个可以证明Person.prototype(原型对象)是构造函数的实例啊 即使person1.constructor==Person 可能是person1中没有constructor 然后去Person.prototype里面去找的啊
        我刚学这个 所以可能没完全理解 也没有表达好 求解惑😞
        Yi罐可乐:你尝试把` Person.prototype`想象成一个普通的对象 A ,再思考一下试试?:grin:
      • a508f76b77d4:你将会了我原型,谢谢
        Yi罐可乐:@a508f76b77d4 :blush:
      • songyaqi:排版很棒
        Yi罐可乐:@songyaqi 哈哈,用 MarkDown 编辑的
      • 5e0aebe855eb:写的不错,挺有帮助的。
      • 翻滚的前端程序员:非常好的三篇文章,第一次打赏-,- 虽然只有两块,哈哈 :smile:
        Yi罐可乐:@翻滚的程序员 哈哈,多谢多谢:heart:
      • 海深不蓝_:最近正在学感觉挺有帮助的
        Yi罐可乐:@海深不蓝_ :smile: 如果能耐心看完,肯定有很大的帮助。
      • 小心鲨鱼:先收藏了
      • d86d4f5a98eb:值得收藏

      本文标题:最详尽的 JS 原型与原型链终极详解,没有「可能是」。(一)

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