变量对象

一、概要

在构建系统过程中,我们会在程序中定义一些变量和函数。然而对于解释器来说,它是如何获取这些数据的?当引用一个对象的时候,在解释器内部又发生什么?
许多ECMAScript程序都知道,变量和执行上下文是密切相关的:

    var a = 10;//全局上下文中的变量
    (function(){
        var b = 20;//函数上下文中的局部变量
    })();

    alert(a);// 10
    alert(b);// b is not defined  

不仅如此,许多程序员也都知道,ECMAScript标准中指出独立的作用域只有通过”函数代码”才能创建。与C/C++不同的是,ECMAScript中的for循环代码块是无法创建本地上下文的:

    for(var k in {a:1,b:2}){
        alert(k);
    }

    alert(k);//尽管循环已经结束,但是变量"k"仍然在作用域中

二、数据声明

既然变量和执行上下文有关,那么它就该知道数据存储在哪里以及如何获取。这种机制就称作变量对象:
变量对象是一个特殊的对象,它与执行上下文息息相关。变量对象主要存储变量、函数声明、函数入参等
举个例子,可以用ECMAScript的对象来表示变量对象: VO = {};
VO同时也是一个执行上下文的属性:

    activeExecutionContext = {
        VO : {
            //上下文中的数据(变量声明、函数声明、函数形参)
        }
    }  

声明新的变量和函数的过程其实就是在VO中创建新的变量以及函数名对应的属性和属性值的过程。
如下所示:

    var a = 10;

    function test(x){
        var b = 20;
    }

    test(30);  

上述代码对应的变量对象:

      //全局上下文中的变量对象
      VO(globalContext) = {
          a : 10,
          test : 
      }  

      //test函数上下文中的变量对象
      VO(test functionContext) = {
          x : 30,
          b : 20
      };

但是,在实现层,变量对象只是一个抽象的概念。在实际执行上下文中,VO可能完全不叫VO,并且初始的结构也可能完全不同。

三、全局上下文的变量对象

全局对象是一个在进入任何执行上下文前就创建出来的对象,此对象以单例形式存在;它的属性在程序任何地方都可以访问,其声明周期随着程序的结束而终止。
全局对象在创建的时候,诸如Math、String、Date、parseInt等等属性也会被初始化,同时,其中一些对象会指向全局对象本身。比如,DOM中,全局对象上的window属性就是指向了全局对象。

 global = {
     Math:<...>,
     String:<...>
     ...
     ...
     window: global
 };  

在引用全局对象的属性时,前缀通常可以省略,因为全局对象是不能通过名字直接访问的。然而,通过全局对象上的this值,以及通过如DOM中的window对象这样递归引用的方式都可以访问到全局对象。

    String(10);//等同于 global.String(10);

    //带前缀
    window.a = 10;//global.window.a = 10 = global.a = 10;
    this.b = 20;//global.b = 20;

    var a = new String('test');

    alert(a);//直接访问,通过VO(globalContext)访问

    alert(window['a']);//间接访问
    alert(a === this.a);//true  

四、函数上下文中的变量对象

在函数的执行上下文中,VO是不能直接访问的。它主要扮演被称作活跃对象(activation object)的角色。
VO(functionContext) === AO;
活跃对象会在进入函数上下文的时候创建出来,初始化的时候会创建一个argument属性,其值就是Argument对象:
Argument对象是活跃对象上的属性,它包含了如下属性:

  • callee—对当前函数的引用
  • length—实参的个数
  • argument对象的properties-index的值与当前(实际传递的)形参是共享的

      function foo(x,y,z){
          //定义的函数的参数个数
          alert(foo.length);//3
    
          //实际传递参数的个数
          alert(argument.length);//2
    
          //引用函数自身
          alert(argument.callee === foo);//true
    
          //参数共享
          alert(x === argument[0]);true
          alert(x);//10
    
          argument[0] = 20;
          alert(x);// 20
    
          x = 30;
          alert(argument[0]);//30
    
          //然而对于没有传递的参数z
          //相关的argument对象的properties-index是不共享的
    
          z = 40;
          alert(argument[2]);//undefined
    
          argument[2] = 50;
          alert(z);//40
      }  
    

