• JavaScript(五):函数(闭包,eval)


    1.函数的申明:三种方法:

    1. function命令
    2. 函数表达式:变量赋值
    3. Function构造函数
     1 //method 1: function命令
     2 function test(){
     3     console.log('hello function');
     4 }
     5 
     6 //method 2:函数表达式,赋值给变量
     7 var test1=function(){//这是个匿名函数
     8     console.log('hello function1');
     9 };//注意这里有分号
    10 
    11 //method 3:Function构造函数
    12 var test2=new Function(
    13     'return "hello function2"'
    14 );
    15 
    16 test();
    17 test1();
    18 console.log(test2());

    运行结果:

    第二种方法:函数表达式,如果函数不是匿名函数,而是加上相应的函数名,则只在函数内部有效。

    1 var test3=function x(){
    2     return typeof x;//此处有效
    3 };
    4 console.log(test3());
    5 console.log(x());//报错 is not defined;外部访问无效

    所以引申可以这样写:

    1 //这样:内部和外部均能访问test4
    2 var test4=function test4(){
    3     return typeof test4;
    4 };
    5 console.log(test4());

    好处:可以在函数体内部调用自身;方便排错(除错工具显示函数调用栈时,一层一层往上抛,将显示函数名,而不是匿名函数)

    第三种方法:Function构造函数:可以不加new,返回结果一样;可以传递多个参数,只有最后的这个参数被当做函数体。

    建议:不要使用该Function构造函数来申明函数!不直观。

     1 var test5=new Function(
     2     'return "hello Function construct"'
     3 );
     4 //效果相当于 不加new
     5 // var test5=Function(
     6 //     'return "hello Function construct"'
     7 // );
     8 var test6=new Function(
     9     'a',//函数参数
    10     'b',//函数参数
    11     'return a<b ? a: b'//函数体
    12 );
    13 console.log(test5());//hello Function construct
    14 console.log(test6(10,100));//10

    函数可以重复申明,但是后申明的函数会覆盖前面申明的函数。而且由于函数名的提升,前面的函数在任何时候均无效。

    第一等公民:因为函数与其它数据类型的地位平等,因此称作第一等公民!

    JavasScript将函数看作一种值,与数值、字符串、布尔值等等其它值地位相等。凡是可以使用值的地方,均能使用函数。如:将函数赋值给变量;将函数赋值给对象的属性(键);将函数作为参数传入其它函数;将函数作为另一个函数的返回结果!

     1 console.log('函数是第一等公民');
     2 function print(s){
     3     console.log(s);
     4 }
     5 //将函数赋值给变量
     6 var test7=function (){
     7     console.log('将函数赋值给变量');
     8 };
     9 //将函数赋值给对象的属性
    10 var obj={
    11     x:1,
    12     y:print
    13 };
    14 //将函数作为参数传入另一个函数;将函数作为另一个函数结果返回
    15 function test8(print){
    16     return print;
    17 }
    18 print('haha---');
    19 test7();
    20 obj.y('xixi---');
    21 test8(print)('nicai---');

    运行结果:

    函数名的提升:JavaScript引擎将函数名等同视为变量名,所以采用function命令申明函数时,函数会像变量提升一样,提升至代码头部

    1 test9();//相当于函数申明之后,然后调用
    2 function test9(){
    3     console.log('test 9');
    4 }

    运行结果:

    上面相当于:

    1 function test9(){
    2     console.log('test 9');
    3 }
    4 test9();

    但是如果采用函数表达式:

    1 test10();//报错
    2 var test10=function (){
    3     console.log('test 10');
    4 };

    运行结果:

    其实上面代码相当于:

    1 var test10;
    2 test10();
    3 test10=function(){
    4     console.log('test 10');
    5 };

    不要在条件语句中使用申明函数:(ECMAScript规范);虽然浏览器中可能不报错!

    1 var a=null;
    2 if(a){
    3     function test11(){console.log('test')}
    4 }
    5 test11();

    运行结果:

    但是有些浏览器可能由于函数名提升,而导致运行结果正确。所以要达到这种效果,可以用函数表达式替代function命令申明函数。

    1 var a=null;
    2 if(a){
    3     var test11=function (){console.log('test')}
    4 }
    5 test11();

    函数属性和方法:

    name,length,toString()

    name:返回function后面的函数名称

    1 console.log('---function property name');
    2 function test12(){}
    3 var test13=function (){}
    4 var test14=function x(){}
    5 console.log(test12.name,test13.name,test14.name);

    运行结果:

    length:返回函数预期传入的参数个数(注意:无论实际传入的参数个数是多少,length返回的是预期的参数个数)

    1 // var test15=function (a,b,c){};//test15.length=3
    2 function test15(a,b,c){}
    3 console.log(test15.length);
    4 test15(1);
    5 console.log(test15.length);
    6 test15();
    7 console.log(test15.length);

    运行结果:

    toString():返回函数源码;以字符串形式输出

    1 function test16(){console.log('test 16')}
    2 console.log(test16.toString());//将函数完整的以字符串形式输出
    3 console.log(typeof test16.toString());//string

    运行结果:

    函数的参数:JavaScript参数不是必须的,允许参数省略;但是不能只传入靠后的参数,不传入靠前的参数(如果要达到这种效果,传入undefined)

     1 console.log('---paras');
     2 function test17(a,b){
     3     console.log(a<b?a:b);
     4 }
     5 test17(1,10);//正常传入参数
     6 test17(1);//只传入一个参数
     7 test17(1,2,3);//传入多个参数
     8 // test17(,1);//报错
     9 test17(undefined,100);//替代上面这行代码
    10 console.log(test17.length);//获取预期参数个数:2

    运行结果:

    如果参数传入的是原始类型的值(数值,字符,布尔值),传递方式是传值传递。即函数内部修改变量并不会影响外部。

    如果函数参数传入的是复合类型的值(数组,对象,函数),传递方式是传址传递。即函数内部修改变量将会影响外部。

     1 var op=10;
     2 function test18(op){//传值传递,不影响外部
     3     op=100;
     4     return op;
     5 }
     6 console.log(test18(op));//100
     7 console.log(op);//10
     8 var op={x:1,y:2};
     9 function test19(op){//修改复合类型里面的单个属性,会影响外部
    10     return op.x=10;//传址引用
    11 }
    12 console.log(test19(op));//10
    13 console.log(op.x);//10
    14 
    15 function test20(op){
    16     return op={x:100,y:1000};//完全替换复合属性,不会影响外部
    17 }
    18 console.log(test20(op));//{x:100,y:1000}
    19 console.log(op);//{x:10,y:2}

    arguments对象:函数允许不定数目参数,所以arguments对象包含函数运行时所有参数。arguments[0]表示第一个参数,依此类题。同时需要注意:1.arguments只在函数内部使用有效。2.尽管arguments看起来很像一个数组,但它是对象。函数特有的slice,forEach方法,arguments都没有。

    1 function test21(a,b){
    2     console.log(arguments[0],arguments[1]);
    3     console.log(arguments.length);
    4 }
    5 test21(10,100);
    6 test21(10,100,100);

    运行结果:

    所以我们可以通过arguments.length达到查看函数实际运行中带有的参数个数。这是test21.length只能查看预期参数的个数所不具备的!

    函数作用域(scope)全局变量(global variable);局部变量(local variable)

     1 var a1=1000;
     2 function test22(){
     3     console.log(a1);//函数内部能够读取函数外部的变量
     4 }
     5 console.log('---scope');
     6 test22();//1000
     7 function test2_2(a2){
     8     var a2=100;//var申明,其实是局部变量,不能delete掉
     9     return a2;
    10 }
    11 // console.log(a2);//报错;因为函数外部不能读取函数内部的变量(局部变量)
    12 
    13 function test23(){
    14     a3=1;//不加var 申明,其实是一个全局变量,可以用delete删除掉
    15 }
    16 test23();
    17 console.log(a3);//因为a3是全局变量,所以能够访问
    18 
    19 var a4=4;
    20 function test24(a4){
    21     a4=100;
    22     console.log(a4);//函数内部的局部变量可以覆盖函数外部全局变量
    23 }
    24 test24(a4);
    View Code

    即:函数内部可以访问全局变量和局部变量;局部变量会覆盖全局变量;函数外部通常不能访问函数内部局部变量。

    注意:var命令申明的局部变量,在函数内部也存在变量提升的现象。

    同时注意:函数执行时所在的作用域,是定义时的作用域,不是调用时的作用域!

    1 var x=1;
    2 function f1(){
    3     console.log(x);
    4 }
    5 function f2(){
    6     var x=10;
    7     f1();
    8 }
    9 f2();

    f1()调用时使用的是定义时的作用域。返回结果是1。

    注意:

    如果“箭头”前面这里没有var,那么其实是定义了全局变量。返回结果得到是10。所有建议所有变量申明的地方均使用var命令!

    注意下面这种情况:

    1 var f3=function(){
    2     console.log(a);
    3 };
    4 function f4(f3){
    5     var a=10;
    6     f3();
    7 }
    8 //报错,a is not defined
    9 f4(f3);//f3()调用时使用的定义时作用域,所以无法访问a.

    函数本身作用域:函数作为第一等公民,是一个值,也有自己的作用域。它的作用域与变量一样,就是其申明时所在的定义域。与其运行时所在作用域无关!

    由此也就产生了闭包!

    闭包(closure):可以简单理解为“定义在函数内部的函数”。本质上闭包就是将函数内部与函数外部相连接的一个桥梁!

     1 console.log('---closure');
     2 function g(){
     3     var value=10;
     4     function g1(){
     5         console.log(value);
     6     }
     7     return g1;
     8 }
     9 //正常来说我们不能直接在外面访问函数内部局部变量value,但是借助闭包函数g1,我们可以访问value.
    10 var v=g();
    11 v();//获取到了value

    闭包的用处1.读取函数内部变量2.让这些变量始终保存在内存中!

    故:外层函数每次运行,都会产生一个新的闭包。而这个闭包又会保存外层函数的内部变量,内存消耗很大。所以不能随意滥用闭包,否则容易影响网页性能!

    1 function clickNum(i){
    2     return function () { return i++};
    3 }
    4 var cli=clickNum(10);//cli始终保存在内存中
    5 console.log(cli());//10
    6 console.log(cli());//11
    7 console.log(cli());//12

    闭包的另外一个作用是可以封装对象的私有属性和方法

     1 function Bird(){
     2     var _weight=10;//私有属性加_表示,类似python
     3     function getWeight(){
     4         return _weight;
     5     }
     6     function setWeight(weight){
     7         _weight=weight;
     8     }
     9     return {//返回对象
    10         getWeight:getWeight,
    11         setWeight:setWeight
    12     }
    13 
    14 }
    15 
    16 var b=Bird();
    17 console.log(b.getWeight());//10
    18 b.setWeight(100);
    19 console.log(b.getWeight());//100

    立即调用的函数表达式(IIFE,Immediately-Invoked Function Expression):

    分清语句与表达式:(程序由语句组成,语句由表达式组成!)

    1 //这是语句
    2 function fn1(){}
    3 //这是表达式:函数表达式
    4 var f2=function (){};

    将function fn1(){}放入()中,那么就变成了表达式,可以立即进行调用!这就是“立即调用的函数表达式”。

    1 (function (){console.log('hi!')})();

    也可以这样写:

    1 (function(){console.log('orange')}());

    所以扩展开来,JavaScript以表达式来处理的函数定义的方法,均能产生这样的效果。

    var g=function (){console.log('white')}();
    true&&function(){console.log('blue')}();
    !function(){console.log('gold')}();
    +function(){console.log('silver')}();
    new function(){console.log('red')}();

    通常:只对匿名函数使用这种“立即执行的函数表达式”。目的:1.不需为函数命名,2.IIFE内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量

    eval函数将字符串当做语句执行

    1 console.log(eval('3+5'));//8
    2 console.log(eval('var a=3+5'));//undefined
    3 
    4 var a=10;
    5 eval('a=100');//如果这里是外部人员输入的,那么内部数据a被修改,产生安全问题
    6 console.log(a);//100,

    为了规避上面eval函数所带来的风险,严格模式规定,eval内部申明的变量,不会对外部变量造成影响!

    但是严格模式下依然可以修改外部变量,安全问题依然存在!

    在代码第一行加上'use strict';

    1 var a=10;
    2 eval('a=100');//如果这里是外部人员输入的,那么内部数据a被修改,产生安全问题
    3 console.log(a);//100,
    4 eval('var ab=1000;');
    5 console.log(ab);// ab is not defined

    运行结果:

    此外,eval函数中的字符串不会得到JavaScript引擎的优化,运行速度较慢!所有,建议尽量不要使用eval.

    经常可以见到eval解析JSON数据字符串,不过正确的写法是使用JSON.parse方法。

    eval还有“直接调用”“间接调用”之分。

    直接调用”的作用域是当前作用域;“间接调用”的作用域是全局作用域

     1 //直接调用
     2 var b1=10;
     3 function testb(){
     4     var b1=1;
     5     eval('console.log(b1)');//当前作用域
     6 }
     7 testb();//1
     8 console.log(b1);//10
     9 
    10 //间接调用
    11 var b2=10;
    12 function testc(){
    13     var e=eval;
    14     var b2=1;
    15     e('console.log(b2)')//e是eval的引用,作用域是全局作用域
    16 }
    17 testc();//10
    18 console.log(b2);//10

    参考:阮一峰JavaScript标准参考教程

  • 相关阅读:
    强引用、软引用、弱引用、幻象引用有什么区别?
    vue基础指令学习
    如何设计一个自动化测试框架
    测试工程师需要了解的shell变量知识
    记一次kubernetes集群异常: kubelet连接apiserver超时
    golang http/transport 代码分析
    logging in kubernetes
    tune kubernetes eviction parameter
    kubernetes continually evict pod when node's inode exhausted
    Compile git version inside go binary
  • 原文地址:https://www.cnblogs.com/why-not-try/p/7944126.html
Copyright © 2020-2023  润新知