zoukankan      html  css  js  c++  java
  • JavaScript学习总结(一)——闭包、对象、函数

    一、闭包(Closure)

    1.1、闭包相关的问题

    请在页面中放10个div,每个div中放入字母a-j,当点击每一个div时显示索引号,如第1个div显示0,第10个显示9;方法:找到所有的div,for循环绑定事件。

    示例代码:

    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="UTF-8">
            <title>闭包</title>
            <style type="text/css">
                div{
                    width: 50px;
                    height: 50px;
                    background: lawngreen;
                    float: left;
                    margin: 20px;
                    font:25px/50px "microsoft yahei";
                    text-align: center;
                }
            </style>
        </head>
        <body>
            <div>a</div>
            <div>b</div>
            <div>c</div>
            <div>d</div>
            <div>e</div>
            <div>f</div>
            <div>g</div>
            <div>h</div>
            <div>i</div>
            <div>j</div>
            
            <script type="text/javascript" src="js/jquery-3.1.1.js" ></script>
            <script type="text/javascript">
                var divs=document.getElementsByTagName("div");
                for (var i=0;i<divs.length;i++) {
                    divs[i].onclick=function(){
                        alert(i);
                    }
                }
                
            </script>
        </body>
    </html>

    运行结果:

    按照平时的思路去做的话,如上图结果所示,点击任何一个div弹出的都是10,因为点击事件的函数内部使用外部的变量i一直在变化,当我们指定click事件时并没有保存i的副本,这样做也是为了提高性能,但达不到我们的目的,我们要让他执行的上下文保存i的副本,这种机制就是闭包。

    使用闭包的方法:

    var divs=document.getElementsByTagName("div");
                
                for (var i=0;i<divs.length;i++) {
                    divs[i].onclick=(function(n){
                        return function(){
                            alert(n);
                        }
                    })(i);
                }

    运行结果:

    n是外部函数的值,但是内部函数(点击事件)需要使用,返回函数前的n被临时驻留在内存中给点击事件使用,简单说就是函数的执行上下文被保存起来,i生成了多个副本。

    1.2、理解闭包

    闭包概念:当一个内部函数被调用,就会形成闭包,闭包就是能够读取其他函数内部变量的函数,定义在一个函数内部的函,创建一个闭包环境,让返回的这个子程序抓住i,以便在后续执行时可以保持对这个i的引用。内部函数比外部函数有更长的生命周期;函数可以访问它被创建时所处的上下文环境。

    Javascript语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量

    二、对象

    对象就是“键/值”对的集合并拥有一个连接到原型(prototype)对隐藏连接。

    2.1、对象常量(字面量)

    一个对象字面量就是包含在一对花括号中的零个或多个“键/值”对。对象字面量可以出现在任何允许表达式出现的地方。

    对象的定义:

      //空对象
            var obj1={};
            
            //对象中的属性
            var obj2={name:"Bob",age:18};
            var obj3={"nick name":"Tom"};
            
            //对象中的方法
            var obj4={
                price:99,
                inc:function(){
                    this.price+=1;
                }
            }

    对象中可包含的内容:

    对象常量可以出现在任何允许表达式出现的地方,对象、数组、函数可以相互间嵌套,形式可以多种多样。对象的值可以是:数组,函数,对象,基本数据类型等。

    //对象中可包含的内容
                var obj5 = [{
                    name: "jack"
                }, {
                    name: "lucy",  //常量
                    hobby:["读书","看报","电影"],  //数组
                    friend:{name:"mark",height:178,friend:{}},  //对象
                    show:function(){  //函数
                        console.log("大家好,我是"+this.name);
                    }
                }];
                //对象中的this是动态的,指向的是:调用者
                obj5[1].show();

    输出:大家好,我是lucy

    2.2、取值

    方法一:直接使用点号运算

       //3取值
                var obj6={"nick name":"pig",realname:"Rose"};
                console.log(obj6.realname);
                //console.log(obj6.nick name);  错误

    方法二:使用索引器,当对象中的key有空格时

     //3取值
                var obj6={"nick name":"pig",realname:"Rose"};
                
                console.log(obj6["realname"]);
                console.log(obj6["nick name"]);

    2.3、枚举(遍历)

    方法一:

       var obj7={weight:"55Kg","nick name":"pig",realname:"Rose"};
                
                for (var key in obj7) {
                    console.log(key+":"+obj7[key]);
                }

    运行结果:

    输出顺序是不能保证的。

    2.4、更新与添加

    如果对象中存在属性就修改对应值,如果不存在就添加。对象通过引用传递,它们永远不会被复制

    var obj1={name:"King"};
                obj1.name="Jack";  //修改
                obj1.weight=100;   //添加属性
                obj1.show=function(){   //添加方法
                    console.log(this.name+","+this.weight);
                }
                obj1.show();

    输出:

        var obj1={name:"King"};
                obj1.name="Jack";  //修改
                obj1.weight=100;   //添加属性
                obj1.show=function(){   //添加方法
                    console.log(this.name+","+this.weight);
                }
                obj1.show();
                
    //            //引用
                var obj2=obj1;  //obj2指向obj1的引用
                obj2.name="Tom";
                obj1.show();

    输出:

    2.5、对象的原型

    javascript是一种动态语言,与C#和Java这样的静态语言是不一样的;javascript并没有严格的类型,可以简单认为javascript是由对象组成的,对象间连接到原型(prototype)实现功能的扩展与继承。每个对象都链接到一个原型对象,并且可以从中继承属性,所有通过常量(字面量)创建的对象都连接到Object.prototype,它是JavaScript中的顶级(标配)对象,类似高级语言中的根类。

     

    (在该处设置断点,运行,鼠标悬浮在上面时会显示如上图)

    现在我们修改系统中的Object对象,添加一个创建方法,指定要创建对象的原型,实现类似继承功能:

    <script type="text/javascript">
                if(typeof Object.beget!="function")
                {
                    Object.create=function(obj){
                        //构造函数,用于创建对象
                        var F=function(){};
                        //制定由构造函数创建对象的原型
                        F.prototype=obj;
                        //调用构造方法创建新对象
                        return new F();
                    }
                }
                
                var Jack={
                    name:"Jack",
                    show:function(){
                        console.log("姓名:"+this.name);
                    }
                }
                
                Jack.show();//输出
                
                var Tom=Object.create(Jack);//简单认为是:创建一个对象且继承Jack
                Tom.name="Tom";//重写name属性
                Tom.show();
            </script>

    运行结果:

    原型关系是一种动态关系,如果修改原型,该原型创建的对象会受到影响。

    <script type="text/javascript">
                if(typeof Object.beget!="function")
                {
                    Object.create=function(obj){
                        //构造函数,用于创建对象
                        var F=function(){};
                        //制定由构造函数创建对象的原型
                        F.prototype=obj;
                        //调用构造方法创建新对象
                        return new F();
                    }
                }
                
                var Jack={
                    name:"Jack",
                    show:function(){
                        console.log("姓名:"+this.name);
                    }
                }
                
                Jack.show();//输出
                
                var Tom=Object.create(Jack);//简单认为是:创建一个对象且继承Jack
                Tom.name="Tom";//重写name属性
                
                var Mary=Object.create(Jack);
                Mary.name="Mary";
                
                //修改原型中的方法
                Jack.show=function(){
                    console.log("姓名——"+this.name);
                }
                
                Tom.show();
                Mary.show();
            </script>

    运行结果:

    2.6、删除

     //删除属性
                delete mark.name;   
                //调用方法,输出:姓名:undefined
                mark.show(); 
                
                //删除函数
                delete mark.show;  
                //错误,mark.show is not a function
                mark.show();

    删除不用的属性是一个好习惯,在某些情况下可能引发内存泄漏。

    2.7、封装

    使用对象封装的好处是可以减少全局变量的污染机会,将属性,函数都隶属一个对象。

    封装前:

    var name="func"; //name是全局的,被暴露
                i=1; //全局的,没有var关键字盛名的变量是全局的,与位置关系不大
                function show(){  //show是全局的,被暴露
                    console.log("name——>"+name);
                    console.log(++i);
                }
                
                //i是全局的 
                show();//2
                show();//3

    运行结果:

    封装后:

    var fun=function(){
                    var i=1;
                    return{
                        name:"fun",
                        show:function(){
                            console.log("name——>"+this.name);
                            console.log(++i);
                        }
                    }
                }
                
                var a=fun();
                a.show(); //2
                a.show(); //3
                
                var b=fun();
                b.show();  //2,因为被封装,且闭包,i是局部私有的

    运行结果:

    三、函数

    javascript中的函数就是对象,对象就是“键/值”对的集合并拥有一个连接到原型对隐藏连接。

    3.1、参数对象 (arguments)

    第一个函数中有一个默认对象叫arguments,类似数组,但不是数组,该对象是传递给函数的参数。

    function counter(){
                    var sum=0;
                    for (var i=0;i<arguments.length;i++) {
                        sum+=arguments[i];
                    }
                    return sum;
                }
                
                console.log(counter(100,125,50,75,250));
                console.log(counter());

    运行结果:

    这里的arguments是一个隐式对象,不声明也在函数中,内部函数可以访问外部函数的任意内容,但是不能直接访问外部函数的arguments与this对象。

    function f1(){
                    console.log(arguments.length);
                    f2=function(){
                        console.log(arguments.length);
                    }
                    return f2;
                }
                
                var ff=f1(2,4,6,8);
                ff();

    运行结果:

    3.2、构造函数

    在javascript中对象构造函数可以创建一个对象。

         /*构造函数*/
              //可以简单的认为是一个类型的定义
               function Student(name,age){
                     this.name=name;
                     this.age=age;
                     this.show=function(){
                         console.log(this.name+","+this.age);
                     }
               }
               
               //通过new关键字调用构造函数,创建一个对象tom
               var Bob=new Student("Bob",18);
               var Tom=new Student("Tom",20);
               
               Bob.show();
               Tom.show();

     运行结果:

    3.3、函数调用

    3.3.1、call

    调用一个对象的一个方法,以另一个对象替换当前对象
    call([thisObj[,args]) 
    hisObj 可选项。将被用作当前对象的对象。args 将被传递方法参数序列。
    call 方法可以用来代替另一个对象调用一个方法。call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。

    示例:

               function Student(name,age){
                     this.name=name;
                     this.age=age;
               }
               var Bob=new Student("Bob",18);
               var Tom=new Student("Tom",20);
                showfun=function(add){
                       console.log(add+":"+this.name+","+this.age);
               }
               
               showfun.call(Bob,"Hao are you?");
               showfun.call(Tom,"Hello");

    运行结果:

    call方法中的参数都可以省去,第1个参数表示在哪个对象上调用该方法,或this指向谁,如果不指定则会指向window对象。

    示例:

      var name="函数";
               var age=22;
               showfun.call();

    运行结果:

    3.3.2、apply

    apply([thisObj[,argArray]])
    应用某一对象的一个方法,用另一个对象替换当前对象,与call类似。
    如果 argArray 不是一个有效的数组或者不是arguments对象,那么将导致一个 TypeError。
    如果没有提供 argArray 和 thisObj 任何一个参数,那么 Global 对象将被用作 thisObj, 并且无法被传递任何参数。
    对于第一个参数意义都一样,但对第二个参数:
    apply传入的是一个参数数组,也就是将多个参数组合成为一个数组传入,而call则作为call的参数传入(从第二个参数开始)。
    如 func.call(func1,var1,var2,var3)对应的apply写法为:func.apply(func1,[var1,var2,var3])
    同时使用apply的好处是可以直接将当前函数的arguments对象作为apply的第二个参数传入

    示例代码:

          
          
    function Student(name,age){
                     this.name=name;
                     this.age=age;
               }
               var Bob=new Student("Bob",18);
               var Tom=new Student("Tom",20);

          applyfun=function(greeting,height){ console.log(greeting+","+this.name+","+this.age+","+height); } applyfun.apply(Bob,["Hao are you?","178cm"]); applyfun.apply(Tom,["Hello","192cm"]);

    运行结果:

    从上面的示例中可以发现apply的第2个参数是一个数组,数组中的内容将映射到被调用方法的参数中,如果单这样看发现不如call方便,其实如果直接取方法的参数arguments则apply要方便一些。通过简单的变化就可以替代call。

     function display(){
                  applyfun.apply(Tom,arguments);//这里的Tom是上文中创建的对象
              }
              display("你好","170cm");

    运行结果:

    javascript里call和apply操作符可以随意改变this指向
    如果在javascript语言里没有通过new(包括对象字面量定义)、call和apply改变函数的this指针,函数的this指针都是指向window的。
    关于this指针,我的总结是:是谁调用的函数,那么这个函数中的this指针就是它;如果没有明确看出是谁调用的,那么应该就是window调用的,那么this指针就是window。

    3.3.3、caller

    在一个函数调用另一个函数时,被调用函数会自动生成一个caller属性,指向调用它的函数对象。如果该函数当前未被调用,或并非被其他函数调用,则caller为null。
    在JavaScript的早期版本中,Function对象的caller属性是对调用当前函数的函数的引用

     function add(){
                  console.log("add被调用");
                  //add方法的调用函数,如果调用add方法的不是函数则为null
                  console.log(add.caller);
              }
              
              function calc(){
                  add();
              }
              
              //直接调用add方法
              add();
              //间接通过calc方法调用
              calc();

    运行结果:

    caller与this还是有区别的,this是指调用方法的对象,而caller是指调用函数的函数。

    function add(n){
                  console.log("add被调用");
                  if(n<=2){
                      return 1;
                  }
                  //add方法的调用函数,如果调用add方法的不是函数则为null
                  return add.caller(n-1)+add.caller(n-2);
              }
              
              function calc(n){
                  console.log("calc被调用");
                  return add(n);
              }
              
              console.log(calc(3));

    结果:

    3.3.4、Callee

    当函数被调用时,它的arguments.callee对象就会指向自身,也就是一个对自己的引用

    复制代码
               function add(n1,n2){
                      console.log(n1+n2);
                      //arguments.callee(n1,n2);  //指向add方法
                      return arguments.callee;
               }
               
               add(1,2)(3,4)(5,6)(7,8)(8,9);
    复制代码

    运行结果:

    当第1次调用add方法时输入3,立即将函数返回再次调用,每次调用后又返回自己,这样可以实现链式编程。

    3.5、立即执行函数表达式 (IIFE)

    IIFE即Immediately-Invoked Function Expression,立即执行函数表达式

    3.5.1、匿名函数与匿名对象

    匿名函数就是没有名称的函数,javascript中经常会使用匿名函数实现事件绑定,回调,实现函数级的私有作用域,如下所示:

            function(){
                console.log("这是一个匿名函数");
            };

    匿名对象:

    复制代码
            {
                name:"foo",
                show:function(){
                    console.log(this.name);
                }
            }
    复制代码

    没有名称的匿名函数也叫函数表达式,它们间是有区别的。

    3.5.2、函数与函数表达式

    下面是关于函数与函数表达式定义时的区别

    a)、函数定义(Function Declaration)

    function Identifier ( Parameters ){ FunctionBody }

    function 函数名称(参数){函数主体}

    在函数定义中,参数(Parameters)标识符(Identifier )是必不可少的。如果遗漏,会报提示错误:

    代码:

            function(){
                console.log("这是一个匿名函数");
            };

    结果:

    b)、函数表达式(Function Expression)

    function Identifier(Parameters){ FunctionBody }
    函数表达式中,参数和标识符都是可选的,与函数定义的区别是标识符可省去。

    其实,"function Identifier(Parameters){ FunctionBody }"并不是一个完整的函数表达式,完整的函数的表达式,需要一个赋值操作。
    比如: var name=function Identifier(Parameters){ FunctionBody }

    3.5.3、立即执行函数表达式与匿名对象

    复制代码
                //1 正常定义函数
                function f1(){
                    console.log("正常定义f1函数");
                };
                
                //2 被误解的函数表达式
                function(){
                    console.log("报错Unexpected token (");
                }();
                
                //3 IIFE,括号中的内容被解释成函数表达式
                (function(){
                    console.log("IIFE,正常执行");
                })();
                
                //4 函数表达式
                var f2=function(){
                    console.log("这也被视为函数表达式");
                };
    复制代码

    第3种写法为什么这样就能立即执行并且不报错呢?因为在javascript里,括号内部不能包含语句,当解析器对代码进行解释的时候,先碰到了(),然后碰到function关键字就会自动将()里面的代码识别为函数表达式而不是函数声明。

    如果需要将函数表达式或匿名对象立即执行,可以使用如下方法:

    复制代码
    <!DOCTYPE html>
    <html>
    
        <head>
            <meta charset="UTF-8">
            <title>IIFE</title>
        </head>
    
        <body>
            <script type="text/javascript">
                //调用匿名函数
                (function() {
                    console.log("这是一个函数表达式");
                })();
    
                //调用匿名对象
                ({
                    name: "foo",
                    show: function() {
                        console.log(this.name);
                    }
                }).show();
    
                console.log({
                    a: 1
                }.a);
    
                console.log({
                    a: function() {}
                }.a());
            </script>
        </body>
    
    </html>
    复制代码

    运行结果:

    3.5.4、各种IIFE的写法

    复制代码
    //最常用的两种写法
    (function(){ /* code */ }()); // 老师推荐写法
    (function(){ /* code */ })(); // 当然这种也可以
    
    // 括号和JS的一些操作符(如 = && || ,等)可以在函数表达式和函数声明上消除歧义
    // 如下代码中,解析器已经知道一个是表达式了,于是也会把另一个默认为表达式
    // 但是两者交换则会报错
    var i = function(){ return 10; }();
    true && function(){ /* code */ }();
    0, function(){ /* code */ }();
    
    // 如果你不怕代码晦涩难读,也可以选择一元运算符
    !function(){ /* code */ }();
    ~function(){ /* code */ }();
    -function(){ /* code */ }();
    +function(){ /* code */ }();
    
    // 你也可以这样
    new function(){ /* code */ }
    new function(){ /* code */ }() // 带参
    复制代码

    如果是函数表达式,可直接在其后加"()"立即执行。

    如果是函数声明,可以通过"()"、"+"、"-"、"void"、"new"等运算符将其转换为函数表达式,然后再加"()"立即执行。

    3.5.5、参数

    函数表达式也是函数的一种表达形式,同样可以像函数一样使用参数,如下所示:

                (function (n){
                    console.log(n);
                })(100);

    输出:100 

    其实通过IIFE还能形成一个类似的块级作用域,当块内的程序在使用外部对象时将优先查找块内的对象,再查找块外的对象,依次向上。

                (function(win,undfd){
                    win.console.log("Hello"==undfd);
                })(window,undefined);

    3.5.6、添加分号

    为了避免与其它的javascript代码产生影响后报错,常常会在IIFE前增加一个分号,表示前面所有的语句都结束了,开始新的一语句。

                var k=100
                (function (n){
                    console.log(n);
                })(k);

    上面的脚本会报错,因为javascript解释器会认为100是函数名。

                var k=100
                ;(function (n){
                    console.log(n);
                })(k);

    这样就正确了,在javascript中一行语句的结束可以使用分号,也可以不使用分号,因为一般的自定义插件会使用IIFE,这是一段独立的代码,在应用过程中不能保证用户会加上分号,所以建议在IIFE前加上分号。

    3.5.7、IIFE的作用

    1)、提高性能

    减少作用域查找时间。使用IIFE的一个微小的性能优势是通过匿名函数的参数传递常用全局对象window、document、jQuery,在作用域内引用这些全局对象。JavaScript解释器首先在作用域内查找属性,然后一直沿着链向上查找,直到全局范围。将全局对象放在IIFE作用域内提升js解释器的查找速度和性能。

    function(window, document, $) {
    
    }(window, document, window.jQuery); 

    2)、压缩空间

    通过参数传递全局对象,压缩时可以将这些全局对象匿名为一个更加精简的变量名

    function(w, d, $) {  
      
    }(window, document, window.jQuery);

    3)、避免冲突

     匿名函数内部可以形成一个块级的私有作用域。

    4)、依赖加载

    可以灵活的加载第三方插件,当然使用模块化加载更好(AMD,CMD),示例如下。

    A.html与B.html文件同时引用公用的common.js文件,但是只有A.html需要使用到StuObj对象,B.html不需要,但使用其它方法。

    Student.js

    复制代码
    var StuObj = {
        getStu: function(name) {
            return new Student(name);
        }
    }
    
    /*构造函数*/
    function Student(name) {
        this.name = name;
        this.show = function() {
            console.log("Hello," + this.name);
        }
    }
    复制代码

    Common.js

    复制代码
    function other1() {}
    
    function other2() {}
    
    (function($) {
        if($) {
            $.getStu("Tom").show();
        }
    })(typeof StuObj=="undefined"?false:StuObj);
    复制代码

    A.HTML

    复制代码
    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="UTF-8">
            <title>A</title>
        </head>
        <body>
            <script src="js/Student.js" type="text/javascript" charset="utf-8"></script>
            <script src="js/common.js" type="text/javascript" charset="utf-8"></script>
        </body>
    </html>
    复制代码

    B.HTML

    复制代码
    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="UTF-8">
            <title></title>
        </head>
        <body>
            <script src="js/common.js" type="text/javascript" charset="utf-8"></script>
            <script type="text/javascript">
                other1();
            </script>
        </body>
    </html>
    复制代码

    3.5.8、IIFE的变形

    也许有人会说IIFE将参数放在最后,需要移动到文档的末尾才能看到参数,比较麻烦,那么可以将IIFE变形为如下形式:

    复制代码
            (function(n){
                console.log(n);
                
                
                
                
                
                
                
                //认为这里有30000代码
                
                
                
                
                
                
                
            }(100));
    复制代码

    如果中间有很长的代码,参数100只有到文档的末尾才可以看得到,变形后的结果:

    复制代码
            (function(exp){
                exp(100);
            }(function(n){
                console.log(n);
                //认为这里有30000代码
            }));
    复制代码

    修改后的代码中有两个函数表达式,一个作为参数,就是我们主要要完成的功能向控制台输出数字,另一个作来IIFE立即执行的函数,主要的功能函数变成的IIFE的参数了。

    复制代码
                (function(win, doc, $) {
    
                }(window, document, jQuery));
    
                (
                    function(library) {
                        library(window, document, window.jQuery);
                    }
                    (function(window, document, $) {
    
                    })
                );
    复制代码

    bootstrap的写法:

    复制代码
                +function(yourcode) {
    
                    yourcode(window.jQuery, window, document);
    
                }(function($, window, document) {
                        $(function() {});  //jQueryDOM加载完成事件
                  });
    复制代码

    结合call或apply的写法:

                  (function(x){console.log(x)}).call(window,888);
                  (function(x){console.log(x)}).apply(window,[999]);

    输出:888 999

  • 相关阅读:
    201671030123叶虹 实验十四 团队项目评审&课程学习总结
    201671030123叶虹《英文文本统计分析》结对项目报告
    201671030123 叶虹 实验三作业互评与改进报告
    《构建之法》——三个问题
    201671030129 周婷 实验十四 团队项目评审&课程学习总结
    201671030129 周婷 《英文文本统计分析》结对项目报告
    201671030129 词频统计项目报告
    201671030129 周婷 实验三:作业互评与改进
    快速通读《现代软件工程——构建之法》
    201673020127 郁文曦 课程学习总结
  • 原文地址:https://www.cnblogs.com/lcy-house/p/6183491.html
Copyright © 2011-2022 走看看