js模式学习
在JavaScript有二个特性,
第一个特性是js可直接使用变量,甚至无需声明。如
    function sum(x,y){
        result = x + y;    //result是全局变量。
       console.log(result);
    }
    sum(1,2);
    console.log(result);    //3
如果使用var可以避免这个:
     function sum(x,y){
      var  result = x + y;
       console.log(result);
    }
    sum(1,2);
    console.log(result);    //报错,找不到这个变量
创建隐式全局变量的反模式是带有var声明的链式赋值
   function foo() {
       var a = b = 0;
   }
   foo();
//   console.log(a);    //a is not defined
   console.log(b);  //0,因为 var a = (b = 0 );此时b未经声明,表达式的返回值是0,在赋给var声明的局部变量a。
对链式赋值的所有变量都进行声明。
 function foo() {
       var a , b;
       a = b =0;    //这样均为局部变量。
   }
   foo();
变量释放时的副作用
- 使用var创建的全局变量(这类变量在函数外部创建)不能删除
 - 不使用var创建的隐含全局变量(尽管它是在函数内部创建)可以删除
 
    var global_var = 1;
    global_novar = 2;    //反模式
    (function(){
        global_fromfunc = 3;    //反模式
    }());
    delete global_var;    //false
    delete global_novar;    //true
    delete global_fromfunc;    //true
    console.log(typeof global_var);    //number
    console.log(typeof global_novar);    //undefined
    console.log(typeof global_fromfunc);    //undefined
在ES5 strict模式中,为没有声明的变量赋值会抛出错误(类似上述代码中的两种反模式)
访问全局对象
从内嵌函数的作用域访问
   var global = (function(){
       return this;
   }());
单一var模式(Single var Patten)
只使用一个var在函数顶部进行变量声明是一种非常有用的模式。
    function func() {
       var a = 1,
           b = 2,
           sum = a + b,
           myobject = {},
           i,
           j;
       //函数体。。。
    }
提升:凌散变量的问题
JavaScript允许在函数的任意地方声明多个变量,无论在哪里声明,效果都等同于在函数顶部进行声明。这就是所谓的提升。在JavaScript中,只要变量是在同一个范围(同一函数)里,就视为已经声明,哪怕是在变量声明就使用。
    //反模式
    myname = "global";//全局变量
    function func() {
      alert(myname);//未定义
      var myname = "local";
      alert(myname); //局部变量
    }
    func();
前面的代码片断运行结果和以下代码一样。
    //反模式
    myname = "global";//全局变量
    function func() {
      var myname; //等同于 -> var myname = undefined;
      alert(myname);//未定义
      myname = "local";
      alert(myname);  //局部
    }
    func();
for循环
这种模式的问题在于每次循环迭代时都要访问数据的长度。特别是当myarray不是数据,而是HTML容器对时。
    //次优循环
    for(var i = 0; i< myarray.length; i++) {
      //对myarray[i]的操作
      }
以下代码:在这种方式在,对长度的值值提取一次,但应用到整个循环中。
     for(var i = 0, max = myarray.length; i< max; i++) {
      //对myarray[i]的操作
    }
for-in 循环
    var man = {
        hand: 2,
        legs: 2,
        heads: 1
    };
    if(typeof Object.prototype.clone === "undefined") {
        Object.prototype.clone = function () {};
    }
    var i,
        hasOwn = Object.prototype.hasOwnProperty;
     // for-in循环
    for(i in man) {
     if(hasOwn.call(man, i )) { //过滤
         console.log(i, ":", man[i]);
     }
    }
避免使用隐式类型转换
    var zero = 0;
    if (zero === false) {
    // 因为zero是0,而不是false,所以代码未执行
    }
    // 反模式
    if (zero == false) {
        //改代码会被执行。
    }
避免使用eval()
该函数会将任意字符串当做一个JavaScript代码来执行。当需要讨论的代码是预先就编写好了(不是在动态运行时决定),是没有理由需要使用eval()。如果是运行时动态生成的,则也有更好的方法代替eval()。
    //反模式
     var property = "name";
    alert(eval("obj." + property));
    // 推荐的方法
    var property = "name";
    alert(obj[property]);
使用parseInt()的数值约定
该函数的第二个函数是一个进制参数,通常可以忽略该参数,但最后不要这么做。
    var month = "06",
        year = "09";
    month = parseInt(month,10);
    year = parseInt(yaer,10);
在ECMAScript 3版本中,0开始字符串会被当做一个八进制。而在ECMAScript 6 版本发生了改变。
对象字面量语法
- 将对象包装在大括号中(左大括号 “{” 和右大括号 “}”)
 - 对象中以逗号分隔属性和方法。
 - 用冒号来分隔属性名称和属性的值
 - 当给变量赋值时,请不要忘记右大括号 “}” 后的分号。
 
来自构造函数的对象
在下面的例子中展示了以两种等价的方法来创建两个相同的对象:
第一种方法-使用了字面量
    var car = {goes:"far"};
