[Effective JavaScript 笔记]第28条:不要信赖函数对象的toString方法

时间:2022-09-06 15:32:35

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方法的更多相关文章

  1. [Effective JavaScript 笔记]第58条:区分数组对象和类数组对象

    示例 设想有两个不同类的API.第一个是位向量:有序的位集合 var bits=new BitVector(); bits.enable(4); bits.enable([1,3,8,17]); bi ...

  2. [Effective JavaScript 笔记]第3章:使用函数--个人总结

    前言 这一章把平时会用到,但不会深究的知识点,分开细化地讲解了.里面很多内容在高3等基础内容里,也有很多讲到.但由于本身书籍的篇幅较大,很容易忽视对应的小知识点.这章里的许多小提示都很有帮助,特别是在 ...

  3. [Effective JavaScript 笔记] 第4条:原始类型优于封闭对象

    js有5种原始值类型:布尔值.数字.字符串.null和undefined. 用typeof检测一下: typeof true; //"boolean" typeof 2; //&q ...

  4. [Effective JavaScript 笔记] 第5条:避免对混合类型使用==运算符

    “1.0e0”=={valueOf:function(){return true;}} 是值是多少? 这两个完全不同的值使用==运算符是相等的.为什么呢?请看<[Effective JavaSc ...

  5. &lbrack;Effective JavaScript 笔记&rsqb;第27条:使用闭包而不是字符串来封装代码

    函数是一种将代码作为数据结构存储的便利方式,代码之后可以被执行.这使得富有表现力的高阶函数抽象如map和forEach成为可能.它也是js异步I/O方法的核心.与此同时,也可以将代码表示为字符串的形式 ...

  6. &lbrack;Effective JavaScript 笔记&rsqb;第45条:使用hasOwnProperty方法以避免原型污染

    之前的43条,44条讨论了属性的枚举,但都没有彻底地解决属性查找中原型污染的问题.看下面关于字典的一些操作 'zhangsan' in dict; dict.zhangsan; dict.zhangs ...

  7. &lbrack;Effective JavaScript 笔记&rsqb;第20条:使用call方法自定义接收者来调用方法

    不好的实践 函数或方法的接收者(即绑定到特殊关键字this的值)是由调用者的语法决定的.方法调用语法将方法被查找的对象绑定到this变量,(可参阅之前文章<理解函数调用.方法调用及构造函数调用之 ...

  8. &lbrack;Effective JavaScript 笔记&rsqb;第47条:绝不要在Object&period;prototype中增加可枚举的属性

    之前的几条都不断地重复着for...in循环,它便利好用,但又容易被原型污染.for...in循环最常见的用法是枚举字典中的元素.这里就是从侧面提出不要在共享的Object.prototype中增加可 ...

  9. &lbrack;Effective JavaScript 笔记&rsqb;第51条:在类数组对象上复用通用的数组方法

    前面有几条都讲过关于Array.prototype的标准方法.这些标准方法被设计成其他对象可复用的方法,即使这些对象并没有继承Array. arguments对象 在22条中提到的函数argument ...

随机推荐

  1. 杂谈:用 Sublime Text 2 写 ActionScript3

    Sublime Text这是程序员最喜爱的编辑器,说说在win7下使用Sublime Text来编写as文件以及编译与运行swf. 准备工作 1.Sublime Text 2 2.Java 的JDK( ...

  2. 【python】引用其他目录文件

    假设有 目录/A(a.py), 目录/B(b.py), 括号里是目录中的文件 在目录/A中编写a2.py,里面可以import a,但是不能import b 解决方法 import sys sys.p ...

  3. 3&period;mvc core 文件目录详细的解释

    wwwroot 放js css image的文件夹,静态文件. favicon.ico 网站图标.上传文件的话最好在里面新建一个Upload的文件夹进行管理 Controllers 控制器, View ...

  4. Unity中有两种Animation Clip

    http://blog.csdn.net/zzxiang1985/article/details/51291861 在Unity中,我们有两种方法创建Animation Clip. 一种(后面简称方法 ...

  5. Notepad&plus;&plus;配置Python运行环境

    转自:http://www.cnblogs.com/zhcncn/p/3969419.html Notepad++配置Python开发环境   1. 安装Python 1 下载 我选择了32位的2.7 ...

  6. &lbrack;转&rsqb;Java远程方法调用

    Java远程方法调用,即Java RMI(Java Remote Method Invocation)是Java编程语言里,一种用于实现远程过程调用的应用程序编程接口.它使客户机上运行的程序可以调用远 ...

  7. IIS7内建账号,应用程序池

    在IIS7以前的IIS版本中有一个本地帐号,是在安装时创建的,叫做 IUSR_MachineName.一旦启用匿名身份认证,这个IUSR_MachineName帐号就是IIS默认使用的身份(ident ...

  8. SpannableString可以被点击的文字

    1 TextView tv= (TextView) findViewById(R.id.textview_z); String text="一段可以被点击点击的文字,文字可以变成图片&quo ...

  9. GUI界面操作-实现简单的记事本

    wxPython编写界面程序的基本流程: 1.import wx   #导入wxPython的包 2.class App(wx.App)   #子类化一个应用程序类 3.def onInit(self ...

  10. Xcode调试与其他

    在项目中设置main接收的参数,模拟终端输入 Product->Scheme->Edit Scheme->Run->Arguments 例: 相当于在终端执行命令:./ac-t ...