toString.call() 引起的一些思考

toString 作为全局方法时,挂载于 window 全局对象。原型链最顶层的原型对象 Object.prototype 有 Object.prototype.toString 方法(其实,这个方法就是和全局的 toString 是同一方法),另外,一些内置构造函数 Array、String、Boolean、Function、Date 等也都分别部署了自己的 toString 方法。

Object.prototype.toString === toString
// true

为了更好地理解下文,在这里再强调一下原型链的作用:读取对象的某个属性时,JavaScript 引擎先寻找该对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找,层层上溯,直到顶层原型对象 Object.prototype。如果回溯到最顶层还是找不到,则返回 undefined。

所有全局方法均挂载于 window 对象,所有对象都继承自 Object 对象(即,Object.prototype 存在于所有对象的原型链上,并且处于原型链的最末端)。

toString === window.toString  //true
window instanceof Object      //true

window 是个对象,不是构造函数,其构造函数是 Window

window.prototype              //undefined
window instanceof Window              //true
window.constructor === Window         //true
window.constructor === window.Window  //true
window.__proto__ === Window.prototype //true

对象的静态方法和原型方法不同,静态方法一般自行定义(优先级更高),原型方法来自对象的原型 prototype,供该对象的子对象继承。

Object.prototype.toString === Object.toString //false
({}).toString === Object.prototype.toString   //true

Object 的原型部署了 toString 方法,在其原型对象上调用该方法,始终返回 “[object Object]”。换句话讲,Object.prototype.toString 方法本不需要传参,传参也被忽视。字符串 “[object Object]” 本身没有太大的用处,继承对象可以自定义该方法。

Object.toString()                
//"function Object() { [native code] }"
Object.prototype.toString()      //"[object Object]"
Object.prototype.toString([])    //"[object Object]"
Object.prototype.toString(true)  //"[object Object]"

window.toString() 也不需要传参,传参会被忽视

window.toString(1)      //"[object Window]"
toString(1)             //"[object Undefined]"
window.toString([])     //"[object Window]"
toString([])            //"[object Undefined]"

其实,window.toString 和 Object.prototype.toString 是同一个方法

window.toString === Object.prototype.toString   //true
Object.prototype.toString.call([])    //"[object Array]"
window.toString.call([])              //"[object Array]"
Object.prototype.toString.call(1)     //"[object Number]"
window.toString.call(1)               //"[object Number]"

再来看看数组原型方法 Array.prototype.toString

[1,2,3].toString === Array.prototype.toString  //true
[1,2,3].toString()                             //"1,2,3"
Array.prototype.toString.call([1,2,3])         //"1,2,3"

Array.prototype.toString([1,2,3])              //""

同一个方法在不同的方式下(执行上下文,this 等不一样)调用,产生的结果可能不一样。和 toString 类似,直接给 Array.prototype.toString 传参返回值都是””

Array.prototype.toString()    //""
Array.prototype.toString.call(Array.prototype)  //""
[1,2,3].toString.call(Array.prototype)  //""

Array.prototype.toString(1)   //""
Array.prototype.toString.call(Array.prototype,1)  //""
[1,2,3].toString.call(Array.prototype,1)  //""

Array.prototype.toString([])  //""
Array.prototype.toString.call(Array.prototype,[])  //""
[1,2,3].toString.call(Array.prototype,[])  //""

通过 call 调用,就完全不一样了,数组参数会格式化为字符串:

Array.prototype.toString.call([1,2,3])  //"1,2,3"

非数组参数返回结果同 window.toString.call()

Array.prototype.toString.call(1)    //"[object Number]"
Array.prototype.toString.call({})   //"[object Object]"

再看布尔型构造函数原型方法 Boolean.prototype.toString

true.toString === Boolean.prototype.toString   //true
true.toString()                                //"true"
false.toString()                               //"false"
Boolean.prototype.toString(true)               //"false"
Boolean.prototype.toString(false)              //"false"

同样,直接给 Boolean.prototype.toString 传参返回值都是 “false”

Boolean.prototype.toString([])   
//"false"
Boolean.prototype.toString.call(Boolean.prototype,[])  
//"false"
true.toString.call(Boolean.prototype,[])  
//"false"


Boolean.prototype.toString(1)    
//"false"
Boolean.prototype.toString.call(Boolean.prototype,1)  
//"false"
true.toString.call(Boolean.prototype,1)  
//"false"


Boolean.prototype.toString({})   
//"false"
Boolean.prototype.toString.call(Boolean.prototype,{})  
//"false"
true.toString.call(Boolean.prototype,{})  
//"false"

通过 call 调用,布尔型参数会格式化为字符串

Boolean.prototype.toString.call(true)   //"true"
Boolean.prototype.toString.call(false)  //"false"

非布尔型实参会怎样?报错!!(该方法不通用)

Boolean.prototype.toString.call(1)   
//TypeError: Boolean.prototype.toString is not generic
Boolean.prototype.toString.call({})   
//TypeError: Boolean.prototype.toString is not generic

