深度解析原型中的各个难点

本文同步分布在个人 Githubgit

本文不会过多介绍基础知识,而是把重点放在原型的各个难点上。github

你们能够先仔细分析下该图,而后让咱们进入主题浏览器

prototype

首先来介绍下 prototype 属性。这是一个显式原型属性,只有函数才拥有该属性。基本上全部函数都有这个属性,可是也有一个例外markdown

let fun = Function.prototype.bind()
复制代码

若是你以上述方法建立一个函数,那么能够发现这个函数是不具备 prototype 属性的。app

prototype 如何产生的

当咱们声明一个函数时,这个属性就被自动建立了。函数

function Foo() {}
复制代码

而且这个属性的值是一个对象(也就是原型),只有一个属性 constructoroop

constructor 对应着构造函数,也就是 Foothis

constructor

constructor 是一个公有且不可枚举的属性。一旦咱们改变了函数的 prototype ,那么新对象就没有这个属性了(固然能够经过原型链取到 constructor)。spa

那么你确定也有一个疑问,这个属性到底有什么用呢?其实这个属性能够说是一个历史遗留问题,在大部分状况下是没用的,在个人理解里,我认为他有两个做用:prototype

  • 让实例对象知道是什么函数构造了它
  • 若是想给某些类库中的构造函数增长一些自定义的方法,就能够经过 xx.constructor.method 来扩展

_proto_

这是每一个对象都有的隐式原型属性,指向了建立该对象的构造函数的原型。其实这个属性指向了 [[prototype]],可是 [[prototype]] 是内部属性,咱们并不能访问到,因此使用 _proto_ 来访问。

由于在 JS 中是没有类的概念的,为了实现相似继承的方式,经过 _proto_ 将对象和原型联系起来组成原型链,得以让对象能够访问到不属于本身的属性。

实例对象的 _proto_ 如何产生的

从上图可知,当咱们使用 new 操做符时,生成的实例对象拥有了 _proto_属性。

function Foo() {}
// 这个函数是 Function 的实例对象
// function 就是一个语法糖
// 内部调用了 new Function(...)
复制代码

因此能够说,在 new 的过程当中,新对象被添加了 _proto_ 而且连接到构造函数的原型上。

new 的过程

  1. 新生成了一个对象
  2. 连接到原型
  3. 绑定 this
  4. 返回新对象

在调用 new 的过程当中会发生以上四件事情,咱们也能够试着来本身实现一个 new

function create() {
    // 建立一个空的对象
    let obj = new Object()
    // 得到构造函数
    let Con = [].shift.call(arguments)
    // 连接到原型
	obj.__proto__ = Con.prototype
    // 绑定 this,执行构造函数
    let result = Con.apply(obj, arguments)
    // 确保 new 出来的是个对象
    return typeof result === 'object' ? result : obj
}
复制代码

对于实例对象来讲,都是经过 new 产生的,不管是 function Foo() 仍是 let a = { b : 1 }

对于建立一个对象来讲,更推荐使用字面量的方式建立对象。由于你使用 new Object() 的方式建立对象须要经过做用域链一层层找到 Object,可是你使用字面量的方式就没这个问题。

function Foo() {}
// function 就是个语法糖
// 内部等同于 new Function()
let a = { b: 1 }
// 这个字面量内部也是使用了 new Object()
复制代码

Function.proto === Function.prototype

对于对象来讲,xx.__proto__.contrcutor 是该对象的构造函数,可是在图中咱们能够发现 Function.__proto__ === Function.prototype,难道这表明着 Function 本身产生了本身?

答案确定是否定的,要说明这个问题咱们先从 Object 提及。

从图中咱们能够发现,全部对象均可以经过原型链最终找到 Object.prototype ,虽然 Object.prototype 也是一个对象,可是这个对象却不是 Object 创造的,而是引擎本身建立了 Object.prototype因此能够这样说,全部实例都是对象,可是对象不必定都是实例。

接下来咱们来看 Function.prototype 这个特殊的对象,若是你在浏览器将这个对象打印出来,会发现这个对象实际上是一个函数。

咱们知道函数都是经过 new Function() 生成的,难道 Function.prototype 也是经过 new Function() 产生的吗?答案也是否认的,这个函数也是引擎本身建立的。首先引擎建立了 Object.prototype ,而后建立了 Function.prototype ,而且经过 __proto__ 将二者联系了起来。这里也很好的解释了上面的一个问题,为何 let fun = Function.prototype.bind() 没有 prototype 属性。由于 Function.prototype 是引擎建立出来的对象,引擎认为不须要给这个对象添加 prototype 属性。

因此咱们又能够得出一个结论,不是全部函数都是 new Function() 产生的。

有了 Function.prototype 之后才有了 function Function() ,而后其余的构造函数都是 function Function() 生成的。

如今能够来解释 Function.__proto__ === Function.prototype 这个问题了。由于先有的 Function.prototype 之后才有的 function Function() ,因此也就不存在鸡生蛋蛋生鸡的悖论问题了。对于为何 Function.__proto__ 会等于 Function.prototype ,我的的理解是:其余全部的构造函数均可以经过原型链找到 Function.prototype ,而且 function Function() 本质也是一个函数,为了避免产生混乱就将 function Function()__proto__ 联系到了 Function.prototype 上。

总结

  • Object 是全部对象的爸爸,全部对象均可以经过 __proto__ 找到它
  • Function 是全部函数的爸爸,全部函数均可以经过 __proto__ 找到它
  • Function.prototypeObject.prototype 是两个特殊的对象,他们由引擎来建立
  • 除了以上两个特殊对象,其余对象都是经过构造器 new 出来的
  • 函数的 prototype 是一个对象,也就是原型
  • 对象的 __proto__ 指向原型, __proto__ 将对象和原型链接起来组成了原型链

相关文章
相关标签/搜索