这是一篇介绍类似于 js hack 的文章,只是说明了一种可行的途径,并且可能会增加代码复杂程度,具体项目中是否可以使用还请自辨:)

在前端开发过程中,有许多业务流程可能是需要用户进行登录的,并且登录过程是放在弹出层中,这样就可以不用刷新页面,增强用户体验。在登录时,用户的操作就会被打断,为了进一步增强用户体验,我们可能需要在登录完成后自动继续进行用户在登录前想进行的操作。

假设有这样一个场景,用户需要发表一个留言,但是发表留言是需要登录的,而发表留言的输入框是一直显示的,这也就要求在用户点击了发表按钮时对用户登录状态进行验证,传统的做法是将在用户登录状态检查封装在一个函数之中,这个函数接收一个回调参数,如果登录验证通过,则执行回调函数。这样的逻辑可以用以下代码表示:

function doAction() {
checkLogin(function() {
// 处理业务逻辑
});
}
function checkLogin(callback) {
if (isLogin) {
callback();
} else {
showLogin(callback);
}
}
function showLogin(callback) {
document.getElementById("login-btn").onclick = function() {
isLogin = true;
callback();
};
}

    总觉得这样的方式会将业务逻辑放到一个匿名函数中,而不是放在了按钮的事件响应函数中,感觉上不是那么好。这对整个事件响应函数的改动比较大,主要的业务逻辑都放在了登录验证函数的回调中。而希望可以是下面这样:

    function doAction() {
    // 直接在函数开始进行登录验证,将业务逻辑独立出来
    if (!doLogin()) {
    return;
    }
    // 处理业务逻辑
    }
    function doLogin() {
    if (isLogin) {
    return true;
    } else {
    var callback = function() {}; // 自动组装 callback
    showLogin(callback);
    return false;
    }
    }
    function showLogin(callback) {
    document.getElementById("login-btn").onclick = function() {
    isLogin = true;
    callback();
    };
    }

      这里的关键在于怎么“自动组装 callback”,很幸运的是,JavaScript 的 Function 提供了一个属性 caller 可以用来获取调用当前函数的函数是什么。并且还可以用 caller 的 arguments 属性来获取 caller 执行时的参数是什么,这也就使我们自动组装 callback 并恢复 caller 的原参数成为可能。

      <div id="result"></div>
      <input type="button" value="Sumit" onclick="sendPost()" id="post" />
      <input type="button" value="Clear login" onclick="isLogin=false" id="reset" />
      <div id='login' style="border:1px solid #000;padding:10px;margin:10px;display:none">
      <strong>Login</strong><br />
      <input type="button" id='loginbtn' value="Login" />
      </div>
      <script type="text/javascript">
      // 是否登录
      var isLogin = false;
      // 检查登录状态
      var checkLogin = function(cfg) {
      if (isLogin) {
      return true;
      }
      cfg = cfg || {};
      var callback = null;
      // 自动执行回调
      if (cfg.autoCallback && arguments.callee.caller) {
      try {
      var caller  = arguments.callee.caller,
      args    = caller.arguments || [],
      scope   = cfg.callbackScope || {},
      newArgs = [];
      for (var i = 0; i < args.length; ++i) {
      newArgs.push(args[i]);
      }
      callback = caller ? function() {
      try {
      caller && caller.apply(scope, newArgs);
      } catch (e) {
      //alert(e.message);
      }
      } : null;
      } catch (e) {
      callback = null;
      }
      }
      showLogin(callback);
      return false;
      };
      function sendPost() {
      if (!checkLogin({autoCallback:true})) {
      return;
      }
      document.getElementById('result').innerHTML += 'Hello<br />';
      }
      function showLogin(callback) {
      document.getElementById('login').style.display = ';
      document.getElementById('loginbtn').onclick = function() {
      document.getElementById('login').style.display = 'none';
      isLogin = true;
      callback && callback();
      }
      }
      </script>

      其实弄出这么个方法来实现登录自动回调也是为了方便写代码,不用每次去将一堆业务逻辑放到 callback 中,直接在函数开始回一个 if (!checkLogin({autoCallback:true})) 就行。

      caller 属性在 MDC 中被标记为“非标准”的,但是在主流浏览器中,都是支持这个属性的,也就是说,在浏览器兼容性上使用 caller 是没有问题的。

      不过,使用奇技淫巧容易伤身体,慎用:)