JS原型、toString.call、call引发的思考

142 阅读3分钟

JS:一切皆对象

原型

  • 对象拥有__proto__,指向其构造函数的prototype,即构造函数的原型,在浏览器中,以[Prototype]]表示

  • 构造函数拥有prototype,当new时,会将构造函数prototype属性与其实例__proto__属性值拷贝(如果该属性是对象,则拷贝引用地址)

  • 构造函数的终点是FunctionFunction是构造函数,所以也拥有prototype属性

  • JS一切皆对象,函数也是对象,作为函数和对象,所以函数同时拥有了__proto__prototype属性

  • JS一切皆对象,原型也是对象,作为对象,所以原型也拥有__proto__,这个__proto__指向构造这个对象的构造函数的原型,即构造函数的prototype属性,即对象.__proto__===构造函数.prototype

  • 一切对象都是由构造函数构造的,包括函数自己,所以,函数本质上是Function的实例

    注意这里的对象,指的是对象,而非基本数据类型,如number、string等

    // Function自己构造了自己
    console.log( Function.__proto__===Function.prototype );
    ​
    // Object对象由Function构造,但同时Object对象在语言层面的构造函数的原型是Object.prototype,也就是说Object对象由Function和Object共同构造
    console.log( Object instanceof Object );
    console.log( Object.__proto__===Function.prototype );
    ​
    // Function与Object互相继承,鸡生蛋、蛋生鸡
    console.log( Function instanceof Object );
    console.log( Object instanceof Function );
    ​
    // Function与Object作为函数对象,构造函数的原型相同
    console.log( Function.__proto__ === Object.__proto__ );
    ​
    // 原型链的顶端是Object构造函数的prototype
    console.log( Function.prototype.__proto__===Object.prototype );
    // Object.prototype是特例,是原型,同是是对象,但这个原型没有原型,即null
    console.log( Object.prototype.__proto__===null );
    ​
    // {}不是函数生成的对象,所以不是由Function函数构造
    console.log( {} instanceof Function );
    // {}是字面对象,由Object函数构造
    console.log( {} instanceof Object );
    ​
    // Object.toString指向Function.prototype.toString
    console.log(Object.toString===Function.prototype.toString);
    // Object.prototype.toString指向Function原型的原型的toString,也就是Object.prototype.toString,也就是原型终点的toString
    console.log(Object.prototype.toString===Function.prototype.__proto__.toString);
    ​
    ​
    function myFunction1(a, b){
      return a + b;
    }
    ​
    // 函数继承自Function,说明函数也是由Function构造的
    console.log( myFunction1 instanceof Function );
    ​
    // 函数也可直接用new Function 声明函数
    const myFunction2 = new Function( 'a', 'b', 'return a + b' );
    console.log( myFunction2( 1, 2 ) ); // 3
    
  • 由上述得知,对象拥有__proto__属性,Function.prototype作为原型,原型也是对象,也拥有__proto__属性,这个__proto__属性指向该原型的构造函数的原型,也就是原型的终点Object.prototype,即Function.prototype.__proto__===Object.prototype

原型链

  • 如果当前对象没有某方法,会沿着原型链去找,也就是通过该对象的__proto__去它的构造函数的prototype属性中去找,找到则调用,未找到,继续沿着构造函数.prototype.__proto__去找,即原型链去找,即查找的方向是对象.__proto__.__proto__.....去找,直至原型链终点Object.prototype

toString.call

好了,在以上表述明确的基础上,可以理解为什么必须用Object.prototype.toString.call,而不是直接用Object.toString.call去获取对象类型的字符串表示了

Object.toString

  • Object对象是全局对象,在浏览器里,Object来自于window
  • Object对象自己没有toString方法,所以会从原型链上找,也就是通过Object.__proto__上去它的构造函数的prototype中去找,而Object.__proto__===Function.prototypeFunction.prototype中有toString方法,找到了,就不再沿原型链继续找了,所以Object.toString本质上调用的是Function.prototype.toString

Object.prototype.toString

  • 此方法是原型终点的方法,Function.prototype上重写的toString方法不同
  • 所以Object.prototype.toString!==Function.prototype.toString,也就是Object.prototype.toString!==Object.toString

call

原型与原型链讲完了,现在只剩call了

  • Object.prototype.toString.call

    查看浏览器,Object.prototype中只有toString方法,没有call方法,而且toString方法是一个函数,那么Object.prototype.toString.call是怎么的呢?

  • 看了前面的原型与原型链,可明白,任何对象都是由构造函数构造的,构造函数也是对象,而构造函数的终点是Function,也就是说,任何函数都有__proto__的属性,该属性来自于Function.prototype

  • toString是一个函数,最终也是由构造函数构造的,也就是说,toString.__proto__===Function.prototype

  • 再重复一遍toString中的call方法,来自于Function.prototype,也就是说,任何函数对象,通过原型链,都能调用到Function.prototype中的任何方法

  • 所以,本质上,Object.prototype.toString.call调用的不是Object.prototype.call,而是调用Function.prototype.call这个方法

  • 同理:Object.toString.call === Function.prototype.call

  • 同理:Object.call === Function.prototype.call

散会