浅谈 HTML5 的 Drag and Drop

前言

这里引用 PPK 大神一篇文章 The HTML5 drag and drop disaster 里面的一段话作为开始:

After spending about a day and a half in testing I am forced to conclude that the HTML5 drag and drop module is not just a disaster, it’s a fucking disaster.

可想而知,这货是有多么坑爹啦?不过 PPK 的文章我确实也没仔细看,只是在翻阅资料的时候,偶尔看见了,然后第一句话就让我感同身受,好吧,那接下来就与 DND 战斗吧!

需求

大致说一下需求,这里需要完成一个拖拽上传的功能,相信这也是老生常谈了,2、3年前 Gmail 便实现了,国内各网站也有了不同程度的支持,点点网 一贯以追求良好的用户体验为楷模,所以今年年初他们也推出了这个功能。但是总体来说,通过细致的体验后发现,细节方面做得还是不够完善。

做完整个功能之后,我总结一下我所希望考虑到的细节吧:

  1. 有东西拖动进入页面,需要有反馈,知道这是一个拖动;
  2. 设定一个允许 drop 的区域,文件拖入此区域才算是一个正确的用户行为,并有相关反馈提示此处可以 drop,其他区域无法 drop,如果用户提前释放鼠标,则会有相关的 revert 的动画出现(貌似这个就 Mac 下会有,Windows 一切尽在不言中);
  3. drop 成功后,前端拿到适合的图片文件,直接通过 dataURI 进行展示,然后通过兼容之前 form 表单提交接口,后台完成图片数据的后端提交,基本做到平滑、用户无感;
  4. 考虑到浏览器其他插件会也会充分运用到 Drag and Drop 的因素,尽量做到不影响其他脚本功能。

大致就那么多吧,前三点是开始完成需求时就一直要求完成的,也是其中最纠结的地方,充分让我体会到了 DND 事件和API是如何的 sucks!!!
阅读全文…

初识 SeaJS

最近NodeJSCommonJS真是好火啊,前端真的不再如从前那么“单纯”了,从此需要学习掌握更多的传统软件开发的知识和技能,也就是从前后端开发做的很多工作会逐渐前移到前端工程师。这里为什么要把NodeJSCommonJS放在一起说呢,主要是我对里面的模块机制十分感兴趣,正好NodeJS中的模块实现方式也遵循CommonJS中的相关规范。

神奇的 require

首先还是看一段代码吧:

var http = require('http');
http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('Hello World\n');
}).listen(1337, "127.0.0.1");
console.log('Server running at http://127.0.0.1:1337/');

这是NodeJS首页上的一段代码,我们注意看一下require这个函数的用法,这里的代码需要http这个模块,因此就var http = require('http');但是令我比较疑惑的就是明明require函数是个异步的调用,因为http的模块文件不可能同步在这里堵塞加载执行(当然可能有人会说使用同步的AJAX请求,但是我们同样需要支持跨域问题),虽然这段代码是NodeJS的代码,其主要是运行在服务器端,使用V8引擎驱动,可以使用一些手段让requrie的模块同步加载执行,但是对于一个Web应用看到这样的代码一定会疑惑为什么这里不需要一个回调函数~好吧,就此认为这就是NodeJS在服务器端的神奇之处~~~那么继续看下面一段代码:

define(function(require, exports, module) {

    // 获取依赖的模块:
    var $ = require('jquery');

    // 向外提供接口:
    exports.someMethod = someFunction;

});

这段代码是来自SeaJS首页的一段代码(额,我们的主角终于现身了~)这里再次出现了var $ = require('jquery');这样一段 require 函数,这里同样会有刚刚上述的问题,明明是个异步的需求怎么在这里可以使用同步的写法呢?一种假设是 require 函数在这里已经事先准备好了 jquery 的对象(加载和执行 jquery.js ),但是这有什么策略么?不可能所有支持的模块都必须这样提前加载,况且以后根本无法方便扩展,添加自己的模块啊;第二种假设就是 jquery 对象真是在这里第一次出现,然后下载文件,执行文件的~但是据我所知,只能使用同步的 AJAX 才能做到这样,但是跨域的问题是最致命的问题,这里不可能没有处理啊??!!纠结啊,甚是纠结啊~一个require竟然有如此神奇的功力~

拨开 require 的神秘外衣

