• 热门专题

JavaScript中的函数:闭包 this 高阶函数

作者:BaiDuBaiKe  发布日期:2016-01-05 19:28:47
Tag标签:闭包  函数  高阶  
  • 一.函数基本理论

    function compare(val1,val2){
        return val1 - val2;
    }
    var result = compare(5,10);

    1,函数的定义没什么意义,之后创建一个字符串,就是函数代码

    2,函数执行(被调用)的时候发生的事情:(以上面的代码为例)

    创建一个执行环境execution context ,该对象有一个特殊的属性叫[scope chain] 作用域链,属性的值是一个类数组对象,如上图所示,第一个包含了,this,arguments,val1和val2的活动对象,第二个是包含了compare和result,this的活动对象。

    理解函数的基本原理对于函数的理解函数闭包的概念很有帮助。

    二.高阶函数

    1.函数作为参数传递

    最经典的例子就是毁掉函数

    var fs  = require('fs');
    fs.readFile('test.txt',function(data,err){
        console.log(data);
    });

    2.函数作为返回值

    作为返回值时候,要注意此时的this指向。

    3.函数柯里化

    函数柯里化指首先接受一些参数,接受到的参数后不立即执行,而是返回一个新函数,刚才传入的参数在函数形成的闭包中被保存起来,待到真正求值的时候刚才保存的参数才会真正的求值。

    var cost = (function(){
        var args = [];
        return function(){
            if(arguments.length===0){
                var money =0;
                for(var i-0;i<args.length;i++){
                    money+=args[i];
                }
                return money;
            }else{
                [].push.apply(args,arguments);
            }
    
        }
    })();
    cost(100);//100
    cost(200);//200
    cost();//300

    4.函数节流

    函数节流的思想就是让一些频繁执行的函数减少执行频率;比如因为浏览器窗口变化引起resize事件的频繁执行,mouseover,上传进度等等。

    var throttle = function(fn,interval){
        var _self = fn,timer,firstTime;
        return function(){
            var args = arguments,_me = this;
            if(firstTime){
                _self.apply(_me,args);
                return firstTime = false;
            }
            if(timer){
                return false;
            }
            timer = setTimeout(function(){
                clearTimeout(timer);
                timer = null;
                _self.apply(_me,args);
            },interval||500);
        }
    };
    window.onresize = throttle(function(){
        console.log(1)},500);

    代码的解决办法是利用定时器延迟执行,如果定时器在规定时间后还没执行完,那么,下一次执行的时候就不会执行,直接返回;

     5.分时函数

    分时函数应用的场景比如,你的QQ好友有上千个,每一个好友是一个dom,这是加载的时候浏览器可能吃不消,就要用到setInterval函数来延迟加载。

    //ary需要加载的数据,fn加载逻辑,count每一批加载的个数
    var timeChunk = function(ary,fn,count){
        var obj, t;
        var len = ary.length;
        var start = function(){
            for(var i=0;i<Math.min(count||1,ary.length);i++){
                var obj = ary.shift();
                fn(obj);
            }
        };
        return function(){
             t = setInterval(function(){
                if(ary.length===0){
                    return clearInterval(t);
                }
                start();
            },200);
        }
    }
    
    var ary = [];
    for(var i=0;i<1000;i++){
       ary.push(i);
    }
    var renderFirendList = timeChunk(ary,function(n){
       var div = document.createElement('div');
       div.innerHTML = n;
       document.body.appendChild(div);
    },8);
    renderFirendList();

     6.惰性加载函数

    惰性 加载函数也很常见,比如浏览器嗅探中的时间绑定函数

    var addEvent = function(elem,type,handler){
        if(window.addEventListener){
            return elem.addEventListener(type,handler,false);
        }
        if(window.addEvent){
            return elem.addEvent('on'+type,handler);
        }
    }

     以上代码在非IE浏览器下每次都不会走第二个分支,并且每次添加一个事件就会执行一次判断,虽然这不会增加性能开销,但是可以利用惰性加载来解决

    var addEvent = function(elem, type, handler){
        if(window.addEventListener){
            addEvent = function(elem, type, handler){
                elem.addEventListener(type, handler, false)
            }
        }else if(window.addEvent){
            addEvent = function(elem, type, handler){
                elem.addEvent('on'+type, handler);
            }
        }
        addEvent(elem,type,handler);
    }

     三.this

    this的判断只要记住一点,就是在执行的时候动态绑定的,而不是函数声明时候绑定,以下是优先级

    if(hava new){

      this 就是new返回的这个对象

    }else if(hava call,apply 绑定){

      apply,call绑定的那个对象就是this

    }else if(有对象调用){

      this就是这个调用的对象

    }else{

      默认绑定到window //这种情况一般是闭包

    }

    window.name = 'globalname';
    var obj = {};
    obj.name = 'lucy';
    obj.show = (function(){
        console.log(this.name);
        return function(){ console.log(this.name)}
    })()
    
    obj.show();
    VM928:6 globalname
    VM928:7 lucy

    对于上面的代码,obj.show定义为一个对象的方法,但是该方法立即执行,并且是在全局的作用域下执行的,所以输出为globalname,当obj.show执行完之后返回了一个函数赋值给obj.show,说以obj.show此时才真正是对象的方法,所以第二个返回lucy,这个例子完美的证明了运行时动态绑定this。

    四.闭包

    闭包是有权访问另外一个函数作用域中的变量的函数。《JavaScript高级程序设计第三版》。

    典型的例子是一个内部函数访问外部函数的变量,即使这个内部函数返回了或者是被调用了,仍然可以访问外部变量,如下

    function com(propertyName){
        return function(obj1,obj2){
            var value1 = obj1[propertyName];
            var value2 = obj2[propertyName];
            return value1 - value2;
        }
    }
    var obj1 ={'name':1};
    var obj2 ={'name':2};
    var compare = com('name');
    console.log(compare(obj1,obj2));

    上面例子中,匿名函数访问了外部函数的局部变量propertyName,并且当它返回了,而且是在其他地方被调用了仍然可以访问。比如com函数返回了一个匿名函数,并且在其他地方被调用了这个函数,但是仍然可以访问propertyName变量对象,但是有一点就是,这里的propertyName是一个变量对象(活动对象)而不是变量本身,如果是在for等循环语句中就会出现错误。如下面的例子:

    function foo(){
    
        var result = [];
        for(var i = 0; i < 5; i++){
            result[i] = function(){
                return i;
            }
        }
        return result;
    }
    var s = foo();
    
    console.log(s[1]());//5

    上面代码输出结果是5的原因是每一个内部匿名函数包含的活动对象是i这个变量对象,所以最终foo()执行返回的每一个result[i](这里的i没有变成5是因为它没在匿名函数内),都是外部foo活动对象,所以最终结果就是5.避免这种结果的方法就是在外面继续增加一层作用域,使每一个result[i]函数都持有自己i的活动对象。

    function bar(){
        var result = [];
        for(var j = 0; j < 5; j++){
            result[j] = (function(num){
                return function(){
                    return num;
                }
            })(j)
        }
        return result;
    }

    这2段代码的函数活动对象图如下:

    第一个代码所有的result都引用函数foo中活动对象i所以当foo执行完返回后,i变量的值是5所以出现如上所示。

    第二段代码中,由于参数是按值复制传递的,所以j会一次赋值给num,最内层的匿名函数保存了3个活动对象,分别是立即执行函数,bar,和window,并且立即执行函数也是有5个,并且保存了5个num值,这样就可以达到预期的效果。

About IT165 - 广告服务 - 隐私声明 - 版权申明 - 免责条款 - 网站地图 - 网友投稿 - 联系方式
本站内容来自于互联网,仅供用于网络技术学习,学习中请遵循相关法律法规