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提供公开服务

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