前言
都知道,原型能够使对象的方法达到复用的目的,而不是每个对象都存在相同方法。
概念
在详细了解原型之前,需要明白以下概念:
prototype:原型,函数自带的属性,也是一个对象;
constructor:原型对象自带的一个属性,指向宿主原型的宿主方法;
__proto__:对象的一个属性,指向实例化出该对象的方法的原型。
例:
一个方法 A,实例化出一个 a,则此时 a.__proto__ === A.prototype,而 A.prototype.constructor === A
new 实现
想要了解原型是如何复用对象的,就得了解一个方法是怎么 new 出对象的,直接看代码:
1 function MyNew(fn, ...args){ 2 let obj = Object.create(fn.prototype) 3 let res = fn.apply(obj, args) 4 return res instanceof Object ? res : obj 5 } 6 7 function A (name){ 8 this.name = name 9 } 10 let a = MyNew(A, "jane") 11 console.log(a.name)
上面代码,通过自己实现的 MyNew 成功实例化 A,并成功打印出 jane。
通过上面代码,可以看出 new 其实就是新创建出一个Object,然后再通过 apply 将新创建的对象(this)绑定到A上,再返回,这样就成功访问 A 的 name 并打印。
下面再给 A 的 prototype 添加一个方法:
1 function A (name){ 2 this.name = name 3 } 4 A.prototype.fn = function(){ 5 console.log(1) 6 }
7 let a = MyNew(A, "jane") 8 a.fn()
A 的 实例仍然可以访问 A 上的 prototype 上的方法。再看 MyNew ,这是因为,在创建 obj 的时候,已经将 obj 的 __proto__ 链接在了 A.prototype 上了,这样在调用方法时,在自身对象找不到的情况下,就会往 __proto__ 找,而 __proto__ 又链接在 A.prototype 上,最终在 A.prototype 上找到方法。
再看下面的方式调用:
1 a.hello() 2 3 // ------ 4 5 a.toString()
当 a 调用 hello 方法时会报错,而当 a 调用 toString 方法时却可以成功调用。
这里 hello 在原型上没有找到,属于正常保存,而 toString 是因为在 Object 上找到了,所以没报错。这里容易忽略的一点就是 prototype 也是一个对象,也有 __proto__ 属性,而 prototype 的 __proto__ 则指向 Object.prototype,所以最终在 Object 上找到 toString。
prototype.constructor
这个属性( constructor )与 class 中的 constructor 构造不同,并没有什么特殊的地方,甚至可以改掉其值,而唯一性作用可能是获取其宿主原型的宿主方法吧。
黑话
本文中提到了宿主原型,宿主方法,为了避免误解,这里说一下它们的意思:
一个 prototype ,而 prototype 中有 constructor ,而 constructor 中的宿主原型就是指 prototype。
一个方法 A,A 有 prototype ,而 prototype 的宿主方法就是 A 。
参考
https://segmentfault.com/a/1190000022927245
https://zhuanlan.zhihu.com/p/30012224