介于NodeJS的代码性质以及难度,这里暂时没有去研究其 require 的实现方法,限制着重谈谈SeaJS。关于SeaJS是什么,建议首先访问其网站,大致了解一下,在其中自然也提到了不少CommonJS的规范问题。

为了更好地探究这个神奇的require函数,我首先从github上拉到了最新的SeaJS源代码。大致看下 seajs 代码执行后会产生些什么,如下图:

SeaJS Overview

是的,没看错,seajs 这个对象暴露出来的接口就这么简单几个,但是不能仅仅因为暴露的接口简单精巧就忽视了其丰富的内涵。我们打开其源代码,大致看一下整个项目结构,这里主要把目光集中在 src 目录下:

SeaJS Source Structure

文件命名主要是以 sea 开头的总引导文件以及 api 文件,fn 开头的辅助函数文件,util 开头的工具文件。通过 build.xml 可以了解整个SeaJS发布文件的构成方式。打开 sea.js 文件,可以看到刚刚我提到的辅助函数、工具在项目整个解构中都是以私有方式保存在暴露的 seajs 对象中,但是为什么实际看不到这些私有属性呢,愿意可以在 sea-api.js 中发现,具体看代码吧,这里不细说了~总的来说,整个 seajs 充分运用闭包的优势,把私有变量均保存在闭包中方便使用,在最终的 api 接口中删除暴露的私有属性接口,但是由于闭包特性,其内部值仍得以保留,这种方式个人感觉十分新颖,尤其实在做这样一个多文件项目时,在最终的 api 发布阶段再清除不必要的私有接口,既保证了开发时的灵活方便,又保证了数据的安全性。

好像越说离题越远,那现在回到神秘的 require 函数,那么这个接口是暴露的哪个内部辅助函数呢?看名字以为是在 fn-require.js 中,实则不然,我们再把眼光看远一些,原来 require 函数的最外层是一个 define 函数,那么打开 fn-define.js,代码看得云里来雾里去的,但是明确一下现在的目标时找到 require 相关的实现,但是首先,再岔开一下话题,看下 CommonJS 里面的这段规范说明 Modules/Wrappings,哈哈,说到的问题是不是正中下怀啊,正是我们现在关注的方向,继续看代码,注意到这样一个函数:

deps = parseDependencies(factory.toString());

/* ------------------------------------------ */