处理上下文代码的几个阶段:
1、进入执行上下文
一旦进入执行上下文(在执行代码之前),VO就会被一些属性填充:

  • 函数的形参—-变量对象的属性,其属性名就是形参的名字,其值就是实参的值,对于没有传递的参数,其值为undefined
  • 函数声明—-变量对象的属性,其属性名和值都是函数对象创建出来的,如果变量对象已经包含了相同名字的属性,则进行替换
  • 变量声明—-变量对象的一个属性,其属性名为变量名,其值为undefined,如果变量名和已声明的函数名或者函数参数名相同,则不会影响已存在的属性。

       function test(a,b){
           var c = 10;
           function d(){}
           var e = function _e(){};
           (function x(){});
       }
    
       test(10);//函数调用
    

    当以10为参数进入test函数上下文的时候,对应的AO如下所示:

        A0(test) = {
            a : 10,
            b : undefined,
            c : undefined,
            e : undefined,
            d : <reference to FunctionDeclaration "d">
        }  
    

    上面的AO中并不包含函数x,这是因为这里的x并不是函数声明,而是函数表达式,函数表达式不会对VO造成影响。

2、执行代码
此时,AO/VO的属性已经填充好了。(尽管大部分的属性都还没有赋予真正的值,都只是初始化时候的undefined值)
继续以上面的例子为例,到了执行代码阶段,AO/VO就会修改成如下形式:

    AO['c'] = 10;
    AO['e'] = <reference to FunctionExpress>  

以下是更加典型的例子:

    alert(x);//function

    var x = 10;
    alert(x);// 10

    x = 20;

    function x(){}

    alert(x);// 20  

正如此前提到的,变量声明是在函数声明和函数形参之后,并且变量声明不会对已存在的同名的函数声明和函数形参发生冲突,因此,在进入上下文阶段,VO填充如下形式:

    VO = {};

    VO['x'] = <reference to FunctionDeclaration "x">

    //发现var x = 10;
    //如果函数"x"还未定义
    //但是在我们的例子中变量声明并不会影响同名的函数值

    VO['x'] = <值不受影响,仍是函数引用>

关于变量

大多数讲Javascript的文章甚至Javascript的书通常都会这么说:声明全局变量的方式有两种,一种是使用var关键字,另外一种是不用var关键字,而这样的描述是错误。要记住的是:使用var关键字是声明变量的唯一方式。
如下赋值语句:a = 10;仅仅只是在全局对象上创建了新的属性(而不是变量)。
不同点如下:

    alert(a);//undefined
    alert(b);//"b" is not defined

    b = 10;
    var a = 20;  

接下来要谈到VO和不同阶段对VO的修改(进入上下文阶段和代码执行阶段):
进入上下文:

      VO = {
          a : undefined
      };

我们看到,这个阶段并没有任何”b”,因为它不是变量,b在执行阶段出现。
将上述代码稍作改动:

    alert(a);//undefined

    b = 10;
    alert(b);//10,代码执行阶段建立

    var a = 20;
    alert(a);//20,代码执行阶段赋值   

关于变量还有非常重要的一点是:与简单属性不同的是,变量是不能删除的,这意味着想要通过delete操作符来删除一个变量是不可能的。

    a = 10;
    alert(window.a);// 10
    alert(delete a);//true

    alert(window.a);//undefined

    var b = 20;
    alert(window.b);//20

    alert(delete b);//false

    alert(window.b);//仍然是20

但是,这里有一个例外,就是在eval执行上下文中,是可以删除变量的。

    eval('var a = 10');
    alert(window.a); // 10

    alert(delete a); // true

    alert(window.a); // undefined

Javascript函数

一、函数类型

