JavaScript也谈内存优化(3)


;(function(win, doc, $, undefined) {
  // 主业务代码
})(window, document, jQuery);


甚至连如RequireJS, SeaJS, OzJS 等前端模块化加载解决方案,都是采用类似的形式:

复制代码 代码如下:


// RequireJS
define(['jquery'], function($) {
  // 主业务代码
});

// SeaJS
define('m​​odule', ['dep', 'underscore'], function($, _) {
  // 主业务代码
});


如果你说很多Node.js 开源项目的代码都没有这样处理的话,那你就错了。Node.js 在实际运行代码之前,会把每一个.js 文件进行包装,变成如下的形式:

复制代码 代码如下:

 
(function(exports, require, module, __dirname, __filename) {
  // 主业务代码
});

这样做有什么好处?我们都知道文章开始的时候就说了,JavaScript中能形成作用域的有函数的调用、with语句和全局作用域。而我们也知道,被定义在全局作用域的对象,很有可能是会一直存活到进程退出的,如果是一个很大的对象,那就麻烦了。比如有的人喜欢在JavaScript中做模版渲染:

复制代码 代码如下:

 
<?php
  $db = mysqli_connect(server, user, password, 'myapp');
  $topics = mysqli_query($db, "SELECT * FROM topics;");
?>
<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>你是猴子请来的逗比么?</title>
</head>
<body>
  <ul></ul>
  <script type="text/tmpl">
    <li>
      <h1><%=title%></h1>
      <p><%=content%></p>
    </li>
  </script>
  <script type="text/javascript">
    var data = <?php echo json_encode($topics); ?>;
    var topicTmpl = document.querySelector('#topic-tmpl').innerHTML;
    var render = function(tmlp, view) {
      var complied = tmlp
        .replace(/\n/g, '\\n')
        .replace(/<%=([\s\S]+?)%>/g, function(match, code) {
          return '" + escape(' + code + ') + "';
        });

      complied = [
        'var res = "";',
        'with (view || {}) {',
          'res = "' + complied + '";',
        '}',
        'return res;'
      ].join('\n');

      var fn = new Function('view', complied);
      return fn(view);
    };

    var topics = document.querySelector('#topics');
    function init()    
      data.forEach(function(topic) {
        topics.innerHTML += render(topicTmpl, topic);
      });
    }
    init();
  </script>
</body>
</html>

这种代码在新手的作品中经常能看得到,这里存在什么问题呢?如果在从数据库中获取到的数据的量是非常大的话,前端完成模板渲染以后,data变量便被闲置在一边。可因为这个变量是被定义在全局作用域中的,所以JavaScript引擎不会将其回收销毁。如此该变量就会一直存在于老生代堆内存中,直到页面被关闭。

可是如果我们作出一些很简单的修改,在逻辑代码外包装一层函数,这样效果就大不同了。当UI渲染完成之后,代码对data的引用也就随之解除,而在最外层函数执行完毕时,JavaScript引擎就开始对其中的对象进行检查,data也就可以随之被回收。

3.2 绝对不要定义全局变量

我们刚才也谈到了,当一个变量被定义在全局作用域中,默认情况下JavaScript 引擎就不会将其回收销毁。如此该变量就会一直存在于老生代堆内存中,直到页面被关闭。

那么我们就一直遵循一个原则:绝对不要使用全局变量。虽然全局变量在开发中确实很省事,但是全局变量所导致的问题远比其所带来的方便更严重。

使变量不易被回收;
1.多人协作时容易产生混淆;
2.在作用域链中容易被干扰。
3.配合上面的包装函数,我们也可以通过包装函数来处理『全局变量』。

3.3 手工解除变量引用

如果在业务代码中,一个变量已经确切是不再需要了,那么就可以手工解除变量引用,以使其被回收。

复制代码 代码如下:


var data = { /* some big data */ };
// blah blah blah
data = null;

3.4 善用回调

除了使用闭包进行内部变量访问,我们还可以使用现在十分流行的回调函数来进行业务处理。

复制代码 代码如下:

 
function getData(callback) {
  var data = 'some big data';

  callback(null, data);
}

getData(function(err, data) {
  console.log(data);

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wgpjgy.html