原型链深入:对函数充当多种角色的理解

我心飞翔 分类:javascript

函数的函数角色和对象角色

数据类型分为原始值和对象类型,函数属于对象类型的值。他是一种特殊的对象。那么,所有的对象都有__proto__,函数又是怎么回事呢?

举例理解:

function Fn() {
    this.x = 100;
}
Fn.prototype.getX = function getX() {
    console.log(this.x);};
let f1 = new Fn;
let f2 = new Fn;
 

我们dir(Fn),发现函数中有[[scope]] (作用域),代码字符串,和各种属性。

属性中有 prototypename(属性名),length:0(形参个数)

image。png

看一下Object,Object内置构造函数 每一个对象都是其实例

dir(Object.prototype)
image。png

dir(Object)
image。png

image。png

通过以上结构可以知道,平常应这样使用:实例.hasOwnProperty(),Object.assign()

所以,函数是具备多种角色的:

  • 函数
    • 普通函数(拥有上下文/作用域)
    • 构造函数(作用:类/实例/原型/原型链)
  • 普通对象(作用:拥有键值对,进行成员访问)

前面的文章说过,不考虑函数为对象的情况下,__proto__的指向是这样的

image。png

考虑函数也是一个实例对象,也拥有自己的__proto__,也应该指向他所属的类的实例原型,这个类就是 Function

所有的函数(普通函数/构造函数)都是他的一个实例。那么既然是类,Function也应该有自己的prototype

dir(Function)

image。png

dir(Function.prototype)
image。png

image。png

以下是不考虑函数的构造函数的__proto__,仅考虑对象时,__proto__的指向
image。png

那么我们知道,所有的构造函数都是Function构造函数的实例,那么所有函数的__proto__应该指向Function.prototype

如下红线:

image。png

基于这张图,我们来进行一些打印有趣的代码

Function.prototype===Function.__proto__//true 
Fn.call===Function.prototype.call//true 去构造函数的 原型上找方法

Object.apply===Function.prototype.apply//true 去构造函数的 原型上找方法
 

所有对象(包含函数对象) 都是Object的实例,都应该可以调取Object.prototype上的方法

例如Fn.hasOwnProperty,实际上是依据原型链,找到Fn.__proto__.__proto__.hasOwnProperty

image。png

所以

Object.__proto__===Function.prototype//true
Object.__proto__.__proto__===Object.prototype//true
 

FunctionObject到底谁大?

如果非要钻牛角尖去解释这个问题,那么我们要从不同的角度去理解

  1. Object构造函数本身是函数的角度去理解的话,Object本身就是一种函数,他一定是Function构造函数的一个实例

    Object instanceof Function // true
     
  2. 对于任何一个函数来说,所有函数都是对象,即使是构造函数,也属于对象,那么Function构造函数又是Object的实例

    Function instanceof Object // true
     

所以这个问题是鸡生蛋蛋生鸡的问题。

如果是从是对象的角度理解,那么最终所有的__proto__都指向Object.prototype,所有的值(排除原始值)都是对象数据类型的(函数也是一种特殊的对象),所有的值都是Object的实例,即所谓的万物皆对象,xxx instanceof Object==>true

如果从函数角度理解,函数比较特殊(普通函数/箭头函数/构造函数/内置函数/自定义函数/生成器函数)既是对象又是函数,作为函数来讲,所有函数都是Function的一个实例

到底谁大,那就要看从什么样的身份,什么样的角度,充当什么样的角色相对的去理解。其实不用刻意的钻牛角尖非要区分谁大。知道有这样的特性即可

这里面只有一个比较特殊的:Function.prototype

Object.prototypeFn.prototype都是一个对象,按理来说Function.prototype也应该是一个对象,但是打印可以看到:
image。png

Function.prototype指向的是一个函数,这个函数叫做 匿名空函数,执行后什么事都不干

打印后后发现他也虽然是个函数,但是他没有prototype,跟对象很相似,虽然是个函数,但反而更像对象
image。png

举例子加深理解

例1

function C1(name) {
    if (name) {
        this.name = name;
    }
}
function C2(name) {
    this.name = name;
}

function C3(name) {
    this.name = name || 'join';
}
C1.prototype.name = 'Tom';
C2.prototype.name = 'Tom';
C3.prototype.name = 'Tom';
alert((new C1().name) + (new C2().name) + (new C3().name));
// "Tomundefinedjoin" 
 

比较简单,第一个没有name就是原型上的name,第二个没传就是undefined,第三个||返回的是后面的join,然后字符串拼接

例2

function Foo() {
    getName = function () {
        console.log(1);
    };
    return this;
}
Foo.getName = function () {
    console.log(2);
};
Foo.prototype.getName = function () {
    console.log(3);
};
var getName = function () {
    console.log(4);
};
function getName() {
    console.log(5);
}
Foo.getName();//2
getName();//4
Foo().getName();//1
getName();//1
new Foo.getName();//2
new Foo().getName();//3
new new Foo().getName(); 
 

前四个输出考察函数执行,变量提升,this,函数作为对象角色。

image。png

分别输出 2 4 1 1

后面考察函数的多种角色问题,优先级问题

new Foonew Foo()都是执行new,区别在于运算符优先级问题

image。png

  • xxx.xxx 成员访问 20
  • new xxx() 20
  • new xxx 19

处理时按照优先级来

new Foo.getName()这句话有两个运算,new FooFoo.getName,后者成员访问的优先级更高,所以先运算Foo.getName,其返回值假设为一个函数Fg然后再执行new Fg(),而new Fg()会把Fg函数执行一遍,输出2,至于创造出来什么对象,就不用管了

注意,new Foo.getName()并不是newFoo.getName()的返回结果 undefined,而是将后面的函数当做一个整体来new,假设Foo.getNameFg那么其实运行的是new Fg(),我们可以将Foo.getName修改如下,确实验证了说法
image。png

所以所以new Foo.getName()的执行最终相当于new (Foo.getName)(),打印2

new Foo().getName() 这句代码new Foo()Foo().getName都是20,按照文档,应该从左往右运算,所以应该先执行new Foo(),执行完的结果得到后再执行结果.getName()
new Foo()时,其中的this不再指向window,而指向实例对象,所以最终返回this,返回的仍然是实例对象,而不是window。因为私有的没有gitName()方法,所以会去其原型上寻找getName()最终输出3
image。png

new new Foo().getName()这句话,首先执行new Foo(),假设返回一个实例对象0x003,那么就转化为new 0x003.getName()那么就是和第一个一样,先进行成员访问了,即 new (0x003.getName)()0x003.getName私有上没有getName,所以要去其原型上找,最后是输出3这个函数,假设这个函数是0x004,那么相当于new 0x004(),执行函数,输出3

下面是执行的过程

image。png

回复

我来回复
  • 暂无回复内容