再来看看数组:

[].constructor === Array         //true
[].__proto__ === Array.prototype //true
[] instanceof Array              //true

Array.prototype                  //[]
[].__proto__ === []              //false
// 实际上,实例对象之间都是不相等的

Array 的原型属性不等于其原型对象!

Array.prototype === Array.__proto__    //false
Array.__proto__ === Function.prototype //true

Funtion的原型属性等于其原型对象!!(Function 构造函数也是其自身的实例)

Function.prototype === Function.__proto__
//true
Function instanceof Function
//true

Function.constructor === Function
//true
Function.prototype === Function.constructor.prototype
//true

Function 构造函数的原型是 Object 的实例,其原型的原型的构造函数就是 Object! 但是,Function 的原型的原型却不是 Object 的实例!!

Function.__proto__
//function Empty() {}
Function.__proto__  instanceof Object
//true

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

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

Function.__proto__.__proto__  instanceof Object
//false 为什么??
Object.prototype instanceof Object
//false
Object.prototype.__proto__ === null
//true Object.prototype 对象的原型是 null,原型链到此终止

Object instanceof Object
//true
Object.__proto__ === Object.prototype
//false
Object.__proto__ === Function.prototype
//true
Object.__proto__.__proto__ === Object.prototype
//true Object在自身的原型链上,故 instanceof 运算返回true


Function.__proto__.constructor === Function
//true

Function.prototype
//function Empty() {}
Function.prototype.__proto__ === Object.prototype
//true

Function.__proto__.__proto__ === Function.__proto__.constructor.prototype
//false 为什么??

一般情况下,实例对象的原型对象和其构造函数的原型属性是等同的,举个例子,默认 p.__proto__ 和 p.constructor.prototype 是恒等的:

function Person(name) {
    this.name = name
}
var p = new Person('jack');
p.constructor === Person                  //true
p.__proto__ === p.constructor.prototype   //true

但是,总有例外,毕竟一般对象的属性都是可以修改的

function Person(name) {
    this.name = name
}

下面我们重写 Person 对象的原型:

Person.prototype = {
    getName: function() {}
}
var p = new Person('jack');
p.__proto__ === Person.prototype         //true
p.__proto__ === p.constructor.prototype  //false 为什么??

其实也很好理解,p.constructor 不再指向 Person 了

p.constructor === Person                        //false
p.constructor === Person.prototype.constructor  //true

目前JS内置的构造函数有 12 个,全局环境下可以访问的有8个。所有构造函数都继承了 Function.prototype 的属性及方法,当然了,也包括 Function 自己和 Object。

Number.__proto__ === Function.prototype  // true
Boolean.__proto__ === Function.prototype // true
String.__proto__ === Function.prototype  // true
Function.__proto__ === Function.prototype // true
Array.__proto__ === Function.prototype   // true
Number.__proto__                         
//function Empty() {}
Empty                                    
//ReferenceError: Empty is not defined

以上 Empty 不是全局构造方法,但是,Number、Function、Array 等是全局构造方法

Number === window.Number   //true

当然了,“所有构造函数”也包括自定义的构造函数

var MyArray = function () {};
MyArray instanceof Function               //true
MyArray.__proto__ === Function.prototype  //true
MyArray.__proto__ === Function.__proto__  //true
Function.__proto__ === Function.prototype //true
Function.constructor  
//function Function() { [native code] }
Function.constructor === Function                      
//true
Function.constructor.prototype === Function.__proto__  
//true

函数也是一等公民

Function.prototype.__proto__ === Object.prototype

说明了所有的构造器也是一个普通的对象,继承了 Object.prototype 所有的方法

可以先 new 一个对象,然后修改该对象的 prototype 属性,例如:

var MyArray = function () {};
MyArray.prototype                        
//Object {}
MyArray.__proto__    
//function Empty() {}

MyArray.__proto__  === Object.__proto__  //true
MyArray.prototype = new Array();
MyArray.prototype.constructor = MyArray;

instanceof 运算符的实质,检查右边构造函数的 prototype 属性是否在左边的实例对象的原型链上。只要有一个符合就返回 true,否则返回 false。

var mine = new MyArray();

于是:

mine instanceof Array                //true

再回到 toString(),以下都很好理解了

toString([])             //"[object Undefined]"
toString.call(window,[]) //"[object Window]"
window.toString([])      //"[object Window]"
window.toString()        //"[object Window]"
toString.call([])        //"[object Array]"
[].toString()            //""
toString.call({}) //"[object Object]"
({}).toString()   //"[object Object]"
toString.call(true) //"[object Boolean]"
true.toString()     //"true"
toString.call(function(){}) //"[object Function]"
(function(){}).toString()   //"function (){}"
toString.call(1) //"[object Number]"
(1).toString()   //"1"
toString.call(null) //"[object Null]"
null.toString()
//TypeError: Cannot read property 'toString' of null
toString.call(undefined) //"[object Undefined]"
undefined.toString()     
//TypeError: Cannot read property 'toString' of undefined

views