ECMAScript中包含三类函数,每一类都有各自的特性
1、函数声明:

  • 拥有函数名
  • 代码位置:全局上下文或者另外一个函数体中
  • 创建时间:在进入上下文时创建
  • 会影响变量对象
  • 声明方式如下:

    function add(a,b){
       return a + b;    
     }
    

    这类函数的主要特性是:只有它们可以影响变量对象(存储在上下文VO中)。
    2、函数表达式:

  • 代码位置位于表达式的位置
  • 名字可有可无
  • 不会影响变量对象
  • 在代码执行阶段创建
    这类函数的主要特性是:代码总是处于表达式的位置。最简单的示例如下:

      var foo = function(a,b){
            return a + b;
      }   
    

    3、函数声明 VS 函数表达式
    当函数表达式有名字的时候,它很难和FD区分。不过如果仔细看这两者定义的话,要区分它们还是很容易的:
    函数表达式总是处于表达式的位置。

      //在括号(分组操作符)只可能是表达式  
      (function foo(){});  
    
      //数组初始化中,同样也只能是表达式  
      [function bar(){}];  
    
      //逗号操作符也只能是表达式  
      1,function baz(){}  
    

    定义中还提到函数表达式是在执行代码阶段创建,并且不是存储在变量对象上。

      //不论是在定义前还是定义后,函数表达式都访问不到  
      //(因为它在代码执行的阶段创建)  
    
      alert(foo);//"foo" is not defined   
    
      (function foo(){});  
    
      //后面也没有用,因为它根本不存在VO中  
    
      alert(foo);//"foo" is not defined    
    

    4、立即执行函数

        方式一:  
        (function (){})();  
        方式二:  
        (function (){}());  
    

    二、函数创建的算法

    如下所示使用伪代码表示函数创建的算法(不包含联合对象的步骤)。有助于理解ECMAScript中的函数对象,此算法对所有函数类型都是一样的。

        F = new NativeObject();  
    
        //属性[[Class]] is "Function"  
        F.[[Class]] = "Function"  
    
        //函数对象的原型  
        F.[[Prototype]] = Function.prototype  
    
        //对函数自身引用  
        //[[Call]]在函数调用F()激活  
        //同时创建一个新的执行上下文  
        F.[[Call]] = <reference to function>  
    
        //内置的构造器  
        //[[Construct]]会在使用"new"关键字的时候激活  
        //事实上,它会为新对象申请内存  
        //然后调用F.[[Call]]来初始化创建的对象,将this值设置为新创建的对象  
        F.[[Construct]] = internalContructor  
    
        //当前上下文(创建函数F的上下文)的作用域链  
        F.[[Scope]] = activeContext.Scope  
        //如果是通过new Function()方式来创建,则  
        F.[[Scope]] = globalContext.Scope  
    
        //形参个数  
        F.length = countParameters  
    
        //通过F创建出来的对象的原型  
        __objectPrototype = new Object();  
        __objectPrototype.constructor = F   
    
        F.prototype = __objectPrototype  
    
        return F  
    

    要注意的是,F.[[Prototype]]是函数(构造器)的原型,而F.prototype是通过该函数创建出来的对象的原型。在有些文章中会将F.prototype叫做”构造器的原型”,这是错误的。

javascript中类型检测

一、Javascript语言定义的5种原始类型及6种基本数据类型

1、javascript中定义的5种原始类型分别为:
null、undefined、number、string、boolean
2、javascript中定义的6种基本数据类型分别为:
null、undefined、number、string、boolean、object

二、检测原始值

如果你希望一个值是字符串、数字、布尔值或undefined,最佳选择是使用typeof运算符。typeof运算符会返回一个表示值的类型的字符串。

  • 对于字符串,typeof返回”string”
  • 对于数字,typeof返回”number”
  • 对于布尔值,typeof返回”boolean”
  • 对于undefined,typeof返回”undefined”

typeof的基本语法是:
typeof variable
你也可以这样使用typeof:
typeof(variable)
尽管这是合法的javascript语法,但这种方法让typeof看起来像一个函数而非运算符。鉴于此,我们推荐无括号的写法。
使用typeof来检测以下4种原始值类型是非常安全的做法。

//检测字符串
if(typeof name === "string"){
    anotherName = name.substring(3);
}

//检测数字
if(typeof count === "number"){
    updateCount(count);
}

//检测布尔值
if(typeof found === "boolean" && found){
    console.info("Found!");
}

//检测undefined
if(typeof MyApp === "undefined"){
    MyApp = {
        //相关代码
    };
}  

typeof运算符的独特之处在于,将其用于一个未声明的变量也不会报错。未定义的变量和值为undefined的变量通过typeof都将返回”undefined”。运行typeof null则返回”object”,这是一种低效的判断null的方法。如果需要检测null,则直接使用恒等运算(===)或非恒等运算(!==)。

