转到正文

浪淘沙

静观己心,厚积薄发

存档

2014 年 5 月 的存档

原文地址:理解Javascript的闭包

前言:还是一篇入门文章。Javascript中有几个非常重要的语言特性——对象、原型继承、闭包。其中闭包对于那些使用传统静态语言C/C++的程序员来说是一个新的语言特性。本文将以例子入手来介绍Javascript闭包的语言特性,并结合一点ECMAScript语言规范来使读者可以更深入的理解闭包。

注:本文是入门文章,例子素材整理于网络,如果你是高手,欢迎针对文章提出技术性建议和意见。本文讨论的是Javascript,不想做语言对比,如果您对Javascript天生不适,请自行绕道。

什么是闭包

闭包是什么?闭包是Closure,这是静态语言所不具有的一个新特性。但是闭包也不是什么复杂到不可理解的东西,简而言之,闭包就是:

• 闭包就是函数的局部变量集合,只是这些局部变量在函数返回后会继续存在。

• 闭包就是就是函数的“堆栈”在函数返回后并不释放,我们也可以理解为这些函数堆栈并不在栈上分配而是在堆上分配

• 当在一个函数内定义另外一个函数就会产生闭包

上面的第二定义是第一个补充说明,抽取第一个定义的主谓宾——闭包是函数的‘局部变量’集合。只是这个局部变量是可以在函数返回后被访问。(这个不是官方定义,但是这个定义应该更有利于你理解闭包)

做为局部变量都可以被函数内的代码访问,这个和静态语言是没有差别。闭包的差别在于局部变变量可以在函数执行结束后仍然被函数外的代码访问。这意味着函数必须返回一个指向闭包的“引用”,或将这个”引用”赋值给某个外部变量,才能保证闭包中局部变量被外部代码访问。当然包含这个引用的实体应该是一个对象,因为在Javascript中除了基本类型剩下的就都是对象了。可惜的是,ECMAScript并没有提供相关的成员和方法来访问闭包中的局部变量。但是在ECMAScript中,函数对象中定义的内部函数() inner function是可以直接访问外部函数的局部变量,通过这种机制,我们就可以以如下的方式完成对闭包的访问了。

function greeting(name) {
    var text = 'Hello ' + name; // local variable
    return function() { alert(text); }  // 每次调用时,产生闭包,并返回内部函数对象给调用者
}
var sayHello=greeting("Closure");
sayHello()  // 通过闭包访问到了局部变量text

上述代码的执行结果是:Hello Closure,因为sayHello()函数在greeting函数执行完毕后,仍然可以访问到了定义在其之内的局部变量text。

好了,这个就是传说中闭包的效果,闭包在Javascript中有多种应用场景和模式,比如Singleton,Power Constructor等这些Javascript模式都离不开对闭包的使用。

ECMAScript闭包模型

ECMAScript到底是如何实现闭包的呢?想深入了解的亲们可以获取ECMAScript 规范进行研究,我这里也只做一个简单的讲解,内容也是来自于网络。

在ECMAscript的脚本的函数运行时,每个函数关联都有一个执行上下文场景(Execution Context) ,这个执行上下文场景中包含三个部分

  • 文法环境(The LexicalEnvironment)
  • 变量环境(The VariableEnvironment)
  • this绑定

其中第三点this绑定与闭包无关,不在本文中讨论。文法环境中用于解析函数执行过程使用到的变量标识符。我们可以将文法环境想象成一个对象,该对象包含了两个重要组件,环境记录(Enviroment Recode),和外部引用(指针)。环境记录包含包含了函数内部声明的局部变量和参数变量,外部引用指向了外部函数对象的上下文执行场景。全局的上下文场景中此引用值为NULL。这样的数据结构就构成了一个单向的链表,每个引用都指向外层的上下文场景。

例如上面我们例子的闭包模型应该是这样,sayHello函数在最下层,上层是函数greeting,最外层是全局场景。如下图:

因此当sayHello被调用的时候,sayHello会通过上下文场景找到局部变量text的值,因此在屏幕的对话框中显示出”Hello Closure”

变量环境(The VariableEnvironment)和文法环境的作用基本相似,具体的区别请参看ECMAScript的规范文档。

闭包的样列

前面的我大致了解了Javascript闭包是什么,闭包在Javascript是怎么实现的。下面我们通过针对一些例子来帮助大家更加深入的理解闭包,下面共有5个样例,例子来自于JavaScript Closures For Dummies(镜像)

例子1:闭包中局部变量是引用而非拷贝

function say667() {
    // Local variable that ends up within closure
    var num = 666;
    var sayAlert = function() { alert(num); }
    num++;
    return sayAlert;
}
var sayAlert = say667();
sayAlert()

因此执行结果应该弹出的667而非666。

例子2:多个函数绑定同一个闭包,因为他们定义在同一个函数内。

