原型的七个Koan
当西罗·桑在深度冥想后下了火狐山时,他的头脑清晰而平静。
然而,他的手却坐立不安,自己抓起画笔,记下了下面的笔记。
0)两种不同的东西可以称为“原型”:
prototype属性,如obj.prototype原型内部属性,在ES5中表示为[[prototype]]。它可以通过ES5 Object.getPrototypeOf()进行检索。Firefox可以通过__proto__属性作为扩展访问它。ES6现在提到了__proto__的一些可选要求。
1) 这些概念的存在是为了回答这个问题:
当我执行obj.properties时,JS在哪里查找.properties?
直觉上,经典继承应该会影响属性查找。
2)
__proto__用于点。在obj.properties中查找属性。.prototype不直接用于查找,而是间接用于查找,因为它在使用new创建对象时确定__proto__。
查找顺序为:
obj财产添加了obj.p=。。。或Object.defineProperty(obj,…)obj.__proto的财产__对象proto_.proto__的财产,依此类推如果某些__proto__为空,则返回undefined。
这就是所谓的原型链。
你可以避免。使用obj.hasOwnProperty('key')和Object.getOwnPropertyNames(f)查找
3) 设置对象__proto__的主要方法有两种:
新增:var F=函数(){}var f=新f()则new已设置:f.__proto__==f原型这就是使用原型的地方。对象创建:f=对象.create(proto)集合:f.__proto__==原型
4) 代码:
var F = function(i) { this.i = i }
var f = new F(1)
对应于下图(省略了一些数字内容):
(Function) ( F ) (f)----->(1)
| ^ | | ^ | i |
| | | | | | |
| | | | +-------------------------+ | |
| |constructor | | | | |
| | | +--------------+ | | |
| | | | | | |
| | | | | | |
|[[Prototype]] |[[Prototype]] |prototype |constructor |[[Prototype]]
| | | | | | |
| | | | | | |
| | | | +----------+ | |
| | | | | | |
| | | | | +-----------------------+ |
| | | | | | |
v | v v | v |
(Function.prototype) (F.prototype) |
| | |
| | |
|[[Prototype]] |[[Prototype]] [[Prototype]]|
| | |
| | |
| +-------------------------------+ |
| | |
v v v
(Object.prototype) (Number.prototype)
| | ^
| | |
| | +---------------------------+
| | |
| +--------------+ |
| | |
| | |
|[[Prototype]] |constructor |prototype
| | |
| | |
| | -------------+
| | |
v v |
(null) (Object)
此图显示了许多语言预定义的对象节点:
无效的对象对象.原型作用功能.原型1.Number.prototype(可以用(1)找到__proto__,括号必须满足语法)
我们的两行代码只创建了以下新对象:
fFF.原型
i现在是f的属性,因为当你这样做时:
var f = new F(1)
它计算F,这是new将返回的值,然后将其分配给F。
5) 构造函数通常通过。查找:
f.constructor === F
!f.hasOwnProperty('constructor')
Object.getPrototypeOf(f) === F.prototype
F.prototype.hasOwnProperty('constructor')
F.prototype.constructor === f.constructor
当我们编写f.constructor时,JavaScript会执行。查找为:
f没有.构造函数f.__proto__==f.prototype具有构造函数==f,因此接受它
结果f.constructor==f直观上是正确的,因为f用于构造f,例如集合字段,与经典OOP语言中的情况非常相似。
6) 经典的继承语法可以通过操纵原型链来实现。
ES6添加了类并扩展了关键字,这些关键字主要是语法糖,用于之前可能的原型操作疯狂。
class C {
constructor(i) {
this.i = i
}
inc() {
return this.i + 1
}
}
class D extends C {
constructor(i) {
super(i)
}
inc2() {
return this.i + 2
}
}
// Inheritance syntax works as expected.
c = new C(1)
c.inc() === 2
(new D(1)).inc() === 2
(new D(1)).inc2() === 3
// "Classes" are just function objects.
C.constructor === Function
C.__proto__ === Function.prototype
D.constructor === Function
// D is a function "indirectly" through the chain.
D.__proto__ === C
D.__proto__.__proto__ === Function.prototype
// "extends" sets up the prototype chain so that base class
// lookups will work as expected
var d = new D(1)
d.__proto__ === D.prototype
D.prototype.__proto__ === C.prototype
// This is what `d.inc` actually does.
d.__proto__.__proto__.inc === C.prototype.inc
// Class variables
// No ES6 syntax sugar apparently:
// http://stackoverflow.com/questions/22528967/es6-class-variable-alternatives
C.c = 1
C.c === 1
// Because `D.__proto__ === C`.
D.c === 1
// Nothing makes this work.
d.c === undefined
没有所有预定义对象的简化图:
(c)----->(1)
| i
|
|
|[[Prototype]]
|
|
v __proto__
(C)<--------------(D) (d)
| | | |
| | | |
| |prototype |prototype |[[Prototype]]
| | | |
| | | |
| | | +---------+
| | | |
| | | |
| | v v
|[[Prototype]] (D.prototype)--------> (inc2 function object)
| | | inc2
| | |
| | |[[Prototype]]
| | |
| | |
| | +--------------+
| | |
| | |
| v v
| (C.prototype)------->(inc function object)
| inc
v
Function.prototype
让我们花一点时间研究一下以下功能是如何工作的:
c = new C(1)
c.inc() === 2
第一行将c.i设置为1,如“4)”所述。
在第二行,当我们这样做时:
c.inc()
.inc是通过[[原型]]链找到的:c->c->c.Prototype->inc当我们将Javascript中的函数调用为X.Y()时,Javascript会在Y()函数调用中将其自动设置为等于X!
同样的逻辑也解释了d.inc和d.inc2。
这篇文章https://javascript.info/class#not-只是一个语法糖提到了值得了解的类的进一步影响。如果没有class关键字(TODO check which),其中一些可能无法实现:
[[FunctionKind]]:“classConstructor”,强制使用new调用构造函数:ES6类构造函数不能作为普通函数调用的原因是什么?类方法是不可枚举的。可以使用Object.defineProperty完成。类总是使用strict。可以通过显式使用strict for each函数来完成,这无疑是乏味的。