理解类, 实例, 原型
区分prototype
与__proto__
用代码实现: new
, Object.create
类与实例
定义构造函数就是定义的是对象的”类”, 调用构造函数生成的对象就是”实例”
创建实例
现有类定义如下:
1 | function Person(name) { |
创建实例可以有以下方式:
1.new
1 | let p = new Person('levy') |
2.Object.create
1 | p = Object.create(Person.prototype) |
3.__proto__
1 | p = {'__proto__': Person.prototype, name: 'levy'} |
解析:
除了null
(undefined
不是对象)/Object.prototype
, js每一个对象都有一个原型, 对象从原型继承属性与方法
对象对应的构造函数的prototype
属性, 就是对象的原型
而大多数浏览器的 ES5 实现之中,每一个对象都有__proto__
属性,指向其原型, 所以用代码来表示就是
1 | let obj = {} |
所以上面第3个代码片断是最本质(当然不是最优)的表示方式
总结: __proto__
是对象的属性, prototype
是构造函数的属性
构造函数
任何js函数都可以做构造函数, 并且都拥有prototype属性
(除了调用Function.bind
返回的函数), 因为使用new
调用构造函数时, 构造函数的prototype
属性会作为新对象的原型
构造函数的prototype
属性是个对象, 它预设了一个不可枚举的constructor
属性, 它指向构造函数, 即:
1 | let F = function() {} |
模拟实现new
和Object.create
Object.create
可以看成是屏蔽了new
关键字的实现, 可以自己模拟一下实现:
1 | Object.create = function(parent) { |
如果更暴力一点, 还可以这样:
1 | Object.create = function(parent) { |
new
调用的原理是:
- 创建一个新对象, 其原型为构造函数的
prototype
属性 - 把这个对象作为this值, 传相应的参数调用构造函数, 构造函数就可以用this初始化对象属性值
- 返回这个新创建的对象
所以可以自己写个New
函数来模拟new
的实现:
1 | // 模拟实现表达式 new fn(x) |
当然上述实现很粗糙, 因为:
new
后面的构造函数, 如果不需要参数的话, 构造函数不需要带括号, 即可以这样new fn
构造函数一般不返回值, 返回的不是对象值也将会被忽略, 但如果构造函数确实返回了一个对象, 则该对象作为
new
表达式的返回值, 而不是调用构造函数后新建并初始化了的对象
总结: 把原理性的文字改造成代码, 是一种很好的学习方式
参考资料
JavaScript权威指南(第六版)
ES6入门
http://blog.vjeux.com/2011/javascript/how-prototypal-inheritance-really-works.html