//另一种方法-使用内置构造函数
//反模式
    var car = new Object();
    car.goes = "far";
自定义构造函数
下面是对Person构造函数的定义:
    var Person = function (name) {
        this.name = name;
        this.say = function () {
            return "I am" + this.name;
        };
    };
当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; }在以上代码中,为了简单起见,将say()方法添加到this中。其造成的结果是在任何时候调用new Person()时都会在内存中创建一个新的函数。这种方法的效率显然非常低下。因为多个实例之间的say()方法实际上没有改变。更好的选择应该是将方法添加到Person类的原型中。
Person.prototype.say = function() { return "I am" + this.name; };以上语句并不是真相的全部。因为空对象实际上并不空,它已经从Person的原型中继承了许多成员。因此,它更像是下面的语句。
``//var this = Object.create(Person.prototype); 
自调用构造函数
为了解决前面模式的缺点,并使得原型属性可以在实例对象中使用,可以在构造函数中检查this是否为构造函数的一个实例,如果为否,构造函数可以再次调用自身,并且在这次调用中正确地使用new操作符:
    function Waffle() {
        if (!(this instanceof Waffle)) {
            return new Waffle();
        }
        this.tastes = "yummy";
    }
    Waffle.prototype.wantAnother = true;
    //测试调用
    var first = new Waffle(),
        second = Waffle();
    console.log(first.tastes);    // 输出"yummy"
    console.log(second.tastes);    // 输出"yummy"
    console.log(first.wantAnother); // 输出true
    console.log(second.wantAnother); // 输出true
数组字面量
在下面的例子中,可以相同的元素,并以两种不同的方法创建两个数组,即使用Array()构造函数和使用字面量模式。
    // 具有三个元素的数组
    // 反模式
     var a= new Array("itsy","bitsy","spider");
    // 完全相同的数组
    var a = ["itsy","bitsy","spider"];
    console.log(typeof a );    //输出"object",这是由于数组本身也是对象类型。
    console.log(a.constructor === Array);    // true
数组构造函数的特殊性
避开new Array() 的另一个理由是避免构造函数中可能产生的陷阱。
当想Array()构造函数传递单个数字时,它并不会成为第一个数组元素的值。相反,它却设定了数组的长度。
    //具有一个元素的数组
    var a = [3];
    console.log(a.length);  //1
    console.log(a[0]); // 3
    //具有三个元素的数组
    var a = new Array(3);
    console.log(a.length);  // 3
    console.log(typeof a[0]); //输出undefined
上面例子中,可能并非预期的效果,但是与new Array()传递一个整数相比,如果向构造函数传递一个浮点数,则情况变得更加糟糕。
    //使用数组字面量
    var a = [3.14];
    console.log(a[0]); // 3.14
    var a = new Array(3.14); //输出RangeError:invalid array length
    console.log(typeof a);
为了避免在运行时创建动态数组可能产生的潜在错误,坚持使用数组字面量表示法。
即时函数
即时函数模式是一种可以支持在定义函数后立即执行该函数的语法。
      (function(){
            alert('wathc out');
    }());
下面的替代语法也是很常见的,但JSLint偏好使用第一种语法:
    (function(){
        alert('wathc out');
    })();
这种模式非常有用,因为它为初始化代码提供了一个作用域沙箱(sandbox)。
    (function(){
      var days = ['Sun','Mon','Tus','Wed','Thu','Fri','Sat'];
      today = new Date();
      msg = 'Today is '+ days[today.getDay()] + ',' + today.getDate();
      alert(msg);
    }()); // 输出 "Today is Fri, 13"
如果上面这些代码没有包装到即时函数中,那么days,today和msg等变量将会成为全局变量,并遗留在初始化代码中。
即时函数的参数
也可以将参数传递到即时函数中,如下例子:
    (function (who, when) {
        console.log("I met " + who + "  on " + when);
    }("Joe Black", new Date()));