三、检测引用值

引用值也称作对象(object)。在Javascript中除了原始值之外的值都是引用。有以下几种内置的引用类型:Object、Array、Date、Error等。使用typeof运算符在判断这些引用类型时则会显得力不从心,因为所有对象都会返回”object”。typeof另外一种不推荐的用法是当检测null的类型时,typeof运算符用于null时将会返回”object”。
检测某个引用值的类型的最好方法是使用instanceof运算符。
instanceof的基本语法是:
value instanceof constructor

//检测日期
if(value instanceof Date){
    console.log(value.getFullYear());
}

//检测正则表达式
if(value instanceof RegExp){
    if(value.test(anotherValue)){
        console.log("Matches");
    }
}  

//检测Error
if(value instanceof Error){
    throw value;
}  

instanceof的一个特性是它不仅检测构造这个对象的构造器,还检测原型链。原型链包含了很多信息,包括定义对象所采用的继承模式。比如,默认情况下,每个对象都继承自Object,因此每个对象的value instanceof Object都会返回true。比如:

var now = new Date();
console.log(now instanceof Object);//true
console.log(now instanceof Date);//true

因此使用instanceof来判断对象是否属于某个特定类型的做法并非最佳选择。
instanceof运算符也可以检测自定义类型,比如:

function Person(name){
    this.name = name;
} 
var me = new Person("sem");
console.log(me instanceof Object);//true
console.log(me instanceof Person);//true  

在Javascript中检测自定义类型时,最好的做法就是使用instanceof运算符,这也是唯一的办法。假设一个浏览器帧(frame A)里的一个对象被传入到另一个帧(frame B)中。两个帧都定义了构造函数Person。如果来自帧A的对象是帧A的Person的实例。则如下规则成立。

//true
frameAPersonInstance instanceof frameAPerson

//false
frameAPersonInstance instanceof frameBPerson  

因为每个帧(frame)都拥有Person的一份拷贝,它被认为是该帧(frame)中的Person的拷贝的实例。

四、检测函数

从技术上讲,Javascript中的函数是引用类型,同样存在Function构造函数,每个函数都是实例,比如:

function myFunc(){}
//不好的写法
console.log(myFunc instanceof Function);//true

然而,这个方法不能跨帧使用,每个帧都有各自的Function构造函数。好在typeof运算符可以用于函数,返回”function”。

function myFunc(){}
//好的写法
console.log(typeof myFunc === "function");//true  

检测函数最好的方法是使用typeof,因为它可以跨帧使用。用typeof来检测函数有一个限制。在IE8和更早版本的IE浏览器中,使用typeof来检测DOM节点(比如document.getElementById())中的函数都返回”object”而不是”function”。
之所以出现这种怪异的现象是因为浏览器对DOM的实现有差异。简而言之,这些早期版本的IE并没有将DOM实现为内置的Javascript方法,导致内置的typeof运算符将这些函数标识为对象。开发者往往通过in运算符来检测DOM的方法,比如:

//检测DOM方法
if("querySelectorAll" in document){
    images = document.querySelectorAll("img");
}

在其他所有的情形中,typeof运算符是检测javascript函数的最佳选择。

五、检测数组

关于如何在Javascript中检测数组类型已经有很多研究了,最终Kangax给出了一种优雅的解决方案。

function isArray(value){
    return Object.prototype.toString.call(value) === "[object Array]";
}

Kangax发现调用某个值的内置toString()方法在所有浏览器中都会返回标准的字符串结果。对于数组来说,返回的字符串为”[object Array]”,也不用考虑数组实例是在哪个帧中被构造出来。这种方法在识别内置对象时往往十分有用,但对于自定义对象请不要用这种方法。

六、检测属性

另外一种用到null(以及undefined)的场景是当检测一个属性是否在对象中存在时,比如:

//不好的写法:检测假值
if(object[propertyName]){
    //一些代码
}

//不好的写法:和null相比较
if(object[propertyName] != null){
    //一些代码
}

//不好的写法:和undefined比较
if(object[propertyName] != undefined){
    //一些代码
}

