zoukankan      html  css  js  c++  java
  • 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
  • 相关阅读:
    【BZOJ3110】K大数查询(权值线段树套线段树+标记永久化,整体二分)
    【BZOJ3669】魔法森林(LCT)
    art-template前端高性能模板
    spring新心得
    工作流程
    idea操作
    log4j学习
    对实体 "useSSL" 的引用必须以 ';' 分隔符结尾。
    JUnit4学习
    maven搭建
  • 原文地址:https://www.cnblogs.com/linst/p/7429291.html
Copyright © 2011-2022 走看看