介绍Javascript原型链
Prototype 普通实例(对象)和函数实例(对象) 首先搞清楚一个概念 : 实例
我更喜欢用“实例(instance)”而不是“对象(object)”,当阅读到时,知道是一种东西即可.
简单来说: 实例 是通过 构造函数 new 出来的.
Javascript中一切都可以是实例,但是实例之间又有所不同:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 function Foo ( ){} let foo = new Foo (); let Bar = new Function (); let ob1 = {} let ob2 = new Object ();console .log (typeof foo);console .log (typeof ob1);console .log (typeof ob2);console .log (typeof Foo );console .log (typeof Bar );console .log (typeof Object );console .log (typeof Date );console .log (typeof Function );
1.2.3都比较好理解. 4.5.6.7:这是由于Javascript的定义:所有函数 (例如上面自定义的Foo,Bar,内置的Object,Date甚至Function本身)都是由Function
创建的实例.
可以看到正常的创建一个实例的方法为: 调用 new constructor()
Javascript 也提供了一些语法糖.可以用来创建实例,例如 function Foo(){}
或者 let obj = {}
,可以简单认为,其本质还是通过new来创建的.
最终得出的概念是: 实例或者对象就是 let foo = new Foo();
中通过new后面的Foo(构造函数)构造出来的东西
同时对象可以分为普通实例和函数实例:
foo、ob1、ob2 等称为普通实例.通过诸如 Foo()、 Object() 等构造函数构造出来.
Foo、Bar、Object 等比较特殊,称为函数实例 : 既可以作为构造函数(可以new实例),本身又是个实例,通过构造函数Function构造造出来,例如 let Bar = new Function()
或者 function Foo(){}
构造函数(函数实例) 当使用 let foo = new Foo()
的时候 Foo 就是 foo 的构造函数:
Javascript有个比较奇怪的地方foo是个Foo的实例(foo instanceof Foo),但是foo的类型(typeof foo)却是Object
这个暂时想不明白为什么(可能是弱类型的原因),因此在描述实例的”类型”,会尽量避免使用”类型”这一概念,而使用”实例的构造函数”.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function Foo ( ) {}let foo1 = new Foo ();let foo2 = new Foo ();console .log (`foo1的构造函数为 : ${foo1.constructor} , foo1和foo2的构造函数相等 : ${foo1.constructor === foo2.constructor} , 这是因为foo1,foo2的构造函数都是 Foo : ${foo1.constructor === Foo && foo2.constructor === Foo} , 即foo1 instance of Foo : ${foo1 instanceof Foo} , 即foo2 instance of Foo : ${foo2 instanceof Foo} , ` );let ob = {};console .log (`实例都有对应的构造函数: ob : ${ob.constructor} , Foo : ${Foo.constructor} , Object : ${Object .constructor} , Foo Object同时也作为函数实例,因此它们的构造函数相同,都是Function : ${Foo.constructor === Object .constructor && Object .constructor === Function } ` );
prototype 和 proto 很多人搞不清prototype和__proto__的关系,两句话描述 :
prototype是属于构造函数的,用来访问构造函数的原型
__proto__是属于实例的,用来访问这个实例的构造函数的原型
__proto__标准应该为属性:[[Prototype]],但是目前主流浏览器以及Node都为实例实现了getter:__proto__这个 非标准功能 .
即prototype和__proto__ 都是用来访问一个特殊实例的,这个实例就是某个构造函数的原型(这个原型并不是通过构造函数构造的一个实例).
1 2 3 4 5 6 7 8 function Foo ( ) {}let foo = new Foo ();console .log (Foo .prototype );console .log (foo.__proto__ );console .log (`foo.__proto__ 等于 Foo.prototype(foo由Foo构造):${foo.__proto__ === Foo.prototype} ` );console .log (`foo.__proto__ instance of Foo : ${foo.__proto__ instanceof Foo} ` ); console .log (`foo.__proto__ instance of Object : ${foo.__proto__ instanceof Object } ` ); console .log (`foo instance of Foo : ${foo instanceof Foo} ` );
下图可以看出__proto__和prototype的区别与联系
原型链 Javascript 原型的存在,目的是实现继承.当一个实例上一个属性不存在时,会去__proto__这个实例上去寻找,如果找不到会继续找取__proto__的__proto__去找…. 直到为空(实际如果在没有修改原型链的情况下,最终会找到Object.prototype的__proto__上,Object.prototype.__proto===null)
可以参照上图:
1 2 3 4 5 function Foo ( ){}let foo = new Foo ();console .log (`${foo.__proto__} ` ); console .log (`${foo.__proto__.__prpto} ` ); console .log (`${foo.__proto__.__prpto.__proto__} ` );
普通对象和原型链 先看一段代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function Foo (ag = 10 ) { this .age = ag; } Foo .prototype .name = 'foo.prototype' ;Foo .prototype .alert = function ( ) { console .log (`from ${this .name} : age is ${this .age} ` ); }let f1 = new Foo (11 );f1.name = 'f1' ; let f2 = new Foo (13 );f1.alert (); f2.alert (); Foo .prototype = { name : 'foo.prototype2' , alert : function ( ) { console .log (`来自 ${this .name} : 年龄是 ${this .age} ` ); } };Object .prototype .alert2 = function ( ) { console .log (`来自 Object.prototype : ${this .age} !!!!!!!` ); };let f3 = new Foo (16 );f1.alert (); f2.alert (); f3.alert (); f1.alert2 (); f2.alert2 (); f3.alert2 ();
代码1: f1 f2 本身没有 alert方法 但是通过原型链 找到 f1.__proto__即 Foo.prototype 上有该方法,因此可以调用
代码2: 找到原型链的最后都没有找到 alert 因此不可调用
代码3: 这里首先要说明 new 操作做了什么事情: let foo = new Foo()
相当于三个步骤:
1 2 3 let foo = {};foo.__proto__ =Foo .prototype ; Foo .call (foo);
因此,foo.__proto__并不会随着 Foo.prototype的改变而改变
常用对象原型链:
new Array()._proto _ => Array.prototype
new Array()._proto ._proto = Array.prototype._proto _ => Object.prototype
new Array()._proto ._proto ._proto _ = Array.prototype._proto ._proto = Object.prototype._proto _ => null
new Object()._proto _ => Object.prototype
new Object()._proto ._proto = Object.prototype._proto _ => null
函数对象和原型链 函数对象原型链
function Foo(){}
Foo._proto _ => Function.prototype
Foo._proto ._proto = Function.prototype._proto _ => Object.prototype
Foo._proto ._proto ._proto _ = Function.prototype._proto ._proto = Object.prototype._proto _ => null
1 2 3 4 function Foo ( ) {}console .log (Object .getOwnPropertyNames (Foo ));console .log (Object .getOwnPropertyNames (Function .prototype ));console .log (Object .getOwnPropertyNames (Object .prototype ));
proto with class ES6 引入了关键字 class,其本质还是跟原型链有一定关系的.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 "use strict" ;class Polygon { constructor (height, width ) { this .height = height; this .width = width; } get area () { return this .height * this .width ; } } class Square extends Polygon { constructor (sideLength ) { super (sideLength, sideLength); } set sideLength (newLength ) { this .height = newLength; this .width = newLength; } } var square = new Square (2 );console .log (square.area );console .log (`Square is : ${typeof Square} ` );console .log (`Polygon is : ${typeof Polygon} ` );console .log (Object .getOwnPropertyNames (square));console .log (`实例square 的 __proto__ 为类型 Square 的prototype : ${square.__proto__ === Square.prototype} ` );console .log (Object .getOwnPropertyNames (square.__proto__ )); console .log (`实例square 的 __proto__ 的 __proto__ 为类型 Polygon 的prototype : ${square.__proto__.__proto__ === Polygon.prototype} ` );console .log (Object .getOwnPropertyNames (square.__proto__ .__proto__ ));
可以看出, class 其实算是个语法糖,其本质还是通过定义function即构造函数,然后构造对象,而 extend 则是通过设置原型链来实现继承