js模式第四章

1,043 阅读6分钟

第四章 函数

函数表达式和函数声明式:

//命名函数表达式
var demo=funtion d(){
    console.log("Hello World!");
};

//函数表达式
var demo=function(){
    console.log("Hello World!");
};

//函数声明式
function demo(){
    console.log("Hello World!")
}

区别:

  • 函数表达式又分为命令函数表达式函数表达式(匿名函数),并且函数表达式结尾需要加上分号,而函数声明式则不需要。

  • 函数声明式会进行变量提升而函数表达式则不会。

    //全局函数 function foo(){ alert('global foo'); }

    function bar(){ alert('global bar') }

    function hoistMe(){ console.log(typeof foo); //输出 "function" console.log(typeof bar); //输出 "undefined"

      foo();//输出"local foo"
      bar();//输出TypeError: bar is not function
    
      //函数声明
      //变量'foo'以及其实现着2被提升
      function foo(){
          alert('local foo');
      }
    
      //函数表达式
      //仅变量 'bar' 提升
      //函数实现并未提升
      var bar = function () {
          alert('local bar')
      }
    

    }

回调模式

函数都是对象,函数可以被当做参数传递给其他函数,当一个函数被当做参数传递给另一个函数,且执行,这叫回调。

Code:

function writeCode(callback){
    //do something
    callback();
}

function introduce(){
    // do something
}

//传递函数,执行回调
writeCode(introduce);

回调与作用域

如果传递的对象不是一个函数,而是一个对象的方法,并且这个方法用this来引用所属的对象,那可能会导致意想不到的后果。

假设一个方法paint(),它是一个名为myapp的对象的方法:

var myapp={};
myapp.color="red";
myapp.paint=function(node){
    node.style.color=this.color;
};

函数findNodes()执行以下语句:

var findNodes=function(callback){
    // ...
    if(typeof callback === "function"){
        callback(found);
    }
};

如果执行findNodes(myapp.paint)并不会得到如期的结果,因为此时this的引用的对象发生了改变,变成了findNodes,而findNodes并没有color这个属性。那我们怎么得到预期的结果呢?

一个解决方法,我们传递回调函数,并且也传递回调函数所属的对象:

var findNodes = function(callback,callback_obj){
    // ...
    if(typeof callback === "function"){
        callback(callback_obj,found);
    }
};

那我们的写法将变成findNodes(myapp.paint,myapp),我们也可以将第一个参数写成字符串的形式,这样无需写两遍对象名, findNodes("paint",myapp),Code:

var findNodes = function(callback,callback_obj){
    // ...  
    if(typeof callback === "string" ){
        callback = callback_obj[callback]
    }

    if(typeof callback === "function"){
        callback.call(callback_obj,found)
    }
};

返回函数

函数也是对象,因此它们也可以用作为返回值。 下面有个demo:

var setup=function(){
    alert(1);
    return function(){
        alert(2);
    };
};
var my=setup(); //alert 1
my(); // alert 2

由于setup()包装了返回函数,它创建了一个闭包(粗俗的理解就是函数中的函数),可以用这个闭包存储一些私有数据,这些数据仅可以被该返回函数访问,但外部代码却无法访问。 Code:

var setup=function(){
    var count = 0;
    return function(){
        return (count+=1);
    };
};

var next=setup();
next(); //result is 1
next(); //result is 2
next(); //result is 3

自定义函数

如果创建了一个新函数并且将其分配给保存另外函数的同一个变量,那么就以一个新函数覆盖了旧函数。在某种程度上,回收了旧函数指针以指向一个新函数。而这一切发生在旧函数体的内部。在这种情况下,该函数以一个新的实现覆盖并重新定义了自身,这可能听起来比实际上更复杂,Code:

var scareMe=function(){
    alert("Boo!");
    scareMe=function(){
        alert("Double boo!");
    };
};

scareMe(); //输出 Boo
scareMe(); //输出 Double boo!

当函数有且执行一次时,这种模式非常有效。 但是这种模式有个缺点,就是定义在自身时,已添加的属性和方法都会丢失,此外,如果该函数使用了不同的名称,比如重新分配给了另外一个变量或者以对象的方法来使用,那么重定义的部分将永远不会发生。并且将会执行原函数体。 Code:

//1 添加一个新属性
scareMe.property="property";

//2 赋值给另一个不同名称的变量
var prank=scareMe;

//3 作为一个方法使用
var spooky = {
    boo:scareMe
};

// 
prank(); //输出 "bool"
prank(); //输出 "bool"
console.log(prank.property) //输出 "property"

spooky.boo(); //输出 "bool"
spooky.boo(); //输出 "bool"
console.log(spooky.boo.property); //输出 "property"