function parseDependencies(code) {
    var pattern = /\brequire\s*\(\s*['"]?([^'")]*)/g;
    var ret = [], match;

    while ((match = pattern.exec(code))) {
        if (match[1]) {
          ret.push(match[1]);
        }
    }

    return ret;
}

哈,原来如此!define 函数中的倚赖关系(实际就是 require 函数的作用),是通过硬解析函数代码,观察其中 require 使用情况来决定的啊~虽然没有想象中的那么神奇,但是也是一种解决方案嘛,说到这里看看上马的假设,更符合假设一的情况,只是这里自然用不着全部加载其他模块,通过代码的“硬解析”来完成依赖需求,在 define 阶段即可有机会首先异步加载 require 的模块,然后当真正 require 时自然也就是同步方式了,这招还真是挺绝妙的啊~

好了,这次已经大致了解了SeaJS的一些基本情况了,也终于解惑了一个一直困扰我的问题,至于源代码的进一步分析和解读就等下次吧~

CSS中的伪类与伪元素

起因

今天在了解CSS优先级与继承相关概念的时候,接触到了这两个东东。之前看见类似:after的东东时,均会认为是伪类(Pseudo-classes),但是实际不然,在CSS2规范中对伪类(Pseudo-classes)以及伪元素(Pseudo-elements)实际做了不同的解释。

释疑

对于这种很纠结的问题,其实最好的答案就在规范里面,立马Google出CSS2规范,里面果真具体定义了,请猛击这里查看。不过规范毕竟是规范,看了不让人晕就不叫规范了=,=

我仔细看了一遍又一遍还是不怎么能够解释得清他们直接的区别,so木有办法,只好硬记下了:first-line, :first-letter, :before, :after这几个就是伪元素,其余都为伪类~希望有DX可以帮忙具体总结总结两者差异吧,或者有时间再好好研读研读规范……

不过这里不得不赞一下CSS3的标准草案,里面终于把这两个概念从代码上做了区分,对于伪类的代码不变,但是对于伪元素,则使用两个::作为前缀,因此这样就很容易区分了,可以猛击这里查看

为何这里如此纠结两者的区别呢?首先第一点是在计算一个选择器的优先级时,伪类和伪元素的权重是不同的,因此在比较细节的地方需要纠结;其次是伪类允许在选择器的任何位置,但是伪元素只允许出现在最后一个简单选择器的末尾(Pseudo-classes are allowed anywhere in selectors while pseudo-elements may only be appended after the last simple selector of the selector),在CSS3的草案中还提到,每个选择器中只允许出现一个伪元素,未来版本可能会允许多个(Only one pseudo-element may appear per selector, and if present it must appear after the sequence of simple selectors that represents the subjects of the selector. Note: A future version of this specification may allow multiple pseudo-elements per selector)

感受

确实,前端这潭水真是太深太深了,任何一个细小的地方都可以引发很多讨论。这时候规范确实是个好东东,虽然那玩意儿确实不是给普通人读的,只是遇到纠结问题去查阅查阅确实会很有收获滴~努力学习吧~

- EOF -

深入理解JavaScript闭包(2)

继续上次闭包的经典文章Javascript Closures的研读。这次学到了“作用域链与函数内部[[scope]]”一节。继续以原文翻译+自己理解的方式呈现文章内容。

作用域链与函数内部[[scope]]

正如上节提到的那样,一个函数调用会创建一个执行上下文,而这个执行上下文中包含一个作用域链。在这个作用域链的最开始部分是执行上下文的Activation/Variable对象,后面紧接着就是函数对象自身的[[scope]]属性,因此很有必要去了解函数内部的这个[[scope]]属性如何定义的。

在ECMAScript规范中,函数实际也是对象。这些函数对象要么是“函数申明(function declarations)”方式的函数——在变量实例化阶段创建的,要么是“函数表达式(function expressions)”——在执行阶段创建的,要么是“函数构造器(Function constructor)”——在调用实例化时创建。

这里比较特殊需要注意的是,使用“函数构造器(Function constructor)”创建的函数对象的[[scope]]属性只包含全局对象。

而使用“函数申明(function declarations)”或者“函数表达式(function expressions)”创建的函数对象,其[[scope]]属性包含了创建该函数的那个父函数(或者全局对象)的执行上下文(译者注:可能这里有些绕,不大容易理解,不过下面会有详细解释,这里大概有这样一种概念就好)。

例如最简单的全局函数声明:

function exampleFunction(formalParameter){
    // function body code
}

在全局执行上下文的变量实例化阶段会创建该函数对应的函数对象。全局执行上下文中有一个只包含一个全局对象的作用域链。因此创建出来的函数对象会以“exampleFunction”的属性名添加到全局对象中,并且为这个函数内部的[[scope]]属性指向只包含全局对象的作用域链(译者注:也就是当前的全局执行上下文)。

类似地,在全局中使用函数表达式声明函数:uoy

var exampleFunction = function(formalParameter){
    // function body code
}

与上例有一点儿不同的是,具名属性“exampleFunction”在全局执行上下文的变量实例化阶段创建,但是其并不指向任何函数对象,因为此时还为创建函数对象(译者注:通常这是由于JS解析引擎将var声明的变量语句提前执行,导致了变量声明和赋值是两个阶段完成)。直到代码执行到真正的函数表达式赋值语句时,才会创建这个函数对象,并让具名属性“exampleFunction”指向它。尽管函数对象创建的时间“较晚”,但其创建依然实在全局之下上下文中完成,因此其[[scope]]属性依然指向当前全局执行上下文,其中只包含一个全局对象(译者注:这一关键点是与上例相同的,因此他们的作用结果就是[[scope]]属性实际指向相同,因此也就能解释两种函数声明方式的类似,其实当了解了这些原理之后再来看JavaScript的各种函数定义,获取会有更深地理解)。

那么对于在函数内部的函数声明或者函数表达式,它们的函数对象创建是在外部函数的执行上下文中完成,因此将会获得更“丰富(elaborate)”的作用域链。考虑如下代码,我们在一个函数内部再声明了一个函数,然后执行外部这个函数:

function exampleOuterFunction(formalParameter) {
    function exampleInnerFuncitonDec() {
        // inner function body
    }
    // the rest of the outer function body.
}

exampleOuterFunction(5);

正如上面说到的那样,外部函数声明所对应的函数对象是在全局执行上下文的变量实例化阶段创建的,因此它的[[scope]]属性包含只有唯一对象(全局对象)的作用域链。

当全局代码执行到exampleOuterFunction的函数调用时,自然会为这次调用创建一个新的执行上下文(译者注:我们把它称作执行上下文A),同时包含一个Activation/Variable对象在其中。新创建的执行上下文A的作用域链包含新创建的Activation对象,之后紧接着外层函数(即exampleOuterFunction)对象的[[scope]]属性(只包含一个全局对象)。新执行上下文A的变量实例化阶段,将会为内层函数定义创建与之对应的函数对象,当然也会为这个函数对象赋值[[scope]]属性,其指向创建这个函数对象的那个执行上下文A(译者注:需要特别注意这里的逻辑关联),因此这个内部函数的[[scope]]属性是一个“包含Activation对象,之后紧接着全局对象”的作用域链。

至此以上所有都是自动地、受结构控制地执行程序源代码。执行上下文的作用域链决定了其中创建的函数对象的[[scope]]属性,同时函数对象的[[scope]]属性决定了该函数被调用时的执行上下文(自然还包括执行上下文中与之对应的Activation对象)。但是ECMAScript提供的with语句却是可以修改作用域链的一种方法。

with语句计算一个表达式,如果表达式是一个对象,那么就将这个对象添加到当前执行上下文的最前端(在Activation/Variable对象之前)。接下来with语句执行剩余的语句(当然也可能出现其中又包含一个自己的语句块),之后还原执行上下文的作用域链回到之前的状态。

函数声明是不会受到with语句的影响,因为它们的函数对象实在变量实例化阶段创建的,因此此时没有with语句的影响。但是对于函数表达式就不同了,它们可能在一个with语句块中执行:

/* create a global variable - y - that refers to an object:- */
var y = { x: 5 }; // object literal with an - x - property
function exampleFuncWith() {
    var z;
    /* Add the object referred to by the global variable - y - to the
       front of he scope chain:-
    */
    with(y){
        /* evaluate a function expression to create a function object
           and assign a reference to that function object to the local
           variable - z - :-
        */
        z = function() {
            // inner function expression body;
        }
    }
}

/* execute the - exampleFuncWith - function:- */
exampleFuncWith();

exampleFuncWith函数被调用的时候自然会产生一个新的执行上下文,其中的作用域链包含Activation对象之后紧接着全局对象。当执行到with语句的时候,会将全局变量y添加到作用域最前,这时候恰好碰到一个函数表达式的执行。函数表达式执行产生的函数对象其中的[[scope]]属性被赋值为当前创建其的执行上下文,那么显而易见,由于刚刚所说的,现在的执行上下文中包含y,且其位置还要在Activation对象之前,此时作用域链的排列是这样的:y –> Activation –> global object,因此这个函数表达式就受到with语句的影响。

with语句相关的语句块都执行完毕之后,当前的执行上下文就会被还原(y对象被移除),但是当时(即在with语句块中时)创建的函数对象的[[scope]]属性指向了包含y对在最前的作用域链。

最后依然是用一张图做总结,下图是对例3的形象描述:

scope_chain

从图中很容易发现,如果在不存在with语句时,函数对象的[[scope]]属性与外层函数(或者全局对象)的执行上下文有着密切的联系,这种联系也就形成了作用域链,建立了内外函数的沟通桥梁,这样也便很容易理解为什么内层函数可以访问外层函数局部变量,反之则不行。

- EOF -

深入理解JavaScript闭包(1)

这边文章主要是Javascript Closures这篇文章的学习笔记,可能包含一定的原文翻译,以及一些自己的理解,希望通过这些学习能够更加深入地理解JavaScript中闭包的概念。

下面是The Execution Context一节的翻译与理解。

执行上下文 (The Execution Context)

执行上下文是ECMAScript规范(ECMA 262 3rd edition)中定义的有关ECMAScript实现的相关行为要求。虽然规范没有具体规定执行上下文的实现,但是根据规范中对其所定义的数据结构(包含一些列相关是属性),因此执行上下文可以理解为(甚至按照)对象的方式来实现,尽管其包含的属性不是公共的。

所有的JavaScript代码都是在执行上下文中所执行的。全局代码(包含已执行的行内代码,JS文件,或者内潜入HTML的JS代码)都是在全局执行上下文中执行的,并且对于每一个函数(包含构造函数)都有一个与之相关联的执行上下文。使用eval函数执行的代码也是包含一个独特的执行上下文,但是由于其在JavaScript中的特殊性,这里就不再多考虑了。有关执行上下文的具体细则规范可以在ECMA 262 (3rd edition) 10.2节中找到。

当一个JavaScript函数被调用的时候,它就进入到一个执行上下文中。如果其中又有其他函数被调用(或者自身的递归)都会创建一个新的执行上下文,然后函数调用进入到那个执行上下文中,直到那个函数执行结束(译者注:这不就类似盗梦空间中的进入一层层梦境,直到该层梦境的时间结束,由此看来盗梦空间真是值得广大程序员好好研究的佳片啊=,=||)。因此,由于这种现象就在代码执行中形成了一个执行上下文堆栈。

当一个执行上下文被创建的时候会依次经历以下几个阶段。首先,在函数的执行上下文中创建一个Activation(激活)对象(貌似犀牛书中就是这样翻译的~)。Activation对象实际是规范中又一机制。由于它拥有一些可访问的具名属性,因此可以将它作为一个对象来看待,但比较特殊的是,它没有原型对象(至少没有一个定义的原型对象),同时Activation对象也不能在代码中直接引用。

Activation对象创建完毕后,下一步就是为函数调用创建arguments对象,众所周知,arguments对象是一个类数组的对象,使用整数对其成员索引,且其排列顺序与函数调用传递的参数顺序一致。同时arguments对象还拥有lengthcallee两个成员(译者注:实际还应该有一个caller成员,不过这个成员已经建议弃用了),不过这里与主题无关就不做详细讨论了。这时候会在Activation对象上创建一个名为"arguments"的属性,且指向arguments对象。

接下来,执行上下文需要给作用域赋值。这里的作用域就是包含一些列的对象,也就是我们常说的作用域链,注意对“链”的理解。每一个函数对象在内部都拥有一个[[scope]]的属性(稍候会详细介绍),它也包含一个链式的对象。那么执行上下文的作用域主要包括Activation对象,且其处于作用域链的最顶端,之后就是函数对象对应的[[scope]]对象。

下一步称为变量实例化(variable instantiation),这时候使用到一个对象,在ECMA 262中称为Variable对象。然后Activation对象实际就是当作Variable对象使用(这里需要注意的是,其实两者指的是同一个对象)。在这个阶段里,会为函数的每一个形参在Variable对象上创建一个具名属性,如果函数调用传入的参数与形参一致的话,那么就会将参数的值逐一赋值给Variable对象上的那些属性(否则的话赋值为undefined)。之后对于调用函数的内部函数声明,同样首先会给这些函数创建函数对象,之后以函数名称为Variable对象的属性名,然后添加对应的函数对象引用。变量实例化的最后一个阶段就是处理调用函数的具备变量。同样的,将所有调用函数内的具备变量声明添加到Variable对象中。(译者注:此时的Variable对象就完整地包含了形参,内嵌函数以及局部变量)

这里需要注意的是在变量实例化阶段中,为Variable对象添加局部变量时,所赋值的初值均为undefined(译者注:因为我们知道JS引擎会将以var声明的具备变量提前解析,这就是刚刚所提到的变量实例化阶段所做的事儿,因此提前之后它们的初值都是undefined),而真正的局部变量实例化(赋值)直到在执行代码体的表达式时才会完成。

事实上,带有arguments的Activation对象与带有函数局部变量的Variable对象就是同一个对象,因此我们也可以把arguments标识符当作一个函数局部对象。

最后一个步骤就是对this关键字赋值。如果this关键字的值为一个对象的话,那么以this关键字为前缀的属性访问均是指向那个对象;但如果this关键字赋值为null的话,那么this关键字就指向全局对象。

对于全局执行上下文来说,处理的方式会有些许差异,这主要是因为其没有参数,因此就不需要定义Activation对象。同样的,全局执行上下文需要作用域,且它的作用域只包含一个对象——全局对象;全局执行上下文也需要经历变量实例化,它内部的函数自然就成为了最顶层的函数声明,包含了大量JavaScript代码;全局对象被当作Variable对象使用,因此其中的变量声明、函数声明自然就成为了全局对象的成员(译者注:这也就是为什么我们建议在全局代码中用一个匿名函数包裹内部代码,防止全局对象的污染)。

同时,全局执行上下文中的this对象指向的是全局对象。

最后以一张图片来结尾。

execution_context

- EOF -

Page 1 Of 41234