以上这段代码里的每个判断,实际上是通过给定的名字来检查属性的值,而非判断给定的名字所指的属性是否存在,因为当属性值为假值结果会出错,比如0、””、false、null、undefined。比如如果属性记录了一个数字,则这个值可以是零。这样的话,上段代码第一个判断就会导致错误。以此类推,如果属性值为null或者undefined时,三个判断都会导致错误。
判断属性是否存在最好的方法是使用in运算符。in运算符仅仅会简单地判断属性是否存在,而不会去读取属性的值,这样就避免了前文提到的有歧义的语句。如果实例对象的属性存在、或者继承自对象原型,in运算符都会返回true。比如:

var object = {
    count:0,
    related : null
};

//好的写法
if("count" in object){
    //这里的代码会执行
}

//不好的写法:检测假值
if(object["count"]){
    //这里的代码不会执行
}

//好的写法
if("related" in object){
    //这里代码会执行
}

//不好的写法:检测是否为null
if(object["related"] != null){
    //这里代码不会执行
}

如果你只想检查实例对象的某个属性是否存在,则使用hasOwnProperty()方法。所有继承自Object的Javascript对象都有这个方法,如果实例中存在这个属性则返回true(如果这个属性只存在原型里,则返回false)。需要注意的是,在IE8以及更早版本的IE中,DOM对象并非继承自Object,因此也不会包含这个方法。也就是说,你在调用DOM对象的hasOwnProperty()方法之前应当先检测其是否存在。

//对于所有非DOM对象来说,这是好的写法
if(Object.hasOwnProperty("related")){
    //执行这里的代码
}  

//如果你不确定为DOM对象,则这样来写
if("hasOwnProperty" in object && object.hasOwnProperty("related")){
    //执行这里的代码
}  

不管你什么时候需要检测属性的存在性,请使用in运算符或者hasOwnProperty(),这样做可以避免很多Bug。

Javascript函数调用的四种方式

一、Javascript函数的特点

Javascript中函数有两个主要特点使其显得比较特殊。第一个特点在于函数是第一类对象,第二个特点在于它们可以提供作用域。函数就是对象,其表现如下:

  • 函数可以在运行时动态创建,还可以在程序执行过程中创建
  • 函数可以分配给变量,可以将它们的引用复制到其他变量,可以被扩展,此外除了少数特殊情况外,函数还可以被删除
  • 可以作为参数传递给其他函数,并且还可以由其他函数返回
  • 函数可以有自己的属性和方法

二、Javascript中函数定义方式

方式一:
var add = function(a,b){ //函数表达式
    return a + b;
};
方式二:
function add(a,b){ //函数声明
    return a + b;
}
方式三:
var add = new Function('a,b','return a + b');//不推荐使用  

Javascript是一种解释性的语言,函数声明会在Javascript代码加载后、执行前被解释,而函数表达式只有在执行到这一行代码时才会被解释。函数声明中不需要分号结尾,但在函数表达式中需要分号,并且应该总是使用分号。

函数表达式VS函数声明:名称和变量声明提升

alert(add(10,10));//正常调用
function add(a,b){
    return a + b;
}
以上代码可以正常运行。因为在代码开始执行之前,解析器就已经读取函数声明并将其添加到执行环境中。  
若下面例子所示,把上面的函数声明改为使用函数表达式方式,就会在执行期间导致错误:  
alert(add(10,10));  
var add = function(a,b){
    return a + b ;
};  

三、Javascript中四种调用方式

1、方法调用模式
当一个函数被保存为对象的一个属性时,我们称它为一个方法。当一个方法被调用时,this被绑定到该对象。如果调用表达式包含一个提取属性的动作(即包含一个.或[subscript]下标表达式),那么它就当做一个方法来调用。

var myObject = {
    value : 0,
    increment:function(inc){
        this.value += typeof inc === 'number' ? inc : 1;
        console.log(inc);
    }
 }  

方法可以使用this访问自己所属的对象,所以它能从对象中取值或对对象进行修改。this到对象的绑定发生在调用的时候。这个延时绑定使得函数可以对this高度复用。
2、函数调用模式
当一个函数并非一个对象的属性时,那么它就是被当做一个函数来调用:
var sum = add(3,4);
以此模式调用函数时,this被绑定到全局对象。这是语言设计上的一个错误,倘若语言设计正确,那么当内部函数被调用时,this应该仍然绑定到外部函数的this变量。这个设计错误的后果就是方法不能利用内部函数来帮助它工作,因为内部函数的this被绑定了错误的值,所以不能共享方法对对象的访问权。如果该方法定义一个变量并给它赋值为this,那么内部函数就可以通过那个变量访问到this。按照约定我们把那个变量命名为that:

