美文网首页前端攻城狮工具前端
最详尽的 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