原型在JavaScript中,有两个原型,分别是 prototype 和 proto_注:在ECMA-262第5版中管这个 proto 叫 [[Prototype]]prototype 属性:这是一个显式原型属性,只有函数才拥有该属性。proto 属性:这是每个对象都有的隐式原型属性,指向了创建该对象的构造函数的原型。什么是函数?什么是对象?创建函数有两种方式:①、通过 function 关键字定义②、通过 new Function其中函数又可以分为普通函数和构造函数,两者唯一的区别就是调用的方式不同,语法上没有差异function normalFn(){} // 普通函数(函数名首字母小写)function StructFn(){} // 构造函数(函数名首字母大写)创建对象的方式多种多样,其中传统方法是通过构造函数来创建对象,使用 new 关键字即可创建let obj = new StructFn() // obj就是一个对象在JS中,万物都是对象,函数也属于对象,只不过函数相对于对象有着更为精确的定义原型初探为了证明 prototype属性 是函数独有,而__proto__是每个对象都有的,我们可以测试以下代码:function a(){}console.log(a.prototype); // {constructor: ƒ}console.log(a.proto); // ƒ () { [native code] }let obj = new Object()console.log(obj.prototype) // undefinedconsole.log(obj.proto) // {constructor: ƒ, defineGetter: ƒ,  …}可以看到对象的 显式原型(prototype)为undefined注意:undefined 和 null 同属于对象,但是它们都没有原型,为什么? 在JavaScript中,目前只有两个只有一个值的数据类型,那就是 undefined 和 null由于这两种数据类型有且只有一个值,并且没有方法,所以自然而然就没有原型了。有的同学会问,那NaN呢?NaN是属于Number数据类型的,属于对象,只有_proto 属性console.log(undefined.proto); // 报错:Uncaught TypeError: Cannot read property ‘proto’ of undefinedconsole.log(null.proto); // 报错:Uncaught TypeError: Cannot read property ‘proto’ of nullconsole.log(NaN.proto) ; // Number {0, constructor: ƒ, toExponential: ƒ,  …}构造函数创建对象,其中发生什么?1.创建了一个新对象2.将新创建的对象的隐式原型指向其构造函数的显式原型。3.将this指向这个新对象4.返回新对象注意看第二条:将新创建的对象的隐式原型指向其构造函数的显式原型也就是说 对象._prototype === 构造函数.prototypefunction fn(){}let obj = new fn();console.log(obj.proto === fn.prototype); // true那么这样,我们在为构造函数添加原型方法时,就可以通过两种方法取访问了fn.prototype.more = function(){console.log(‘fn-prototype-more’)}// 1、通过构造函数的显式原型fn.prototype.more()// 2、通过对象的隐式原型obj.proto.more()原型链原型链:实例与原型之间的链接先来看一个例子:function Abc(){}Abc.prototype.fn = function(){console.log(“Abc-prototype-fn”)};let obj = new Abc()obj.fn() // Abc-prototype-fn从上面的代码我们可以看到。构造函数 Abc 和对象实例 obj 都没有fn这个方法,之所以对象obj能够访问到Abc的原型方法,是通过原型链来寻找,借用了 proto 这个属性所以以下的代码也是等价的obj.fn() // Abc-prototype-fnobj.proto.fn() // Abc-prototype-fn再来看一个复杂的例子:function Abc(){ this.fn = function(){console.log(“Abc-fn”)};}Abc.prototype.fn = function(){console.log(“Abc-prototype-fn”)};Object.prototype.fn = function(){console.log(“Object-fn”)};let obj = new Abc()obj.fn = function(){console.log(“obj-fn”)};obj.fn()这里有4个重名的fn函数,分别是:①、实例对象obj的方法②、构造函数Abc的方法③、构造函数Abc原型上的方法④、Obecjt对象原型上的方法为了表示方法的寻找过程,我画了一幅很丑陋的图,大家不要介意哈!在寻找某个方法或者属性的时候,会先从自身对象寻找;如果没有,则会去构造函数寻找;注意这里还没用到原型链;紧接着,会到构造函数的原型上寻找,此时就是对象Abc通过 proto 属性进行寻找接下来,会到Object对象的原型寻找,Object对象是所有对象之父,可以说所有的对象都继承了Object,此时构造函数通过_proto 属性找到了Object的prototype最后的最后,由于Object.proto 指向了null,这也就是原型链的末端第一道原型链是 obj.proto ,访问到了Abc的prototypeconsole.log(obj.proto === Abc.prototype) // true第二道原型链是 obj.proto.proto ,访问到了Object的prototypeconsole.log(obj.proto.proto === Object.prototype) //true第三道原型链是 obj.proto.proto.proto 访问到了Object的prototype.proto,,最后指向了nullconsole.log(obj.proto.proto.proto === null) //true这样我们就可以看到了整个原型链的流程了