[Effective JavaScript 笔记]第28条:不要信赖函数对象的toString方法
js函数有一个非凡的特性,即将其源代码重现为字符串的能力。
(function(x){
return x+1
}).toString();//"function (x){ return x+1}"
反射获取函数源代码的功能很强大,使用函数对象的toString方法有严重的局限性。
toString方法的局限性
ECMAScript标准对函数对象的toString方法的返回结果(即该字符串)并没有任何要求。这意味着不同的js引擎将产生不同的字符串,甚至产生的字符串与该函数并不相关。
如果函数是使用纯js实现的,那么js引擎会试图提供该函数的源代码的真实表示。
一个失败的例子
(function(x){
return x+1
}).bind(16).toString();//"function () { [native code] }"
失败原因:
使用了由宿主环境的内置库提供的函数。
由于许多宿主环境中,bind函数是由其他编程语言实现的(通常是c++)。宿主环境提供的是一个编译后的函数,在此环境下该函数没有js的源代码供显示。
由于标准允许浏览器引擎改变toString方法的输出,很容易使编写的程序一个js系统中正确运行,在其他js系统中却无法正确运行。程序对函数的源代码字符串的具体细节很敏感,即使js的实现有一点细微的变化都可能破坏程序。
由toString方法生成的源代码并不展示闭包中保存的与内部变量引用相关的值
(function(x){
return function(y){
return x+y;
}
})(42).toString();//"function (y){ return x+y; }"
注意:尽管函数实际上是一个绑定x为42的闭包,但结果字符串仍然包含一个引用x的变量。
从某种意义上说,js的toString方法的这些局限使其用来提取函数源代码并不是特别有用和值得信赖。通常应该避免使用它。对提取函数源代码相当复杂的使用应当采用精心制作的js解释器和处理库。将js函数看作是一个不该违背的抽象是最稳妥的。
提示
当调用函数的toString方法时,并没有要求js引擎能够精确地获取到函数的源代码
由于在不同的引擎下调用toString方法的结果可能不同,所以绝不要信赖函数源代码的详细细节
toString方法的执行结果并不会暴露存储在闭包中的局部变量
通常情况下,应该避免使用函数对象的toString方法
附录一:toString方法
不同数据类型调用toString方法的结果。
toString方法是Object原型对象中的一个方法,所以继承自这个类的对象都会继承这个方法,并可以对toString方法进行覆盖。
js标准库中的5种简单数据类型:Undefined,Null,Boolean,Number和String。还有一种复杂的数据类型Object,Object的本质是一组无序的名值对组成。
简单数据类型
//数字
(Undefined).toString();//"error"
(Null).toString();//error
(true).toString();//"true"
(1).toString();//"1"
('111').toString();//"111"
可以看出其中除了Undefined和Null类型外,为什么其它几个基本类型可以运行呢。
这在我们之前的文章《[Effective JavaScript 笔记] 第4条:原始类型优于封闭对象》中讲到,当简单数据类型调用toString方法会首先把原始类型转换成包装对象。
此时对应的包装对象为
数字为Number对象
布尔值为Boolean对象
字符串为String对象
这些对象也都是继承自Object对象的,并重写了各自的toString方法。
但Undefined类型和Null类型都只有一个值undefined,null,并没有对应的封装对象。
虽然typeof null的值是"object",但并没用。
引用类型
//Object对象
({a:10,b:20}).toString();//"[object Object]"
//Date对象
(new Date).toString();//"Tue Jun 07 2016 15:37:15 GMT+0800 (中国标准时间)"
//RegExp对象
(/^sss$/g).toString();//"/^sss$/g"
//Function对象
function aa(){return "bb"}
aa.toString();//"function aa(){return "bb"}"
//window对象
window.toString();//"[object Window]"
//Math对象
Math.toString();//"[object Math]"
看到上面的toString方法,Object,window,Math是使用Object原型方法。其它对象都使用了自身覆盖的toString方法。
typeof操作符
对以上所有类型使用typeof操作符时会得到以下的结果
typeof 1;//"number"
typeof '1';//"string"
typeof true;//"boolean"
typeof undefined;//"undefined"
typeof (function a(){});//"function"
typeof null;//"object"
typeof {};//"object"
typeof (new Date);//"object"
typeof [];//"object"
typeof window;//"object"
typeof Math;//"object"
typeof (/sdfsf/g);//"object"
可以看出,想使用单单的typeof操作符来对类型进行判断几乎是不可能的。
有人可能会说对于返回object字符串,可以使用构造函数来判断类型即instanceOf方法。
({}) instanceof Object;//true
(new Date) instanceof Date;//true
([]) instanceof Array;//true
(/sdfsf/g) instanceof RegExp;//true
然后null类型只要
var a=null;
a===null;//true;
好像可以实现下面这样的类型判断代码了
function getType(obj){
if(typeof obj !== 'object'){
return typeof obj;
}else{
if(obj===null){
return 'null';
}
if(obj===window){
return 'window';
}
if(obj===Math){
return 'Math'
}
if((obj) instanceof Date){
return 'date';
}
if((obj) instanceof Array){
return 'array';
}
if((obj) instanceof RegExp){
return 'regexp';
}
if((obj) instanceof Object){
return 'object';
}
}
}
上面代码是否可以运行测试一下,并没有问题
getType(1);//"number"
getType(true);//"boolean"
getType('1');//"string"
getType(undefined);//"undefined"
getType(function(){});//"function"
getType(/sf/);//"regexp"
getType(null);//"null"
getType(window);//"window"
getType({});//"object"
getType([]);//"array"
但这里要注意的一个问题就是,这个代码里的对于object类型的检测一定要放到最后面。
如下所示,所有对象都是继承自Object,所以instanceof检测所有对象是否为Object类型的实例返回都是true
([]) instanceof Array;//true
([]) instanceof Object;//true
看到以上代码是不是觉得太复杂麻烦了,有没有一种更简单的方法来对类型进行判断呢?答案当然是有,下面来看toString方法的运用。
toString应用
如上面所说,继承自Object的对象都有toString方法,但每个对象实现了各自的toString方法,导致无法用toString方法进行类型判断。这里可以利用之前讲到过的call或apply方法来调用Object.prototype.toString方法。
function getType(obj){
var toString=Object.prototype.toString;
return toString.call(obj);
}
测试一下各类型会得到如下结果
getType(1);//"[object Number]"
getType(true);//"[object Boolean]"
getType('1');//"[object String]"
getType(undefined);//"[object Undefined]"
getType(function(){});//"[object Function]"
getType(/sf/);//"[object RegExp]"
getType(null);//"[object Null]"
getType(window);//"[object Window]"
getType({});//"[object Object]"
getType([]);//"[object Array]"
getType(Math);//"[object Math]"
所有类型都可以区分出来,是不是很简单呀!
还有个特殊的值需要注意NaN.
getType(NaN);//"[object Number]"
NaN是一个Number类型的特殊值,它是表达是一个不是一个数字的值,这里对这个值也要进行处理。可以关注之前文章《[Effective JavaScript笔记]第3条:当心隐式的强制转换》里关于NaN的内容。处理代码如下
function isReallyNaN(x){
return x!==x;
}
备忘:
这里需要去了解一下,js解释器的知识。
相关的链接有:
javascript设计模式之解释器模式详解
javascript设计模式 - 解释器模式(interpreter)
Chrome V8
[Effective JavaScript 笔记]第28条:不要信赖函数对象的toString方法的更多相关文章
- [Effective JavaScript 笔记]第58条:区分数组对象和类数组对象
示例 设想有两个不同类的API.第一个是位向量:有序的位集合 var bits=new BitVector(); bits.enable(4); bits.enable([1,3,8,17]); bi ...
- [Effective JavaScript 笔记]第3章:使用函数--个人总结
前言 这一章把平时会用到,但不会深究的知识点,分开细化地讲解了.里面很多内容在高3等基础内容里,也有很多讲到.但由于本身书籍的篇幅较大,很容易忽视对应的小知识点.这章里的许多小提示都很有帮助,特别是在 ...
- [Effective JavaScript 笔记] 第4条:原始类型优于封闭对象
js有5种原始值类型:布尔值.数字.字符串.null和undefined. 用typeof检测一下: typeof true; //"boolean" typeof 2; //&q ...
- [Effective JavaScript 笔记] 第5条:避免对混合类型使用==运算符
“1.0e0”=={valueOf:function(){return true;}} 是值是多少? 这两个完全不同的值使用==运算符是相等的.为什么呢?请看<[Effective JavaSc ...
- [Effective JavaScript 笔记]第27条:使用闭包而不是字符串来封装代码
函数是一种将代码作为数据结构存储的便利方式,代码之后可以被执行.这使得富有表现力的高阶函数抽象如map和forEach成为可能.它也是js异步I/O方法的核心.与此同时,也可以将代码表示为字符串的形式 ...
- [Effective JavaScript 笔记]第45条:使用hasOwnProperty方法以避免原型污染
之前的43条,44条讨论了属性的枚举,但都没有彻底地解决属性查找中原型污染的问题.看下面关于字典的一些操作 'zhangsan' in dict; dict.zhangsan; dict.zhangs ...
- [Effective JavaScript 笔记]第20条:使用call方法自定义接收者来调用方法
不好的实践 函数或方法的接收者(即绑定到特殊关键字this的值)是由调用者的语法决定的.方法调用语法将方法被查找的对象绑定到this变量,(可参阅之前文章<理解函数调用.方法调用及构造函数调用之 ...
- [Effective JavaScript 笔记]第47条:绝不要在Object.prototype中增加可枚举的属性
之前的几条都不断地重复着for...in循环,它便利好用,但又容易被原型污染.for...in循环最常见的用法是枚举字典中的元素.这里就是从侧面提出不要在共享的Object.prototype中增加可 ...
- [Effective JavaScript 笔记]第51条:在类数组对象上复用通用的数组方法
前面有几条都讲过关于Array.prototype的标准方法.这些标准方法被设计成其他对象可复用的方法,即使这些对象并没有继承Array. arguments对象 在22条中提到的函数argument ...
随机推荐
- 利用php实现文件迁移重命名
首先表明,这是一个悲伤的故事. 暑假来临,学校安排我们到某软件外包公司实习,想想不用面试也是蛮方便的,可以借此机会向大牛学习学习,虽然没有工资(据说学校还交了600块的保险),但想想还是蛮期待的,但真 ...
- dom4j 使用总结
dom4j是一个Java的XML API,类似于jdom,用来读写XML文件 dom4j的使用方法简单总结来说如下: ①可以创建一个新的xml文件 ②利用SAXReader和File对象创建一个已存在 ...
- ModernUI教程:第一个ModernUI应用(采用项目模板)
在我们开始之前,请确保你已经为你的Visual2012或者2013安装了ModernUI for WPF的模板扩展: >>从Visual Studio 库 下载并安装VSIX扩展 > ...
- ModelProxy 前端接口配置建模框架
ModelProxy 轻量级的接口配置建模框架(1) 先看一下这个博客说明为什么需要用ModelProxy的前端轻量级的框架吧: http://developer.51cto.com/art/ ...
- 【ZOJ 3897】Candy canes//Fiddlesticks
题 题意 给你一串数,a1...an,从左到右每次让一个数减小c,如果这个数小于c,那就减为0.第n个数减小后,又从第一个开始从左到右.如果这次某个数减小到0,那就改变方向,如果遇到已经是0的,就跳过 ...
- BZOJ-1607 [Usaco2008 Dec]Patting Heads 轻拍牛头 筛法+乱搞
1607: [Usaco2008 Dec]Patting Heads 轻拍牛头 Time Limit: 3 Sec Memory Limit: 64 MB Submit: 1383 Solved: 7 ...
- BZOJ1034 [ZJOI2008]泡泡堂BNB
Description 第XXXX届NOI期间,为了加强各省选手之间的交流,组委会决定组织一场省际电子竞技大赛,每一个省的代表 队由n名选手组成,比赛的项目是老少咸宜的网络游戏泡泡堂.每一场比赛前,对 ...
- mysql存储过程的学习
平时在工作中写过很多存储过程,但有时候对某些存储过程还是有些困惑的,所以发一篇文章记录下. 标准存储过程写法 create procedure`myQueryTask`( IN Task_No VAR ...
- MySQL逻辑备份与恢复
备份:mysqldump -uroot -p yyzc department > /home/admin/yyzc_backup.sql 恢复:mysql -uroot -p yyzc < ...
- BC68(HD5606) 并查集+求集合元素
tree Accepts: 143 Submissions: 807 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65 ...