原型和原型链JavaScript常被描述为一种基于原型的语言——每个对象都有一个原型对象,对象以自己的原型为模板,从对象继承方法和属性原型。一个原型对象也可能有一个原型,并从中逐层继承方法和属性,等等。这种关系通常被称为原型链(prototypechain),它解释了为什么一个对象会有在其他对象中定义的属性和方法。更准确地说,这些属性和方法是在对象的构造函数的原型属性上定义的,而不是对象实例本身。在传统的OOP中,首先定义一个“类”,然后在创建对象实例时,将类中定义的所有属性和方法复制到实例中。在JavaScript中,这不是这样复制的——而是在对象实例和它的构造函数(__proto__属性,派生自构造函数的原型属性)之间建立链接,然后沿着原型链往上走,在构造函数中找到这些属性和方法。那么,究竟什么是原型?让我们从对象的属性开始吧!在JavaScript世界中,一切都是对象。可以说,一个JS对象就是一个动态属性的“封装”(指自身的属性)。//从一个函数创建一个对象f,它本身有属性a和b:letFoo=function(){this.a=1;这个.b=2;this.doSomthng=function(){console.log('你好');}}/*这是同一个函数Foo(){this.a=1;这个.b=2;this.doSomthng=function(){console.log('你好,f');}}*/letf=newFoo();//{a:1,b:2}//我们也可以给函数添加属性Foo.c=3Foo.doSomething=function(){console.log('hello,Foo');}我怎么知道哪些属性对象有?后续写一篇文章专门介绍对象的属性。这里我们使用Object.getOwnPropertyNames来获取对象本身的属性://对象自身的属性是指直接定义在对象上的属性,而不是从对象的原型继承的属性。//对象属性包括字段(对象)和函数。//`Object.getOwnPropertyNames`方法返回可枚举和不可枚举的属性和方法的名称。Object.getOwnPropertyNames(f)//['a','b','doSomthng']Object.getOwnPropertyNames(Foo)//['length','name','arguments','caller','prototype','c','doSomething']这里的c,doSomething是为Foo手动添加的,其余都是Foo函数创建时自带的属性。注意Foo的属性中有一个prototype原型属性,就是我们要讨论的函数原型对象。原型prototype和__proto__在JavaScript中,每个实例对象都有一个私有属性(称为[[Prototype]],相当于JavaScript的非标准但许多浏览器实现的属性__proto__。函数在JS中也是对象,允许有属性。所有的函数都会有一个特殊的属性——原型。继承的属性和方法在原型属性上定义。functiondoSomething(){}console.log(doSomething.prototype);//不管函数的声明方式如何,JavaScript中的函数总是有一个默认的原型属性。vardoSomething=function(){};console.log(doSomething.prototype);/*{构造函数:?doSomething(),__proto__:{构造函数:?Object(),hasOwnProperty:?hasOwnProperty(),isPrototypeOf:?isPrototypeOf(),propertyIsEnumerable:?propertyIsEnumerable(),toLocaleString:?toLocaleString(),toString:?toString(),valueOf:?valueOf(),__proto__:null}}*/我们可以给doSomething函数的原型对象添加新的属性,并使用new操作符创建一个基于此原型对象的doSomething实例对象。如下:doSomething.prototype.foo="bar";console.log(doSomething.prototype);/*{foo:"bar",constructor:?doSomething(),__proto__:{constructor:?Object(),hasOwnProperty:?hasOwnProperty(),isPrototypeOf:?isPrototypeOf(),propertyIsEnumerable:?propertyIsEnumerable(),toLocaleString:?toLocaleString(),toString:?toString(),valueOf:?valueOf(),__proto__:null}}*/vardoSomeInstancing=newdoSomething();doSomeInstancing.prop="一些值";//在实例中添加属性console.log(doSomeInstancing);console.log(doSomeInstancing.foo);//'bar'/*{prop:"somevalue",__proto__:{foo:"bar",constructor:?doSomething(),__proto__:{构造函数:?Object(),hasOwnProperty:?hasOwnProperty(),isPrototypeOf:?isPrototypeOf(),propertyIsEnumerable:?propertyIsEnumerable(),toLocaleString:?toLocaleString(),toString:?toString(),valueOf:?valueOf(),__proto__:null}}}*/如上所示,doSomeInstancing中的__proto__是doSomething.prototype,doSomeInstancing继承自doSomethingfoo属性原型chainprototypechain其实每个实例对象的__proto__属性都指向其构造函数的原型对象(prototype)。原型对象也有自己的原型对象(__proto__),层层递进直到某个对象的原型对象为null。根据定义,null没有原型,充当此原型链中的最后一环。JavaScript中几乎所有的对象都是位于原型链顶端的Object的实例。为了更清楚的理解,这里贴一张原型相关内容的图片。从图中任意一个对象开始,可以沿着原型属性__proto__找到一个原型链,沿着原型链查找对象属性(下面会以constructor属性为例)。//f-->Foo.prototype-->Object.prototype-->nullf.__proto__===Foo.prototype//truef.__proto__.__proto__===Object.prototype//truef.__proto__.__proto__.__proto__===null//true//F-->Function.prototype-->Object.prototype-->nullF.__proto__===Function.prototype//trueF.__proto__.__proto__===Object.prototype//trueF。__proto__.__proto__.__proto__===null//true//o-->Object.prototype-->null//trueo.__proto__===Object.prototype//trueo.__proto__.__proto__===null//true//扩展它://Array-->Function.prototype-->Object.prototype-->nullArray.__proto__===Function.prototype//trueArray.__proto__.__proto__===Object.prototype//trueArray.__proto__.__proto__.__proto__===null//true从上图我们可以知道原型对象有一个构造函数对象,指向原始构造函数。函数的原型对象和函数本身通过prototype属性和constructor属性形成循环引用。Foo.prototype.constructor===Foo//trueFunction.prototype.constructor===Function//trueObject.prototype.constructor===Object//true既然原型对象有constructor属性,那么普通对象有吗?Object.getOwnPropertyNames(Foo.prototype)//['constructor']Object.getOwnPropertyNames(f)//['a','b','doSomthng']f.constructor/*?(){this.a=1;这个.b=2;this.doSomthng=function(){console.log('你好');}}*/乍一看,普通对象f没有自己的构造函数属性,但是如果你尝试访问它,你可以得到一个结果,这不是我们定义的函数Foo!确实,f是由函数Foo通过new构造出来的,但是f并没有自己的constructor属性。我们看到的结果是f是从它的原型链中得到的(这就是图中虚线代表构造函数关系的原因)。函数对象的构造函数是一样的!f.constructor===f.__proto__.constructor//truef.constructor===Foo//trueObject.constructor===Object.__proto__.constructor//trueObject.constructor===Function//trueFunction.constructor===Function.__proto__.constructor//trueFunction.constructor===Function//trueJavaScript对象有一个指向原型对象的链。当尝试访问对象的属性时,它不仅会在对象上搜索,还会在对象的原型上搜索,然后在对象原型的原型上搜索,依此类推,直到找到具有匹配名称的属性或到达原型在链的末端,如果直到末端都没有找到对应的属性,则断言该属性不存在,并给出该属性值未定义的结论。prototype和Object.getPrototypeOf:prototype用于类(函数),而Object.getPrototypeOf()用于实例(实例),两者功能相同。functionA(){}A.prototype.doSomething=function(){console.log('A')}a1=newA()a1.doSomething()//执行`a1.doSomething()`相当于执行A.prototype.doSomething.call(a1)Object.getPrototypeOf(a1).doSomething.call(a1)instanceof运算符instanceof运算符用于检测构造函数的原型属性是否出现在实例对象的原型链上。functionFoo(){};varf1=newFoo();f1.__proto__===Foo.prototype;//特别是true:ObjectinstanceofObject//trueFunctioninstanceofFunction//trueObjectinstanceofFunction//trueFunctioninstanceofObject//true因为:Object.__proto__.__proto__===Object.prototype//trueFunction.__proto__===Function.prototype//trueObject.__proto__===Function.prototype//trueFunction.__proto__.__proto__===Object.prototype//true在原型链上查找属性很耗时并且对性能有副作用,这是在性能关键的情况下很重要。此外,尝试访问不存在的属性将遍历整个原型链。要检查对象是否具有自己定义的属性,而不是其原型链上的属性,您必须使用所有对象都继承自Object.prototype的hasOwnProperty方法。创建对象和生成原型链的不同方式1.使用语法结构leto={a:1};//o继承了Object.prototype上面的所有属性//o本身没有名为hasOwnProperty的属性//hasOwnProperty是Object.prototype的一个属性//所以o继承了Object.prototype的hasOwnProperty//Object.prototype的原型为null//原型链如下://o--->Object.prototype--->nullvara=[1,2,3];//数组都是继承自Array.prototype//(Array.prototype包含indexOf,forEach等方法)//原型链如下://a--->Array.prototype--->Object.prototype--->nullfunctionf(){return2;}//函数继承自Function.prototype//(Function.prototype包含调用等方法,bind,etc.)//原型链如下://f-->Function.prototype--->Object.prototype--->null2.使用构造函数创建的对象在JavaScript中,一个constructor实际上是一个普通的函数。当使用new运算符作用于这个函数时,可以称之为构造器(constructor)。函数图(){this.vertices=[];this.edges=[];}Graph.prototype={addVertex:function(v){this.vertices.push(v);}};varg=newGraph();//g是生成的对象,它自己的属性包括'vertices'和'edges'。//g.[[Prototype]]在实例化g时指向Graph.prototype。//g继承了addVertex方法。new关键字将执行以下操作:创建一个空的简单JavaScript对象(即{});为步骤1新建的对象添加属性__proto__,并将此属性链接到构造函数Object的原型;使用步骤1中新创建的对象作为this的上下文;如果函数不返回对象,则返回this。functionmyNew(Fn,...args){constobj={}obj.__proto__=Fn.prototype//constobj=Object.create(Fn.prototype);constres=Fn.apply(obj,args);返回resinstanceof对象?res:obj}3.使用Object.create创建的对象ECMAScript5中引入了一个新方法:Object.create()。可以调用此方法来创建新对象。新对象的原型是调用create方法时传入的第一个参数。vara={a:1};//a--->Object.prototype--->nullvarb=Object.create(a);//b--->a--->Object.prototype--->nullconsole.log(b.a);//1(继承)varc=Object.create(b);//c--->b--->a--->Object.prototype--->nullvard=Object.create(null);//d--->nullconsole.log(d.hasOwnProperty);//未定义,因为d没有继承Object.prototype4。使用类关键字创建的对象ECMAScript6引入了一组新关键字来实现类。使用基于类的语言的开发人员会发现这些结构很熟悉,但又有所不同。JavaScript仍然基于原型。这些新关键字包括class、constructor、static、extends、super。class只是ES6的语法糖。"usestrict";classPolygon{constructor(height,width){this.height=height;this.width=宽度;}}classSquareextendsPolygon{constructor(sideLength){super(sideLength,sideLength);}getarea(){返回this.height*this.width;}setsideLength(newLength){this.height=newLength;this.width=newLength;}}varsquare=newSquare(2);总结一下原型链的内容比较难理解是的,里面的概念很容易混淆,记住以下几点,在上图前面写代码的时候梳理一下关系,相信你一定能尽快拿到JS原型和原型链!要搞清楚__proto__、原型和构造函数之间的关系,你需要知道JS中对象和函数的关系。函数实际上是一种对象。__proto__,构造函数属性是对象独有的;原型属性是函数独有的;函数也是对象的一种,所以函数也有属性__proto__,constructor