function setupSomeGlobals() {
    // Local variable that ends up within closure
    var num = 666;
    // Store some references to functions as global variables
    gAlertNumber = function() { alert(num); }
    gIncreaseNumber = function() { num++; }
    gSetNumber = function(x) { num = x; }
}
setupSomeGolbals(); // 为三个全局变量赋值
gAlertNumber(); //666
gIncreaseNumber();
gAlertNumber(); // 667
gSetNumber(12);//
gAlertNumber();//12

例子3:当在一个循环中赋值函数时,这些函数将绑定同样的闭包

function buildList(list) {
    var result = [];
    for (var i = 0; i < list.length; i++) {
        var item = 'item' + list[i];
        result.push( function() {alert(item + ' ' + list[i])} );
    }
    return result;
}
function testList() {
    var fnlist = buildList([1,2,3]);
    // using j only to help prevent confusion - could use i
    for (var j = 0; j < fnlist.length; j++) {
        fnlist[j]();
    }
}

testList的执行结果是弹出item3 undefined窗口三次,因为这三个函数绑定了同一个闭包,而且item的值为最后计算的结果,但是当i跳出循环时i值为4,所以list[4]的结果为undefined.

例子4:外部函数所有局部变量都在闭包内,即使这个变量声明在内部函数定义之后。

function sayAlice() {
    var sayAlert = function() { alert(alice); }
    // Local variable that ends up within closure
    var alice = 'Hello Alice';
    return sayAlert;
}
var helloAlice=sayAlice();
helloAlice();

执行结果是弹出”Hello Alice”的窗口。即使局部变量声明在函数sayAlert之后,局部变量仍然可以被访问到。

例子5:每次函数调用的时候创建一个新的闭包

function newClosure(someNum, someRef) {
    // Local variables that end up within closure
    var num = someNum;
    var anArray = [1,2,3];
    var ref = someRef;
    return function(x) {
        num += x;
        anArray.push(num);
        alert('num: ' + num +
        '\\nanArray ' + anArray.toString() +
        '\\nref.someVar ' + ref.someVar);
    }
}
closure1=newClosure(40,{someVar:'closure 1'});
closure2=newClosure(1000,{someVar:'closure 2'});
closure1(5); // num:45 anArray[1,2,3,45] ref:'someVar closure1'
closure2(-10);// num:990 anArray[1,2,3,990] ref:'someVar closure2'

闭包的应用

Singleton 单件:

var singleton = function () {
    var privateVariable;
    function privateFunction(x) {
        ...privateVariable...
    }
    return {
        firstMethod: function (a, b) {
            ...privateVariable...
        },
        secondMethod: function (c) {
            ...privateFunction()...
        }
    };
}();

这个单件通过闭包来实现。通过闭包完成了私有的成员和方法的封装。匿名主函数返回一个对象。对象包含了两个方法,方法1可以方法私有变量,方法2访问内部私有函数。需要注意的地方是匿名主函数结束的地方的’()’,如果没有这个’()’就不能产生单件。因为匿名函数只能返回了唯一的对象,而且不能被其他地方调用。这个就是利用闭包产生单件的方法。

参考:

JavaScript Closures For Dummies(镜像) 可惜都被墙了。

Advance Javascript (Douglas Crockford 大神的视频,一定要看啊)

原文地址:理解Javascript的闭包

另外一篇 Javascript中Closure及其相关概念  感觉也不错。

关于js闭包的问题 自己还是需要好好地研究研究

原文地址:JS实现拖动div层移动

JS实现拖动div层移动

      在谈到拖动div层之前,我们有必要来了解下 下面JS几个属性的区别—-  pageX,pageY,layerX,layerY,clientX,clientY,screenX,screenY,offsetX之间的区别!

     PageX: 鼠标在页面上的位置,从页面左上角开始,即是以页面为参考点,不随滑动条移动而变化.(只有firefox等标准游览器特有,IE没有)。
clientX: 鼠标在页面上可视区域的位置,从浏览器可视区域左上角开始,即是以浏览器滑动条此刻的滑动到的位置为参考点,随滑动条移动 而变化.

这两个最主要的区别是 在有滚动条的情况下,pageX是不随滚动条变化而变化,clientx是在可视区域内的距离,不包括滚动条的距离。

  screenX: 鼠标在屏幕上的位置,从屏幕左上角开始,这个没有任何争议.

     offsetX和layerX

      offsetX   IE特有,鼠标相比较于触发事件的元素的位置,以元素盒子模型的内容区域的左上角为参考点,如果有boder,可能出现负值。

      layerX:   firefox特有,鼠标相比较于当前坐标系的位置,即如果触发元素没有设置绝对定位或相对定位,以页面为参考点,如果有,将改变参考坐标系,从触发元素盒子模型的border区域的左上角为参考点 也就是当触发元素设置了相对或者绝对定位后,layerX和offsetX就幸福地生活在一起^-^,几乎相等,唯一不同就是一个从border为参考点,一个以内容为参考点,FF从border开始.

    pageX,pageY只有firefox特有,IE没有,所以要针对游览器兼容性写个函数,Jquery源码中 这样写的,

    所以我们也可以针对写个公用的函数,代码如下:

