连续输入a,b,c,d并点击submit提交,正常情况应如下图:

jquery 如何实现全局常量 jquery定义全局变量_jquery 如何实现全局常量


然而实际运行结果如下:

jquery 如何实现全局常量 jquery定义全局变量_作用域链_02


jquery 如何实现全局常量 jquery定义全局变量_执行环境_03


jquery 如何实现全局常量 jquery定义全局变量_执行环境_04


jquery 如何实现全局常量 jquery定义全局变量_变量_05


如上图所示,每次添加新的Task,导致task_list中存储的Task内容都变为新的Task内容。

检查发现原因如下:
错误定义new_task为全局变量,导致task_list[i].content都一样,都指向全局变量new_list,因为JS数组的push方法操作的是地址指针,而非内存块操作。正确做法是在click事件回调函数中定义new_list为局部变量,这样每次click时函数内部都会定义一个独立的new_list变量。

$(function () {
    var list = [];
    var s = 10;
    list.push(s);
    console.log(list[0] === s); //true;证明JS数组的push操作的是地址指针,而非内存块
    
    init();
    function init() {
        var c = 30;
        list.push(c);
        console.log(list[1] === c); //true
        var a = [3,6,9];
        list.push(a[0]);
        console.log(list[2] === a[0]); //true
    }
   });

如果push()方法存入局部变量的引用地址,离开函数作用域后,局部变量不应该释放内存吗?那么引用地址还有效吗??这里涉及JS的内存和垃圾回收机制。
(一)JS的栈内存和堆内存

  1. JS变量分为基本类型(undefined/null/boolean/number/string)和引用类型(对象/数组/函数);
  2. 栈内存和堆内存。栈内存存放基本数据类型,按值访问,在当前执行环境结束时销毁;堆内存存放引用数据类型,按引用访问(同时在栈内存中保存一个基本类型值存储对象在堆内存中的地址),引用类型不会随执行环境结束而销毁,只有当所有引用它的变量不存在时这个对象才被垃圾回收机制回收。
  3. 当基本数据类型传递时,是复制了一个新的数据给另一个变量;而当引用类型传递时,复制的仅仅是引用数据类型的地址,两个变量通过地址指向的是同一个堆内存中的数据。JS代码运行图示参考

(二)JS的执行环境和作用域

  1. 执行环境和执行环境栈。每个函数都有自己的执行环境,当执行流进入一个函数时,函数的环境会被推入一个环境栈中,在函数执行之后,栈将其环境弹出。全局执行环境直到应用程序退出——如关闭网页或浏览器时才会被销毁。参考:https://www.jianshu.com/p/903b18d70baa
  2. 当代码在一个环境中执行时,会创建变量对象的一个作用域链,作用域链的前端始终是当前执行环境的变量对象,全局执行环境的变量对象始终是作用域链中的最后一个对象。每个环境都可以向上搜索作用域链,但不能向下搜索。牢记内部环境可通过作用域链访问所有外部环境,但外部环境不能访问内部环境中的任何变量和函数

(三)JS的自动垃圾收集机制——标记清除和引用计数

  1. 标记清除。垃圾回收器会在运行时给存储在内存中的所有变量加一个标记,然后去除环境中的变量以及被环境中的变量所引用的变量(闭包),剩下仍存在标记的就是要删除的变量了,因为环境中的变量已经无法访问到这些变量了。
  2. 引用计数。跟踪记录每个值被使用的次数,当引用次数为0的时候,将其占用的空间回收。缺点:存在内存泄漏,没有解决循环引用的问题。
  3. IE中有一部分对象并不是原生J对象。例如,其BOM和DOM中的对象就是使用C++以COM对象(组件对象)的形式实现的,而COM对象的垃圾回收器就是采用的引用计数的策略。因此,即使IE的Javascript引擎使用标记清除的策略来实现的,但JavaScript访问的COM对象依然是基于引用计数的策略的。说白了,只要IE中涉及COM对象,就会存在循环引用的问题。解决方法:手动将变量设置为null,切断循环引用的连接。IE9中把BOM和DOM对象都转换成了真正的JS对象,从而消除了内存泄漏现象。
  4. 内存性能优化:尽量不使用全局变量、全局变量不再有用时可手动解除引用(设置值为null,解除一个值的引用并不是自动回收该值占用的内存,而是让该值脱离执行环境,以便垃圾收集器下次运行时将其回收)。

HTML代码:

<!DOCTYPE html>
 <html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="css/main.css">
</head> 
<body>
<div class="container">
    <h1>My task</h1>
    <div class="add-task">
        <input id="txt" type="text" name="content" placeholder="e.g.下午记得买菜...">
        <button type="submit" id="btn">submit</button>
    </div>
    <div class="tasks-list" id="tasks-list">
    </div> </div>
<script src='js/jquery-1.7.2.js'></script> <script src="js/store.js"></script> <script src="js/main.js"></script>
</body> 
</html>

JS脚本:

$(function () {
    var $btn = $('#btn'),
        new_task = {},
        task_list = {};

    //store为本地存储,调用了store.js
    store.clear();

    init();
    function init() {
        //初始化时要注意若task_list中一条数据都没有,则会返回undefined;
        // task_list = store.get('task_list');
        task_list = store.get('task_list') || [];
        if(task_list.length)
        {
            render_task_list();
        }
    }

    $btn.on('click',function (e) {
        //var new_task = {}; //定义为局部变量
        
        //禁用默认行为
        e.preventDefault();
        //获取task的值
        new_task.content = $('#txt').val();
        if(!new_task.content) return;
        //将新task存入到task_list中
        addTask(new_task);
        //重新显示task_list内容
        render_task_list();
    });
    function addTask(new_task) {
        task_list.push(new_task);
        store.set('task_list',task_list);
    }
    function render_task_list() {
        var $tasks_list = $('#tasks-list');
        $tasks_list.html('');
        for(var i=0;i<task_list.length;i++)
        {
            var $task = render_task_item(task_list[i]);
            $tasks_list.append($task);
        }
    }
    function render_task_item(data) {
        var list_item =
            '<div class="task-item">'+
            '<span><input type=\'checkbox\'></span>'+
            '<span class="task-content">'+ data.content+'</span>'+
            ' <div class="right">'+
            '<span class="delete">删除</span>'+
            '<span class="detail">详细</span>'+
            '</div>';
        return $(list_item);
    }
})