myObject.double = function (){//给myObject增加一个double方法  
    var that = this;
    var helper = function (){
        that.value = add(that.value,that.value);
        console.log(this);
    };
    helper();//以函数的形式调用helper
};
myObject.double();//以方法的形式调用double  

3、构造器调用模式
当以new操作符调用构造函数时,函数内部将会发生以下情况:

  • 创建一个空对象并且this变量引用了该对象,同时还继承了该函数的原型
  • 属性和方法被加入到this引用的对象中
  • 新创建的对象由this所引用,并且最后隐式地返回this(如果没有显式地返回其他对象)
    以上情况看起来就像是在后台发生了如下事情:

    var Person = function(name){
        //使用对象字面量创建一个新对象
        //var this = {};          
        //向this添加属性和方法
        this.name = name;
        this.say = function(){
            return "I am " + this.name;
        };
        //return this;          
    }      
    

    4、Apply调用模式
    因为Javascript是一门函数式的面向对象编程语言,所以函数可以拥有方法。apply方法让我们构建一个参数数组传递给调用函数,它允许我们选择this的值。apply方法接收两个参数,第一个要绑定给this的值,第二个就是参数数组。
    对于apply和call两者作用上是相同的,但两者在参数上有区别的。对于第一个参数意义都一样,但对第二个参数:apply传入的是一个参数数组,也就是将多个参数组合成一个数组传入,而call则作为call的参数传入(从第二个参数开始)。
    如func.call(func1,var1,var2,var3)对应的apply写法为:func.apply(func1,[var1,var2,var3]);

戏说JSON与JSONP

一、什么是JSON

  • JSON指的是Javascript对象表示法(Javascript Object Notation)
  • JSON是轻量级的文本数据交换格式或数据描述格式
  • JSON使用javascript语法来描述对象,但JSON仍然独立于语言和平台
  • JSON和文字对象唯一的语法差异在于,在JSON中,属性名称需要包装在引号中才能称为合法的JSON。而对象字面量中,仅当属性名称不是有效的标识符时才会需要引号。比如,字符之间有空格{“first name”,”Sem”}

二、JSON的优点

  • 基于纯文本,便于跨平台传递;
  • Javascript原生支持,后台语言几乎全部支持;
  • 轻量级数据格式,占用字符数量极少,适合网络传输;
  • 容易编写和解析,格式化处理后可读性较强

三、使用JSON

1、定义及使用
//描述一个人
var person = {
    "name" : "Semlinker",
    "age" : "28"    
}
//获取这个人的姓名
var name = person.name;
2、常用操作
//字符串转JSON
var jsStr = '{"mykey" : "my value"}';
var data = JSON.parse(jsStr);
//序列化为一个JSON字符串
var mycar = {
    name : "ferrari",
    color : "yellow"
};
var jsonstr = JSON.stringify(mycar);

四、什么是JSONP

  • Javascript出于安全方面的考虑,不允许跨域调用其他页面的对象
  • Web中含有src属性的组件,都拥有跨域的能力,比如script、img、iframe组件
  • 当前阶段如果想通过纯web端跨域访问数据只有一种可能,那就是在远程服务器上设法把数据装进js格式的文件里,以便供客户端进一步调用和处理
  • 恰巧我们已经知道有一种叫做JSON的纯字符数据格式可以简洁的描述复杂数据,更妙的是JSON还被js原生支持,所以在客户端几乎可以随心所欲的处理这种格式的数据
  • 这样一来解决方案就呼之欲出了,web客户端通过与调用脚本一模一样的方式,来调用跨域服务器上动态生成的js格式文件,显而易见,服务器之所以要动态生成JSON文件,目的就在于把客户端需要的数据装入进去
  • 客户端在对JSON文件调用成功之后,也就获得了自己所需的数据,剩下的就是按照自己需求进行处理和展现了,这种获取远程数据的方式看起来非常像AJAX,但其实并不一样
  • 为了便于客户端使用数据,逐渐形成了一种非正式传输协议,人们把它称作JSONP,该协议的一个要点就是允许用户传递一个callback参数给服务端,然后服务端返回数据时会将这个callback参数作为函数名来包裹住JSON数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了

