凭Object位于原型链的顶端吗?

就标题而言,这是七八篇里起得最满意的,高大上,即使外行人也会不明觉厉! 

不过不是开玩笑,本文的确打算从__proto__prototype这两个容易混淆来理解JS的终极命题之一:对象与原型链

引用《JavaScript权威指南》的一段描述:

翻译出来就是每个JS对象一定对应一个原型对象,并从原型对象继承属性和方法。好啦,既然有这么一个原型对象,那么对象怎么和它对应的?

对象__proto__属性的值就是它所对应的原型对象:

上面的代码应该已经足够解释清楚__proto__了。好吧,显然还不够,或者说带来了新的问题:Object.prototype是什么?凭什么说onetwo的原型就是Object.prototype

首先来说说prototype属性,不像每个对象都有__proto__属性来标识自己所继承的原型,只有函数才有prototype属性。

为什么只有函数才有prototype属性?ES规范就这么定的。

开玩笑了,其实函数在JS中真的很特殊,是所谓的_一等公民_。JS不像其它面向对象的语言,它没有类(class,ES6引进了这个关键字,但更多是语法糖)的概念。JS通过函数来模拟类。

当你创建函数时,JS会为这个函数自动添加prototype属性,值是空对象。而一旦你把这个函数当作构造函数(constructor)调用(即通过new关键字调用),那么JS就会帮你创建该构造函数的实例,实例继承构造函数prototype的所有属性和方法(实例通过设置自己的__proto__指向承构造函数的prototype来实现这种继承)。

虽然对不熟悉的人来说还有点绕,但JS正是通过__proto__prototype的合作实现了原型链,以及对象的继承。

构造函数,通过prototype来存储要共享的属性和方法,也可以设置prototype指向现存的对象来继承该对象。

我们知道JS是单继承的,Object.prototype是原型链的顶端,所有对象从它继承了包括toString等等方法和属性。

什么情况下会出现鸡和蛋的问题呢?

那么具体到JS,ES规范是怎么说的?

相信经过上面的详细阐述,这张图应该一目了然了。

从上面的规定再结合其它,理出以下几点:

以上3点比较容易理解,或者说规范里就这样定义的。由以上3点导出我们最后的问题:ObjectFunction的鸡和蛋的问题。

  1. Function.prototype是个不同于一般函数(对象)的函数(对象)。


今天我们部门的技术分享上出现了这样一段代码:

问我们这段代码的运行结果是怎样的

首先我们需要了解以下前置知识:

  1. 变量提升和函数提升
  2. new操作符的工资流程

本文对于这些知识进行浅谈,详细的知识作者还是建议读者去自行查找资料和学习。

变量提升简单来说就是使用var声明变量的时候,会在编译时将其提升至最顶端,例如本题的代码的前几行:(有错误)

当然细心的读者应该会发现,这个编译后的代码依旧存在一些问题,因为js不仅有变量提升,同时还有函数提升。

在JS中函数在编译后也会提升至最顶端,因此编译后的正确代码为:

这里要注意:函数提升的优先级高于变量提升,当函数声明与变量名相同时,只要变量未赋值,此名称依旧是个函数,不会被覆盖;只有当变量赋值后,函数声明才会被同名的变量覆盖。

相信读到这里开篇提到的问题已经能解决一部分了

会覆盖同名函数,因此第一行的执行结果为4

简单来说this是一个指向一个对象的指针,通常情况下你只需要记住调用方法时谁调用,this就指向谁。

例如我在全局中调用Foo()方法,那么此时进入方法里this的指向就是全局(Window对象)

那么我们第二行的运行结果也就很容易就理解了,首先Foo方法对全局里的getName重新赋值,并返回Window对象,所以第二行执行的方法为全局对象里重新赋值后的的getName()

因此第二行和第三行的执行结果为1
读到这里第三行的运行结果和第二行一样也不难理解了

首先我们知道在js中函数也是一个对象,它也是具有普通对象的一些功能的,而JavaScript 对象体系是基于构造函数和原型链的。继承不通过类,而是通过原型对象实现,原型对象的所有属性和方法,都能被实例对象共享。

既然函数可以是一个对象,那么原题中的第二部分,就是为Foo添加了一个属性getName(),所以第4行的输出结果就是现在为其添加的这个方法的运行结果:2

接下来我们需要了解原题中第三部分的作用,也就是以下代码的作用:

首先每个构造函数都有一个prototype属性指向原型对象,用来存放共有属性和方法的地址。

每个实例对象都有一个__proto__属性指向构造函数的原型对象。

这样依次类推各个原型组成了“原型链”
在理解了原型链之后你还需要了解下面这句话:
当js试图得到一个对象的属性时,会先去这个对象的本身去寻找,如果这个对象本身没有找到这个属性,那么js就会去它构造函数的’prototype’属性中去寻找,也就是去’proto‘中寻找,如果’prototype’属性本身中依旧没有找到,’prototype’中依旧有一个‘proto’。

读到这里相信第三部分的代码意思也很清晰了,向原型链中添加一个方法:getName()

new操作符的工资流程

new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。

简单来说new就是用来创建一个对象。

那么new操作符是怎么做到的呢?

第一步:创建一个空的对象(即{})

第二步:将空对象的原型prototype指向构造函数的原型

第三步:改变this指针的指向,并将剩余的参数传入,执行构造函数中的代码

第四步:判断构造函数的返回值,将成功后的对象返回

这时我们分析原题第五行的输出结果:

第一步:创建一个空对象

第二步:将obj的原型指向Foo对象的getName()的原型

第三步:改变this指向,传入剩余参数并执行构造函数Foo.getName()。运行到这里输出3。

第四步:判断构造函数的返回值,将成功后的对象返回

第一步:创建一个空对象

第二步:将obj的原型指向Foo的原型

第三步:改变this指向,传入剩余参数并执行构造函数Foo() 第四步:判断构造函数的返回值,将成功后的对象返回 第五步:执行返回的新对象的getName()方法,可我们的新对象中并没有此方法,于是去原型链上寻找,故输出3 相信读到这里最后一行的答案也不需要作者多说什么了,其运行过程与第六行的基本一致。

我要回帖

更多关于 鹰是食物链顶端么 的文章

 

随机推荐