function pageXY(e) {
    var event = e || window.event;

    var doc = document.documentElement,
          body = document.body;

    // IE
    if (event.pageX == null && event.clientX !=  null ) {
        var doc = document.documentElement,
            body = document.body;
            event.pageX = event.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );

            event.pageY = event.clientY + ( doc && doc.scrollTop  || body && body.scrollTop  || 0 ) - ( doc && doc.clientTop  || body && body.clientTop  || 0 );

            return {
                x : event.pageX,
                y : event.pageY
            }
        }

        // firefox
        return {
            x : event.pageX,
            y : event.pageY
        }
    }

offsetX 是IE特有的 layerX是firefox特有的,所以针对这两个也可以写个公用的函数 代码如下:

 

function offsetXY(e) {
    var event = e || window.event;
    return {
         x:event.offsetX || event.layerX,
                 y:event.offsetY || event.layerY
    }
}

 

JSFiddle链接代码如下:

 想看div层拖动的话 请点击我!

拖动层的基本原理是:

首先先来理解下 我们要在页面上拖动某一块 到 页面上的另外一个位置 那么肯定这块元素是绝对定位的 并且 我们移动它时 是不断的改变他们的top值和left值!再者 我们拖动它时候肯定要触发事件!有 onmousedown事件!

  那么我们现在是要计算的是 我们这个元素被拖动到页面上的某个位置时的 左上标的位置X和Y。

      如下图所示:

      

 

要计算元素的左上的x和y坐标 如上图所示:就是指x = clientX-offsetX + “px”; y= clientY-offsetY + “px”;

HTML和CSS代码如下:

 

<div id="father" style="border:0px solid red;width:200px;">
    <div id="a" style="background:red;width:100px;height:100px">长,宽都是100px</div>
    <div id="b" style="border-top:0px solid red;background:yellow;width:100px;height:100px;margin-left:100px;"></div>
    </div>
<style>
    #oDiv{ width:200px; height:200px; color:#fff;background:#00C; position:absolute; top:200px; left:200px; z-index:100;overflow:hidden;}
 </style>

 

JS所有代码如下:

function pageXY(e) {
    var event = e || window.event;

    var doc = document.documentElement,
        body = document.body;

    // IE
    if (event.pageX == null && event.clientX !=  null ) {
        var doc = document.documentElement,
            body = document.body;

            event.pageX = event.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );

            event.pageY = event.clientY + ( doc && doc.scrollTop  || body && body.scrollTop  || 0 ) - ( doc && doc.clientTop  || body && body.clientTop  || 0 );

            return {
                x : event.pageX,
                y : event.pageY
            }
        }

        // firefox
        return {
            x : event.pageX,
            y : event.pageY
        }
    }

    function offsetXY(e) {
        var event = e || window.event;
        return {
             x:event.offsetX || event.layerX,
                         y:event.offsetY || event.layerY
        }
    }
    var $=function(id){
        return ("string"==typeof id) ? document.getElementById(id):id;
    };
    $('a').onmousemove = function(e){
        text(e);
    }
    $('b').onmousemove = function(e){
        text(e);
    }
    function text(e) {
        e = e || window.event;
        var offset = offsetXY(e),
            page = pageXY(e);

        var doc = document.documentElement,
            body = document.body;
                $("pageX").innerHTML= page.x;
        $("pageY").innerHTML= page.y;
        $("clientX").innerHTML=e.clientX;
        $("clientY").innerHTML=e.clientY;
        $("screenX").innerHTML=e.screenY;
        $("screenY").innerHTML=e.screenY;
        $("scrollTop").innerHTML=doc && doc.scrollTop;
        $("scrollLeft").innerHTML=doc && doc.scrollLeft;
        $("offsetX").innerHTML = offset.x;
        $("offsetY").innerHTML = offset.y;
    }

    window.onload = function () {
            var oDiv = document.getElementById("oDiv");//oDiv必须使用CSS定位
            oDiv.onmousedown = drag;
            function drag(evt) {
                evt = evt || window.event;
                this.onmouseup = drop;
                this.onmousemove = moveDiv;
                this.offset = {
                    x:evt.offsetX || evt.layerX, //layerX 和layerY是w3c标准的 offsetX 和 offsetY是IE标准的
                    y:evt.offsetY || evt.layerY
                };
            }
            function moveDiv(evt) {
                evt = evt || window.event;
                this.style.left = evt.clientX-this.offset.x+"px";
                this.style.top = evt.clientY-this.offset.y+"px";
            }
            function drop(evt) {
                this.onmouseup = null;
                this.onmousemove = null;
            }
        };

原文地址:JS实现拖动div层移动

另一个使用JQUERY的文章为:Jquery 实现层的拖动,支持回调函数

还有一个 “自己写了一个无缝滚动的插件(jQuery)”也很有意思

JSON的标准格式如下:
1.所有的键必须用双引号包裹
2.JSON不支持注释
3.JSON的值不得为函数不得未定义。值可以是用双引号包裹的字符串,或是数字,或true、false,或null,或一个对象、数组。允许嵌套结构。
4.JSON的最后一个值不使用逗号(,)分隔符

更相信的请点这里