一般情况下,全局对象是以参数形式传递给即时函数的,以便在不使用Window:指定全局作用域限定的情况下可以在函数内部访问该函数,这样将使得代码在浏览器环境之外时具有更好的互操作性。
     (function (who, when) {
      // 通过 `global`访问全局变量
    }(this);
即时函数的返回值:
正如任何其他函数一样,即时函数可以返回值,并且这些返回值也可以分配给变量:
    var result = (function(){
        return 2 + 2;
    }());
另一种语法:
    var result = (function(){
        return 2 + 2;
    })();
下面这个例子中,即时函数返回的值是一个函数,它将分配给变量getResult,并且将简单地返回res值,该值被预计算并存储在即时函数的闭包中:
    var getResutl = (function() {
        var res = 2 + 2;
        return function() {
            return res;
        }
    } ());
当定义对象属性时可以使用即时函数。如果需要定义一个在对象生成期内永远都不会改变的属性,但是在定义它之前需要执行一些工作以找出正确的值。
    var o = {
        message: (function() {
            var who = "me",
            what = "call";
            return what + " " + who;
        }()),
        getMsg: function(0 {}
            return this.message;
        )
    };
//用法
o.getMsg(); //输出call me
o.message; // 输出call me
初始化时分支
        var utils = {
         addListener : function (el, type, fn) {
             if (typeof window.addEventListener === 'function') {
                 el.addEventListener(type, fn, false);
             } else if (typeof document.attachEvent === 'function') { // IE
             } else { // 更早版本的浏览器
                 el['on' + type] = fn;
             }
         },
             removeListener: function (el, type, fn) {
             }
       };
此段代码的问题在于效率比较低下。每次在调用utils.addListener()或utils.removeListener()时,都将会重复地执行相同的检查。
当使用初始化分支的时候,可以在脚本初始化加载时一次性特侧出浏览器特征。此时,可以在整个页面生命期内重定义函数运行方式。下面是一个可以处理这个任务的例子。
      //接口
       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(typeof document.attachEvent === 'function') { //IE
          utils.addListener('on' + type, fn );
          utils.removeListener('on' + type, fn );
       } else {
           utils.addListener = function (el, type, fn) {
               el['on' + type] = fn;
           };
           utils.removeListener = function (el, type, fn) {
               e['on' + type] = null;
           };
       }
函数属性
    var myFunc = function() {
        var cachekey = JSON.stringify(Array.prototype.slice.call(arguments)),result;
        if (!myFunc.cache[cachekey]) {
            result = {};
            //开销很大的操作
            myFunc.cache[cachekey] = result;
        }
        return myFunc.cache[cachekey];
    };
    // 缓存存储
    myFunc.cache = {};
请注意在序列化过程中,对象的“标识”将会丢失。如果有两个不同的对象并且恰好都具有相同的属性,这两个对象将会共享同一个缓存条目。
配置对象
配置对象模式是一种提供更整洁的API的方法。
    function addPerson(conf);
    var conf = {
        username: "batman",
        first: "Bruce",
        last: "Wayne"
    };
    addPerson(conf);
配置对象的优点在于:
- 不需要记住众多的参数以及其顺序。
 - 可以安全忽略可选参数。
 - 更加易于阅读和维护
 - 更加易于添加和删除参数。
 
而配置对象的不利之处在于:
- 需要记住参数名称。
 - 属性名称无法被压缩。
当函数创建DOM元素时,这种模式可能是非常有用的,例如,可以用在设置元素的CSS样式中,以为元素和样式可能具有大量可选特征和属性。 
Curry化
函数应用
在一些纯粹的函数式编程语言中,函数并不描述被调用,而是描述为应用。
    //定义函数
    var sayHi = function(who) {
        return "Hello" + (who ? ", " + who : "") + "!";
    };
    //调用函数
    sayHi(); //输出Hello
    sayHi('world'); // 输出hello world
    //应用函数
    sayHi.apply(null, ["hello"]); // 输出Hello world!
部分应用
Curry化
    //curry化的add()函数
    // 接受部分参数列表
     function add(x, y) {
        var oldx = x, oldy = y;
        if(typeof oldy === "undefined") {
            return function (newy) {
                return oldx + newy;
            };
        }
        //完全应用
        return x + y;
    }
    //测试
    console.log(typeof add(5));  // 输出 "function"
    console.log(add(3,4));  // 7
        //创建并存储一个新函数
        var add2000 = add(2000);
    console.log(add2000(10)); // 输出2010
更精简的版本
    //curry化的add()函数
    //接受部分参数列表
    function add(x, y) {
        if(typeof y === "undefined") { //部分
            return function (y) {
                return x + y;
            };
        }
        // 完全应用
        return x + y;
    }
下面是一个通用curry化函数的示例
    function schonfinkelize(fn) {
        var slice = Array.prototype.slice,
            stored_args = slice.call(arguments,1);
        return function() {
            var new_args = slice.call(arguments),
                args = stored_args.concat(new_args);
            return fn.apply(null,args);
        }
    }
      //普通函数
        function add(x, y) {
            return x + y;
        }
        //将一个函数curry化以获得一个新的函数
        var newadd = schonfinkelize(add,5);
        console.log(newadd(4)); // 输出9
        console.log(schonfinkelize(add,6)(7)); // 输出13
转换函数schonfinkelize()并不局限于单个参数或者单步Curry化。
下面是更多示例:
        //普通函数
        function add(a, b, c, d, e) {
            return a + b + c + d + e;
        }
        // 可运行于任意数量的参数
        schonfinkelize(add, 1, 2, 3)(5, 5); // 16
        //两步curry化
        var addOne = schonfinkelize(add, 1);
        addOne(10,10,10,10);
        var addSix = schonfinkelize(addOne, 2, 3);
        addSix(5, 5); //输出16
                    
                
                
            
        
浙公网安备 33010602011771号