原型与继承
在 JavaScript 中,对象有一个特殊的隐藏属性 [[Prototype]](如规范中所命名的),它要么为 null,要么就是对另一个对象的引用。该对象被称为“原型。
当我们从 object 中读取一个缺失的属性时,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 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 'use strict' ;const user = { name : 'user' , age : 18 } const admin = { __proto__ : user, name : 'admin' , } console .log (admin.name ); console .log (admin.age ); console .log (admin.__proto__ ); console .log (admin.__proto__ .__proto__ ); console .log (admin.__proto__ .__proto__ .__proto__ ); console .log (Object .getOwnPropertyNames (admin.__proto__ .__proto__ ));console .log (Object .keys (admin)); for (let key in admin) console .log (key); console .log (admin.hasOwnProperty ("name" )); console .log (admin.hasOwnProperty ("age" ));
__proto__ 是 [[Prototype]] 的因历史原因而留下来的 getter/setter
初学者常犯一个普遍的错误,就是不知道 __proto__ 和 [[Prototype]] 的区别。
请注意,__proto__ 与内部的 [[Prototype]] 不一样 。__proto__ 是 [[Prototype]] 的 getter/setter。稍后,我们将看到在什么情况下理解它们很重要,在建立对 JavaScript 语言的理解时,让我们牢记这一点。
__proto__ 属性有点过时了。它的存在是出于历史的原因,现代编程语言建议我们应该使用函数 Object.getPrototypeOf/Object.setPrototypeOf 来取代 __proto__ 去 get/set 原型。稍后我们将介绍这些函数。
根据规范,__proto__ 必须仅受浏览器环境的支持。但实际上,包括服务端在内的所有环境都支持它,因此我们使用它是非常安全的。
由于 __proto__ 标记在观感上更加明显,所以我们在后面的示例中将使用它。
1 2 3 4 5 6 7 8 9 10 11 12 let user = { setName (name ){ this .name = name } } let admin = { __proto__ : user } admin.setName ("admin" ); console .log (admin.name ); console .log (user.name );
我们还记得,可以使用诸如 new F() 这样的构造函数来创建一个新对象。
如果 F.prototype 是一个对象,那么 new 操作符会使用它为新对象设置 [[Prototype]]。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 "use strict" ;MyArray .prototype = new Array (); function MyArray ( ) { } const arr = new MyArray ();console .log (arr.constructor === Array ); console .log (arr.__proto__ .constructor === Array ); arr.push (...[1 , 2 , 3 ]); console .log (arr);
每个函数都有 "prototype" 属性,即使我们没有提供它。
默认的 "prototype" 是一个只有属性 constructor 的对象,属性 constructor 指向函数自身。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 "use strict" ;function User ( ) {}console .log (Object .getOwnPropertyDescriptors (User .prototype ));console .log (User .prototype .constructor === User ); console .log (User .prototype .__proto__ .constructor === Object ); console .log (User .prototype .__proto__ .__proto__ === null ); const user = new User ();console .log (user.__proto__ === User .prototype ); console .log (user.__proto__ .__proto__ .__proto__ === null ); console .log (user.constructor === User );
在常规对象上,prototype 没什么特别的:
1 2 3 4 let user = { name : "John" , prototype : "Bla-bla" };
默认情况下,所有函数都有 F.prototype = {constructor:F},所以我们可以通过访问它的 "constructor" 属性来获取一个对象的构造器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 "use strict" ;function User (name ){ this .name = name; } let user1 = new User ("User1" );console .log (user1);let user2 = new User .prototype .constructor ("User2" );console .log (user2);let user3 = new user1.__proto__ .constructor ("User3" );console .log (user3);let user4 = new user1.constructor ("User4" );console .log (user4); User .prototype = {}let user5 = new user1.constructor ("User5" );console .log (user5); user1.__proto__ = null let user6 = new user1.constructor ("User6" );console .log (user6);
内建对象的原型挂载着相关属性和方法
可向prototype上添加自定义方法或属性
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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 console .log (Object .getOwnPropertyNames (Array .prototype ));console .log (Object .getOwnPropertyNames (String .prototype ));console .log (Object .getOwnPropertyNames (Number .prototype ));console .log (Object .getOwnPropertyNames (Boolean .prototype ));console .log (Object .getOwnPropertyNames (Date .prototype ));console .log (Object .getOwnPropertyNames (Map .prototype ));console .log (Object .getOwnPropertyNames (WeakMap .prototype ));console .log (Object .getOwnPropertyNames (Function .prototype ));console .log (Object .getOwnPropertyNames (Object .prototype ));console .log (Object .getOwnPropertyNames (Object ));
如果速度很重要,就请不要修改已存在的对象的 [[Prototype]]
通常我们只在创建对象的时候设置它一次,自那之后不再修改
现代的获取/设置原型的方法有:
Object.create(null)这样的对象称为 “very plain” 或 “pure dictionary” 对象,因为它们甚至比通常的普通对象(plain object){...} 还要简单。
缺点是这样的对象没有任何内建的对象的方法,例如 toString:
1 2 3 4 5 6 7 8 9 10 11 12 'use strict' ;let obj = Object .create (null );console .log (obj.__proto__ ); let obj1 = {};console .log (obj1.__proto__ ); console .log (obj1.toString ());