scareMe(); //输出 "Double boo!"
scareMe(); //输出 "Double boo!"
console.log(scareMe.property); // 输出 "undefined"

当我们以新名称和作为一个方法使用时,scareMe指针一直被重写,至此scareMe函数自定义的部分一直没有执行。直到当我们直接执行scareMe时,指针没有被重写,自定义部分才得以执行。

即时函数

即时函数模式,该模式有一下几部分组成:

  • 可以使用函数表达式定义一个函数(函数声明则无法达到这个效果)
  • 在末尾添加一组括号,这将导致该函数立即执行。
  • 将整个函数包括在括号中(只有不将该函数分配给变量才需要这样做)。

这种模式的好处就是形成了作用域沙箱。避免了全局污染,并且可以执行一些一次新函数,而不必去创建复用函数。

(function(){
    var days=['Sun','Mon','Tue','Wed','Thu','Fri','Sat'],
        today=new Date(),
        msg='Today is ' + days[today.getDay()] + ', ' + today.getDate();
    alert(msg);
}())

即时函数的参数

即时函数同样可以传递参数,Code:

;(function(who,when){
    console.log("I met " + who + " on " + when)
})("Nei",new Date());

即时函数的返回值

当然即时函数同样可以返回值,Code:

var result=(function(){
    return 2+2;
})();

赋值给一个变量时,即时函数可以不需要最外层括号,Code:

var result=function(){
    return 2+2;
}();

即时函数不单单可以返回整数值,同样还可以返回其他类型,例如返回函数。形成一个闭包,在一个函数作用域中保留变量。Code:

var getResult=(function(){
    var res = 2 + 2;
    return function(){
        return res;
    };
})();

于是我们可以借用即时函数,在创建对象时,可以将对象的属性永久保留在内存中。Code:

var o = {
    message: (function(){
        var who = "me",
            what = "call";
        return who + " " + what;
    })(),
    getMsg:function(){
        return this.message;
    }
};

优点

  • 即时函数模式创建了自调用的函数局部变量,避免了全局空间被临时变量污染。

初始化时分支

初始化时分支时一种优化模式,当知道某个条件在整个程序生命周期内都不会发生改变的时候,仅对该条件测试一次时很有意义的。浏览器嗅探(功能检测)就是一个典型的例子。Code:

var utils = {
    addListener:function(el,type,fn){
        if(typeof window.addEvenListener === "function"){
            el.addEventListener(type,fn,false);
        }else if(typeof document.attachEvent === "function"){
            el.attachEven('on'+type,fn);
        }
    },
    removeListener:function(el,type,fn){
        //......
    }
};
// 当我们每次调用utils.addListener 或 utils.removeListener时,都会重复执行检测代码。但是对于这样不会变的特性时,我们可以执行一次性检查代码。

一次性检查Code:

var utils = {
    addListener:null,
    removeListener:null
};

if(typeof window.addEventListener==="function"{
    utils.addListener = function(el,type,fn){
        el.addEventListener(type,fn,false);
    };
    utils.removeListener = function (el,type,fn) {
        el.removeEventListener(type,fn,false);
    }
}else if(){
    // ie......
}else{
    // 更早浏览器兼容模式
}

备忘模式

函数也是对象,我们可以添加属性上去,例如我们在大量计算的时候,就可以将结果缓存在函数对象上,Code:

var myFunc = function (param){
    if(!myFunc.cache[param]){
        var result = {};
        //...大计算
        myFunc.cache[param]=result;
    }
    return myFunc.cache[param]
};
myFunc.cache={};

当然我们的参数一般都不只一个,这时怎么办呢?看Code:

var myFunc = function (){
    var cachekey = JSON.stringfy(Array.prototype.slice.call(arguments)),
        result;
    if(!myFunc.cache[cachekey]){
        result = {};
        //...大计算
        myFunc.cache[cachekey]=result;
    }
    return myFunc.cache[cachekey]
};
myFunc.cache={};

配置对象

调用函数,传入参数时,一种传参方式是多少个参数传递多少个参数,Code:

Demo(one,two,three,four,five,...);

但是这样传参,参数一旦很多,函数将变得难看和难以维护。 所以我们传参时,将参数写进一个对象中,这样传参就是只有传递一个对象。

var conf={
    username:'Nei',
    first:'N',
    last:'de'
};

Demo(conf)

配置对象的优点在于:

  • 不需要记住众多的参数以及其顺序。
  • 可以安全忽略可选参数。
  • 更加易于阅读和维护。
  • 更加易于添加和删除参数。

而缺点在于:

  • 需要记住参数名称。
  • 属性名称无法被压缩。?