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 -