五、JSONP的实现

一、客户端实现
<!DOCTYPE html>
<html lang="zh-cn">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<head>
    <title></title>
    <script type="text/javascript">
        // 获取航班信息查询结果后的回调函数
        var flightHandler = function(data){
        console.info('你查询的航班结果是:票价 ' + data.price + ' 元,' + '余票 ' + data.ticket + ' 张。');
        };
        // 提供jsonp服务的url地址(不管是什么类型的地址,最终的返回值都是一段javascript代码)
       var url = "http://flightQuery.com/jsonp/flightResult.aspx?code=CA1998&callback=flightHandler";
        // 创建script标签,设置其属性
        var script = document.createElement('script');
        script.setAttribute('src', url);
        // 把script标签加入head,此时调用开始
       document.getElementsByTagName('head')[0].appendChild(script); 
   </script>
</head>
    <body></body>
</html>
二、服务端实现
flightHandler({
  "code": "CA1998",
  "price": 1780,
  "tickets": 5
});

六、ajax与jsonp的区别

  • ajax和jsonp这两种技术在调用方式上很相似,都是请求一个url,然后对服务器返回的数据进行处理,因此jquery和ext等框架都把jsonp作为ajax的一种形式进行了封装
  • ajax和jsonp其实本质上是不同的东西。ajax的核心是通过XmlHttpRequest获取远程内容,而jsonp的核心则是动态添加script标签来调用服务器提供的js脚本
  • ajax与jsonp的区别不在于是否支持跨域,ajax通过服务端代理一样可以实现跨域,jsonp本身也不排斥同域的数据的获取
  • jsonp是一种方式或者说非强制性协议,如同ajax一样,它也不一定非要用json格式来传递数据,如果你愿意,字符串都行,只不过这样不利于用jsonp提供公开服务

玩转Emmet

一、Emmet是什么

Emmet是一组用于快速编写HTMLCSS的工具,它由两个核心组件组成:缩写扩展器和与上下文无关的HTML标签对匹配器。

二、Emmet能干嘛

亲,难道你是传说中的”闪电手”,编写下面这个HTML结构,你需要多长时间,一分钟 ? 两分钟 ? 马上试试

<div id="page">
<div class="logo"></div>
<ul id="navigation">
    <li><a href="">Item 1</a></li>
    <li><a href="">Item 2</a></li>
    <li><a href="">Item 3</a></li>
    <li><a href="">Item 4</a></li>
    <li><a href="">Item 5</a></li>
</ul>
</div>  

努力地敲着键盘…..
见证奇迹的时刻到了,请睁大你的双眼,抬起你那高贵的双手,轻轻地键入

#page>div.logo+ul#navigation>li*5>a{Item $}   ---------》革命尚未成功,请你迅速你按下Tab

怎么样是不是已经有了恋爱的冲动,爱它就赶快行动吧 ——————-> 继续往下看

三、更多示例

示例1:
输入: div#header>ul#nav>li*4>a
输出:
<div id="header">
<ul id="nav">
    <li><a href=""></a></li>
    <li><a href=""></a></li>
    <li><a href=""></a></li>
    <li><a href=""></a></li>
</ul>
</div>

示例2:
输入:div.item$*3
输出:
<div class="item1"></div>
<div class="item2"></div>
<div class="item3"></div>

示例3:
输入:div#i$-test.class$$$*3
输出:
<div id="i1-test" class="class001"></div>
<div id="i2-test" class="class002"></div>
<div id="i3-test" class="class003"></div>

四、常用缩写

  • html:5 (html5结构)
  • link:css (引入css)
  • script:src (引入js)
  • script (html中插入js)
  • ul+ (ul及一个li)
  • a:link (插入a标签)
  • form:get (get表单)
  • input:hidden (hidden输入框)
  • div#name (id:name)
  • div.name (class:name)
  • head>link:css (head添加link)
  • p+p (添加2个p)
  • p*3 (添加3个p)
  • ul>li.item$*6 (创建ul下有个li同时class分别为item1,item2)

五、展开缩写说明

