今天在看Highcharts源码的时候,发现一个比较有意思的函数wrap,代码只有8行,不过却实现了对函数功能进行扩展的能力,作者javascript功底略见一斑,本篇文章做下记录和分析。
首先来看一个问题,众所周知的parseInt函数,其实有两个入参,我们在使用的时候往往不用第二个入参。这个时候parseInt(str),如果str以"0x"开头,则认为是十六进制;如果是以"0"开头,则认为是八进制。但是现实情况呢,在复杂的业务代码中有时候不可避免会出现"099","0AA"这样的字符串,他们显然不是八进制,而是十进制和十六进制。
parseInt("099"),在IE6~8下都返回0,其它浏览器,以及版本高于8的浏览则能正确返回99,可见高版本浏览器要智能一些;
parseInt("0AA"),几乎在所有浏览器下,都返回0,这说明浏览器依旧是不够智能。
我们如何来完善这个问题呢,当然了,我们可以自己重写parseInt,覆盖原生的parseInt。而Haigcharts却用8行代码封装了这样一个用于扩展函数功能的小工具——wrap函数:
- var wrap = Highcharts.wrap = function (obj, method, func) {
- // 备份原函数
- var proceed = obj[method];
- // 扩展原函数
- obj[method] = function () {
- // 拷贝一份实参
- var args = Array.prototype.slice.call(arguments);
- // 将原函数的引用放到第一个入参
- args.unshift(proceed);
- // apply保持上下文调用func
- return func.apply(this, args);
- };
- };
下面我就用wrap来实现对parseInt函数的完善:
- wrap(window,'parseInt',function(proceed, str, scale){
- if(typeof str === "number") return proceed.call(this,str);
- var hexChars = 'abcdef', decChars = '9';
- var hasHex = false, hasDec = false;
- if(!scale){
- if(str.charAt(0)==='0'){
- if(str.charAt(1).toLowerCase()==='x'){// 0x开头 十六进制
- return proceed.call(this,str,16);
- }else{
- for(var i= 1,len=str.length; i<len; i++){
- var char = str.charAt(i).toLowerCase();
- if(hexChars.indexOf(char)>-1) hasHex = true;
- if(decChars.indexOf(char)>-1) hasDec = true;
- }
- if(hasHex){// 0开头,但是包含ABCDEF等字符,则认为是十六进制
- return proceed.call(this,str,16);
- }else if(hasDec){// 0开头,但是包含9,认为是十进制
- return proceed.call(this,str,10);
- }else{// 这才是真正的八进制
- return proceed.call(this,str,8);
- }
- }
- }
- return proceed.call(this,str,10);
- }
- });
- console.log(parseInt("0777"));// 511
- console.log(parseInt("090"));// 90
- console.log(parseInt("0AA"));// 170
- console.log(parseInt(12345));// 12345
其它类库的实现
Underscore的实现
在其它一些实用的js库里面也有类似函数的封装,比较典型的是Undescore.js,代码甚至比Highcharts的实现还要简洁一行,实现逻辑基本一致,我们来看看:
- _.wrap = function(func, wrapper) {
- return function() {
- var args = [func];
- //这个地方用了一个小技巧,把func放到数组第一个元素,不过push方法能自动把arguments参数转成数组元素,这个小技巧我也是才知道
- Array.prototype.push.apply(args, arguments);
- return wrapper.apply(this, args);
- };
- };
要注意的是Underscore的实现跟Highcharts是有区别的,Highcharts是直接替换掉了原函数,而Underscore并没有替换掉原函数,必须要重新赋值一次以覆盖原函数,比如:
- // 这是Underscore里面的wrap函数,我把它拿出来了
- function wrap(func, wrapper) {
- return function() {
- var args = [func];
- //这个地方用了一个小技巧,把func放到数组第一个元素,不过push方法能自动把arguments参数转成数组元素,这个小技巧我也是才知道
- Array.prototype.push.apply(args, arguments);
- //apply this是为了保持原函数的上下文
- return wrapper.apply(this, args);
- };
- }
- // 一个对象,算是个人吧
- var person = {
- age: 33,
- // 本人今年33
- name: "世纪之光",
- //江湖人称 世纪之光
- marry: false,
- //依旧是一条老光棍
- // 生活虽然不顺,但是我依旧乐观,见人要有礼貌
- hello: function(who) {
- console.log("hello " + who);
- return this.name + " says: Hello " + who;
- }
- }
- // 必须要用wrap函数返回的匿名函数覆盖原来的定义
- person.hello = wrap(person.hello,
- function(func, name, more) {
- return func(name) + " " + more;
- });
- var str = person.hello("kitty", "have dinner?");
- console.log(str);
lowpro.jquery.js中的实现
- $.extend({
- // 类似于Underscore里面的bind函数,让this始终指向上下文scope
- bind: function(func, scope) {
- return function() {
- return func.apply(scope, $.makeArray(arguments));
- }
- },
- wrap: function(func, wrapper) {
- var __method = func;
- return function() {
- return wrapper.apply(this, [$.bind(__method, this)].concat($.makeArray(arguments)));
- }
- }
- };