这几天在看《深入理解ES6》,这本书的作者是Nicholas C. Zakas,也就是我最爱的《高程》作者~~不过还是得把目标中的文章部分写一下。
今天我要讲的是toString()和valueOf()。
toString()
相比于toString(),我更愿意叫它实例对象.proto.toString()。
顾名思义:toString()就是将任何数据类型转换为字符串类型(Null、undefined以及[object Object]除外)。
那么number类型和boolean也会转换吗?答案是当然~
let obj = {
"name" : "suoz",
"age" : 20
};
let num = 123;
let arr = ["1","red",3];
let flag = true;
console.log(obj.toString()); //[object Object]
console.log(typeof(num.toString())); //string
console.log(typeof(arr.toString())); //string
console.log(typeof(flag.toString())); //string
我觉得这里会疑惑两点
第一点:为什么number和boolean类型的变量可以调用toString()方法。
在另外一篇文章中我说过,number、boolean、string类型可以是属于一种特殊的引用类型(基本包装类型)。
let num = 123;
num.toString(); //读取模式
当创建其中一种类型的变量时,在访问该变量的时候(”读取”模式),后台会自动帮助我们完成以下操作。
let num = new Number(123); //创建一个Number类型的对象
num.toString(); //使用该对象的方法
num = null; //使用后 立即将该对象销毁
这就在不知不觉中,用户调用了number类型的方法,但是没有任何感觉。
第二点:Object类型调用toString()方法返回的是[object Object]??
其实这就为我们下面的Object.prototype.toString()的介绍作了铺垫。
这里先粗略解释,因为使用toString()方法调用的是该对象[[proto]]指向的原型对象内部的toString(),而不是Object.prototype.toString(),但由于Object类型的对象[[proto]]指向的原型对象还是Object.prototype.toString(),所以它调用了这个方法。
let obj = {"name":"suoz"};
console.log(obj.toString()); //[object Object]
console.log(obj.__proto__ === Object.prototype); //true
Object.prototype.toString = function(){
console.log("hello world!");
};
console.log(obj.toString()); //hello world!
其实它内部实现机制是这样的:
实例对象.__proto__.toString = function(){
var str = "";
for(var i=0; i<this.length-1; i++) {
str += this[i] + ",";
}
str += this[this.length-1];
return str;
};
Object.prototype.toString()
通常我们可以使用它来进行引用数据类型的判断。
let num = new Number(123);
let str = new String("hello");
let flag = new Boolean(true);
let arr = ["red","pink"];
let obj = {"name":"suoz"};
let a = null;
let b = undefined;
console.log(Object.prototype.toString.call(num)); //[object Number]
console.log(Object.prototype.toString.call(str)); //[Object String]
console.log(Object.prototype.toString.call(flag)); //[Object Boolean]
console.log(Object.prototype.toString.call(arr)); //[Object Array]
console.log(Object.prototype.toString.call(obj)); //[Object Object]
console.log(Object.prototype.toString.call(a)); //[Object NULL]
console.log(Object.prototype.toString.call(b)); //[Object Undefined]
每次创建一个对象,都会为该对象的原型对象内部增加toString方法,所以直接调用对象的toString方法时,根据原型链机制,会调用对象[[proto]]指向的原型对象内部toString,而不是Object.prototype内部的toString
let arr = ["red","color"];
console.log(arr.toString()); //"red,color"
console.log(Object.prototype.toString.call(arr)); //[object Array]
console.log(arr.__proto__.hasOwnProperty("toString")); //true
console.log(Object.prototype.hasOwnProperty("toString")); //true
arr.__proto__.toString = function(){
console.log("hello world!");
};
console.log(arr.toString()); //hello world!
arr.__proto__.toString = null;
console.log(arr.toString()); //[object Array]
valueOf()
第一种情况:基本数据类型
let num = 123;
let str = "hello";
let flag = true;
console.log(num.__proto__.hasOwnProperty("valueOf")); //true
console.log(num.valueOf()); //123
Object.prototype.valueOf = function(){
console.log("Object.prototype.valueOf");
};
console.log(num.valueOf()); //123
num.__proto__.toString = function(){
console.log("toString");
};
console.log(num.valueOf()); //123
num.__proto__valueOf = function(){
console.log("num.__proto__.valueOf");
};
console.log(num.valueOf()); //num.__proto__.valueOf
创建一个基本数据类型的值时,对象[[proto]]指向原型对象内部有valueOf,所以会覆盖Object.prototype.valueOf方法。(并且内部的valueOf和Object.prototype.valueOf方法内部实现机制不一样,前者不会调用toString,而后者会调用原型对象内部的toString方法,下面会仔细讲解)
第二种情况分析在下面。
Object.prototype.valueOf()
定义:将对象转换为原始值(number、string、boolean类型)。你很少需要自己调用此函数,当遇到一种需要转换为原始值的情况时,系统会自动调用。
默认情况下, valueOf() 会被每个对象Object继承。每一个内置对象都会覆盖这个方法为了返回一个合理的值,如果对象没有原始值,valueOf() 就会返回对象自身。
第二种情况:引用数据类型
let obj = {"name":"suoz"};
let arr = ["red","pink"];
console.log(arr.__proto__.hasOwnProperty("valueOf")); //false
console.log(Object.prototype.hasOwnproperty("valueOf")); //true
console.log(arr.__proto__.hasOwnProperty("toString")); //true
console.log(arr.valueOf()); //["red","color"]
Object.prototype.valueOf = function(){
console.log("valueof");
};
console.log(arr.valueOf()); //valueOf、undefined
验证创建一个引用类型对象(除了Object类型),该实例对象[[proto]]指向的原型对象内部没有valueOf方法,只有toString方法。
Object.prototype.toString = function(){
console.log("Object toString");
};
console.log(arr.valueOf()); //["red","pink"]
console.log(arr == "red,pink"); //true
arr.__proto__.toString = function(){
console.log("__proto__ toString");
return "red,pink";
};
console.log(arr.valueOf()); //__proto__ toString、["red","pink"]
console.log(arr == "red,pink"); //__proto__ toString、true
验证了Object类型和Array类型都是用的Object.prototype.valueOf(),而Object.prototype.valueOf内部实现机制中包括调用了实例对象原型对象的toString方法
这里我疑惑的是为什么”==”返回”red,pink”,而调用valueOf()返回的是[“red”,”pink”],它们不都是直接使用了Object.prototype.valueOf吗(并且该方法内部又调用了实例对象[[proto]]指向原型对象的toString方法吗)?
Object.prototype.valueOf = function(){
console.log("Object valueOf");
};
console.log(arr.valueOf()); //Object valueOf、undefined
console.log(arr == "red,pink"); //Object valueOf、false
我觉得这里疑惑点还是得考了解一下Object.prototype.valueOf内部实现机制(源代码)去解决。(个人认为可以暂时性按下面的代码理解内部,后续补充)
arr.__proto__.toString = function(){
return "red,pink";
};
Object.prototype.valueOf = function(){
var str = arr.__proto__.toString.call(this);
if(//如果这里是"=="){
//return str; //"red,pink"
}else if(//如果这里是直接调用valueOf){
//return this; //返回对象本身 ["red","pink"]
}
};
什么时候调用toString或valueOf(后续补充)
总结:
- 使用Object.prototype.toString().call()方法,可以判断任何数据类型。
- 创建一个对象,该对象内部的方法toString会覆盖Object.prototype.toString方法,因此直接调用toString的时候,根据原型链搜索属性机制,会搜索该对象[[proto]]指向的原型对象内部的toString方法。
- [[proto]]指向的原型对象内部的toString方法 与 Object.prototype.toString方法内部实现机制是不一样的。前者是返回字符串类型值,后者是返回数据的类型[object XXX]
- 对于基本数据类型来说,创建一个变量或者对象,内部都会有valueOf方法覆盖Object.prototoye.valueOf方法。
- 对于引用类型来说,创建一个对象,内部不会覆盖方法,所以调用valueOf还是使用的Object.prototype.valueOf方法。
- 对于基本类型来说,覆盖的valueOf方法不会隐式调用toString方法
- 对于引用类型来说,Object.prototype.valueOf方法内部会隐式调用toString方法。