展开缩写功能将类似CSS的选择器转换为XHTML代码。
以下是支持的属性和操作符列表

  • E (元素名称 div,p)
  • E#id(使用id的元素div#content,p#intro)
  • E.class(使用类的元素div.header)
  • E > N(子代元素div>p)
  • E + N(兄弟元素h1+p)
  • E N(元素倍增ul#nav>li5>a)
  • E$ * N(条目编号)

Javascript事件代理

一、什么是事件代理

事件代理,也有人翻译为”事件委托”,是一种利用javascript事件冒泡特性的高级方法。当有多个子元素或者子元素经常变化时,通过事件代理可以只监听父级元素事件,这样就避免了把事件处理器添加到多个子级元素上。一般情况下这样做的效率更高,而且代码机构更加清晰,易于后续操作和管理。

二、事件代理的应用

假设有以下导航菜单,它由一个ul父节点和多个li子节点组成。

<ul id="nav">
    <li class="menu"><a href="">menu 1</a></li>
    <li class="menu"><a href="">menu 2</a></li>
    <li class="menu"><a href="">menu 3</a></li>
    <li class="menu"><a href="">menu 4</a></li>
    <li class="menu"><a href="">menu 5</a></li>
</ul>

当我们鼠标移动到li的时候,需要获取此li的相关信息并以悬浮框的形式显示详细信息,或者当li被点击的时候需要触发相应的处理事件。我们通常的做法是为每个li都添加一个onMouseOver或者onClick之类的事件监听()。

function addListeners4Li(liNode){
  liNode.onclick = function clickHandler(){...};
  liNode.onmouseover = function mouseOverHandler(){...}
}

window.onload = function(){
  var ulNode = document.getElementById("nav");
  var liNodes = ulNode.getElementByTagName("li");
  for(var i=0, l = liNodes.length; i < l; i++){
    addListeners4Li(liNodes[i]);
  }   
}  

如果该ul中的子元素li会频繁地添加或者删除,我们就需要在每次添加li的时候都调用addListeners4Li方法来为对应的li节点添加事件处理函数,这样就增加了工作的繁琐性和出错的可能性。
那我们该使用什么办法来解决这个问题呢?更简单的方式就是使用事件代理机制,当事件传递上层父节点的时候,我们通过检查事件的目标对象来判断并获取事件源li。具体代码如下:

// 获取父节点,并为它添加一个click事件
document.getElementById("nav").addEventListener("click",function(e) {
// 检查事件源e.targe是否为Li
if(e.target && e.target.nodeName.toLowerCase() == "li") {
// 真正的处理过程在这里
    handle4Click();
  }
});  

为父节点ul添加一个click事件,当子节点被点击的时候,click事件会从子节点开始向上冒泡。父节点捕获到事件之后,通过判断e.target.nodeName来判断是否为我们需要处理的节点,并且通过e.target拿到了被点击的li节点。从而可以获取到相应的信息,并作处理。

三、事件冒泡及捕获

DOM2.0模型将事件处理流程分为三个阶段:一、事件捕获阶段,二、事件目标阶段,三、事件起泡阶段。如图:
image

事件捕获:当某个元素触发某个事件(如onclick),顶层对象document就会发出一个事件流,随着DOM树的节点向目标元素节点流去,直到到达事件真正发生的目标元素。在这个过程中,事件相应的监听函数是不会被触发的。

事件目标:当到达目标元素之后,执行目标元素该事件相应的处理函数。如果没有绑定监听函数,那就不执行。

事件冒泡:从目标元素开始,往顶层元素传播。途中如果有节点绑定了相应的事件处理函数,这些函数都会被一次触发。如果想阻止事件起泡,可以使用e.stopPropagation()(Firefox)或者e.cancelBubble=true(IE)来组织事件的冒泡传播。

四、事件代理的优点

1、当遇到子元素非常多时,使用事件代理无疑大大的减少了浏览器在内存中创建的事件监听处理器,提高了性能,降低了内存消耗;

2、当子元素可能会不断地被增加、删除或者替换时,使用事件代理就避免出现某些元素的事件没有被移除,造成内存泄露的风险;

3、代码管理更加清晰,外层做统一的事件管理,易于修改和扩展。

Fork me on GitHub