JavaScript中识别native方法

Js中一些本地(native/built-in)对象和方法是可以重写的,比如在针对低版本浏览器的编程中,我们使用:

Array.prototype.forEach = function(item index arr){
    ...
};

来对ES5中才定义的方法增加polyfill。这样可能会带来的一些潜在的隐患问题暂且不表,但我们可以像在现代浏览器中一样安全地使用forEach方法了(只要你实现地足够稳健)。

当在陌生的或不可控的环境中运行JavaScript代码时,你可能不相信可能被 ‘polyfill’ 过的方法,比如Array.prototype.everyArray.prototype.mapwindow.Promise。这个时候,你需要识别你要使用的方法是一个 JavaScript 宿主环境的本地方法还是一个已经由别人 ‘polyfill’ 过的方法。

不过似乎没有任何标准或草案规定自定义方法和 native 方法应该在行为上表现出任何不同。但可以想象,JavaScript 引擎一般由C++语言编写,似乎 native 方法无法打印出其源代码。

默认情形下,将一个函数对象转成String类型即可输出其源代码,同样将方法名作为参数传入RegExp.prototype.test也会先转成String,John Resig 写的js简单继承实现中即使用这种方式来识别_super单词的。因此对于自定义方法,会输出源码,那么对于 native 方法,目前主流浏览器很一致性地输出类似function func_name() { [native code] }的字符串,甚至nodejs也同样,具体回车换行各种环境实现有略微差异,Javascript 实现的 DOM 选择器sizzle总结了一个通用的正则表达式:

/^[^{]+\{\s*\[native \w/

。通过对方法源码进行正则匹配来识别是本地方法与否。

这种通过源码进行识别的方式有一个问题,可以查看Ecma 262对于Funtion.prototype.toString的定义:

An implementation-dependent representation of the function is returned. This representation has the syntax of a FunctionDeclaration. Note in particular that the use and placement of white space line terminators and semicolons within the representation String is implementation-dependent.

也就是说并未针对native方法做了特殊规定,甚至没有规定方法默认应该输出为其源码。在将来的实现中,对于native方法的默认字符串表示可能出现变化,特别是Google的V8引擎中越来越多地直接使用JavaScript语言本身来编写。

另一方面,这种方式强依赖于toString方法的默认行为,因此重写toString可以达到欺骗的效果,如:

var s = Array.prototype.every = function(){
    return !!'this is a fake forEach!';
    };

s.toString = function(){
    return 'function every(){[native code]}';
};

//true but it shouldn't
/^[^{]+\{\s*\[native \w/.test(s);

一个自定义方法通过了检测。虽然这是一个极端的例子,但对于未有明确标准定义的,特别是将来有可能发生行为改变的内容,最好的使用方式就是不依赖于这种非标准的,不稳定的特性。

这里有一篇比较老的文章,其内容揭示了确实有老版本的Safari浏览器无法通过上面的正则表达式检测。