javascript(函数)

function 

函数是一段能够重复调用的代码块。接收相应的参数并能够返回对应的值javascript

函数的声明

  javascript 有三种声明函数的方法。function 命令函数表达式Function构造器。java

    (1)function 命令es6

     function命令声明的代码区块,就是一个函数。function命令后面是函数名,函数名后面是一对圆括号,里面是传入函数的参数。函数体放在大括号里面。     数组

        function add(x, y) {
            console.log(x + y)
        }
        add(3,4);//7

    上面的代码命名了一个add函数,输出两个数字相加的和,之后使用add()这种形式,就能够调用相应的代码。这叫作函数的声明(Function Declaration)。安全

   (2)函数表达式闭包

      除了用function命令声明函数,还能够采用变量赋值的写法。   函数

        let add=function (x, y) {
            console.log(x + y)
        };
        add(3,4);

      这种写法将一个匿名函数赋值给变量。这时,这个匿名函数又称函数表达式(Function Expression),由于赋值语句的等号右侧只能放表达式。性能

      采用函数表达式声明函数时,function命令后面不带有函数名。若是加上函数名,该函数名只在函数体内部有效,在函数体外部无效。    优化

        let add=function addAdd(x, y) {
            console.log(addAdd)
        };
        console.log(addAdd) //报错  未定义
        add(3,4);// 输出函数自己

     上面代码在函数表达式中,加入了函数名addAdd。这个addAdd只在函数体内部可用,指代函数表达式自己,其余地方都不可用。这种写法的用处有两个,一是能够在函数体内部调用自身,二是方便除错(函数内部报错时,函数名存在时报错时错误信息中会包含函数名,函数名不存在时会指向引用的值的名称)。所以,下面的形式声明函数也很是常见。 spa

  

   

        let add = function add(x, y) {
            console.log(x + y)
        };
        add(3, 4);

    须要注意的是,函数的表达式须要在语句的结尾加上分号,表示语句结束。而函数的声明在结尾的大括号后面不用加分号。总的来讲,这两种声明函数的方式,差异很细微,能够近似认为是等价的。

    (3) Function 构造器     

        let add=new Function(
            'x','y','console.log(x+y)'
        )
        add(3,4);//7
        
        let say=new Function(
            'console.log("hello")'
        )
        say();//hello

      你能够传递任意数量的参数给Function构造函数,只有最后一个参数会被当作函数体,若是只有一个参数,该参数就是函数体。    

      Function构造函数能够不使用new命令,返回结果彻底同样。 总的来讲,这种声明函数的方式很是不直观,几乎无人使用。

函数的重复声明

  若是同一个函数被屡次声明,后面的声明就会覆盖前面的声明

        function add(x,y){
            console.log(x+y);
        }
        add(3,4);//12
        
        function add(x,y){
            console.log(x*y)
        }
        add(3,4);//12

  上面代码中,后一次的函数声明覆盖了前面一次。并且,因为函数名的提高(参见下文),前一次声明在任什么时候候都是无效的,这一点要特别注意。

圆括号运算符,return 语句和递归

  调用函数时,要使用圆括号运算符。圆括号之中,能够加入函数的参数。

        function add(x,y){
            return x+y
       console.log('result')//不会执行 } let result
=add(3,4); console.log(result)//7 

  上面代码中,函数名后面紧跟一对圆括号,就会调用这个函数。 函数体内部的return语句,表示返回。JavaScript 引擎遇到return语句,就直接返回return后面的那个表达式的值,后面即便还有语句,也不会获得执行。也就是说,return语句所带的那个表达式,就是函数的返回值。return语句不是必需的,若是没有的话,该函数就不返回任何值,或者说返回undefined。

  函数能够调用自身,这就是递归(recursion)。下面就是经过递归,计算数字的阶乘 

        function fact(i) {
            if (i == 1) {
                return 1
            }
            return i * fact(i - 1)
        }
        console.log(fact(3)) //6

  上面的代码就是

fact(3)==>3*fact(3-1)==>3*fact(2)
fact(2)==>2*fact(2-1)==>3*fact(1)
fact(1)==>1
fact(3)==>3*2*1==>6

第一等公民

  一等公民:通常来讲,若是某程序设计语言中的一个值能够做为参数传递,能够从子程序中返回,能够赋值给变量

  二等公民:能够做为参数传递,可是不能从子程序中返回,也不能赋给变量

  三等公民:它的值连做为参数传递都不行  

  JavaScript 语言将函数看做一种值,与其它值(数值、字符串、布尔值等等)地位相同。凡是可使用值的地方,就能使用函数。好比,能够把函数赋值给变量和对象的属性,也能够看成参数传入其余函数,或者做为函数的结果返回。函数只是一个能够执行的值,此外并没有特殊之处。 因为函数与其余数据类型地位平等,因此在 JavaScript 语言中又称函数为第一等公民

  

        function add(x, y) {
            console.log(x + y);
        }

        // 将函数赋值给一个变量
        let operator = add;
        operator(3, 4); //7


        // 将函数做为参数和返回值
        function addOperat(fn) {
            return fn;
        }
        addOperat(add)(3, 4) //7

函数名的变量提高

  JavaScript 引擎将函数名视同变量名,因此采用function命令声明函数时,整个函数会像变量声明同样,被提高到代码头部、(另外两种声明是不会存在这种状况的)因此,下面的代码不会报错。 

        let operator = add;
        operator(3, 4); //7
        function add(x, y) {
            console.log(x + y);
        }

下面这两种状况都会报错 

  

        // add 未定义
        add();
        let add=function(){
            console.log(1)
        }    

  

        // add 不是一个函数
        let add;
        add();
        add=function(){
            console.log(1)
        }

  所以,若是同时采用function命令和赋值语句声明同一个函数,最后老是采用赋值语句的定义。由于函数表达式声明的函数不会存在函数名的变量提高  

        // 这里不使用 let f
        var f=function(){
            console.log(1)
        };
        function f(){
            console.log(2)
        }
        f();//1

函数的属性和方法

  name属性

    获取函数的原始名称

        function f(){}
        console.log(f.name);//f
        
        let f2=function (){};
        console.log(f2.name);//f2
        
        let f4=function f3(){};
        console.log(f4.name);//f3

  length属性

    函数的length属性返回函数预期传入的参数个数,即函数定义之中的参数个数

    

        function f(){}
        console.log(f.length);//0
        
        let f2=function (a,b){};
        console.log(f2.length);//2
        
        let f4=function f3(a){};
        console.log(f4.length);//1

  toString()

    输出函数的内容(一切内容、包括注释)

    

        function add(x,y){
            let a=x;
            let b=y;
            console.log(x+y)
        }
        console.log(add.toString())// 输出函数本省
        console.log(Math.ceil.toString())
        //对于那些原生的函数,toString()方法返回function (){[native code]}

函数做用域

  做用域(scope)指的是变量存在的范围。在 ES5 的规范中,JavaScript 只有两种做用域:一种是全局做用域,变量在整个程序中一直存在,全部地方均可以读取;另外一种是函数做用域,变量只在函数内部存在。ES6 又新增了块级做用域。

  全局做用域,在任何地方均可以访问

        let age='18';
        function getAge(){
            console.log(age)
        }
        console.log(age);//18
        getAge();// 18  age全局做用域 函数内部也可访问

  函数做用域,只在函数内部能够访问

  

        function getAge(){
            var age='qzy';
            console.log(age)
        }
        getAge()//qzy
        console.log(age)// age未定义 函数内部定义的变量 只有函数内部能够访问

  块级做用域 es6 新增的 let 声明变量 

        {
            let age='25'
        }
        console.log(age) // age未定义

  函数内部定义的变量,会在该做用域内覆盖同名全局变量。

        let age=18;
        function getAge(){
            let age=25
            console.log(age)
        }
        console.log(age);//18
        getAge();//25

  注意,对于var命令来讲,局部变量只能在函数内部声明,在其余区块中声明,一概都是全局变量。 

        if(true){
            var age=25;
        }
        console.log(age);//25

函数内部的变量提高

  与全局做用域同样,函数做用域内部也会产生“变量提高”现象。var命令声明的变量,无论在什么位置,变量声明都会被提高到函数体的头部。

  

        function ageLevel(age){
            if(age>100){
                var level='高手'
            }
            console.log(level)
        }
        ageLevel(101);//高手
        
        // 等价于
        function ageLevelEqual(age){
            var level;
            if(age>100){
                level='高手'
            }
            console.log(level)
        }
        ageLevelEqual(101);//高手

函数自己的做用域

  函数自己也是一个值,也有本身的做用域它的做用域与变量同样,就是其声明时所在的做用域,与其运行时所在的做用域无关  

        let age=25;
        function getAge(){
            console.log(age)
        }
        
        function setAge(){
            let age=26;
            getAge();
        }
        
        setAge();//25

  getAge函数是在最外层声明的,因此它绑定的做用域是最外层,在函数内部变量age不会影响到其绑定的最外层的值

 

  一样的,函数体内部声明的函数,做用域绑定函数体内部。 

        let age = 25;

        function setAge() {
            let age = 26;

            function getAge() {
                console.log(age)
            }
            // 将声明的函数返回
            return getAge
        }
        setAge()();// 26

  正是这种机制,构成了下文要讲解的“闭包”现象。

函数的参数

  函数运行的时候,有时须要提供外部数据,不一样的外部数据会获得不一样的结果,这种外部数据就叫参数。函数参数不是必需的,JavaScript 容许省略参数。

        function square(x){
            return x*x
        }
        
        console.log(square(2));//4
        console.log(square(3));//9

  函数的参数传递方式分为两种:传值传递、传址传递

    传值传递:函数参数若是是原始类型的值(数值、字符串、布尔值),则采用这种方式,此时在函数体内修改参数值,不会影响到函数外部    

        let age=25;
        function setAge(age){
            age=26;
        }
        console.log(age);//25

    传址传递:若是函数参数是复合类型的值(数组、对象、其余函数),则采用这种方式,传入函数的原始值的地址,此时在函数体内修改参数值,会影响到函数外部(有特殊的)  

        let person={
            age:25
        };
        function setPerson(person){
            person.age=26;
        }
        setPerson(person);
        console.log(person.age);//26

    person 为一个对象 在函数背部修改其中的属性,外面的变量中的值也作相应的改变

 

    注意,若是函数内部修改的,不是参数对象的某个属性,而是替换掉整个参数,这时不会影响到原始值。   

        let person={
            age:25
        };
        function setPerson(person){
            person={
                age:26
            };
        }
        setPerson(person);
        console.log(person.age);//25

    在函数f内部,参数对象person被整个替换成另外一个值。这时不会影响到原始值。这是由于,形式参数person的值原本是是参数person的地址,从新对person赋值致使person指向另外一个地址,保存在原地址上的值固然不受影响。

同名参数

  若是有同名的参数,则取最后出现的那个值。 

        function add(a,a){
            console.log(a)
        }
        add(1);// undefined 形参为两个 同名取最后一个 可是未传递 因此未定义 
        add(1,2);// 2
        add(1,2,3);// 2

arguments 对象

  因为 JavaScript 容许函数有不定数目的参数,因此须要一种机制,能够在函数体内部读取全部参数。这就是arguments对象的由来。 arguments对象包含了函数运行时的全部参数,arguments[0]就是第一个参数,arguments[1]就是第二个参数,以此类推。这个对象只有在函数体内部,才可使用。  

        function add(one){
            let len=arguments.length;// 获取传入的参数个数
            for(let i=0;i<len;i++){
                console.log(arguments[i])
            }
        }
        add(1,2,3,4,5)

  注:arguments对象能够在运行时修改,修改后对应的形参就是修改后的值;可是在开启严格模式下能够修改,可是不会影响到形参, 

        var f = function(a, b) {
            'use strict'; // 开启严格模式
            arguments[0] = 3;
            arguments[1] = 2;
            return a + b;
        }

        console.log(f(1, 1)) // 2

  与数组的关系,须要注意的是,虽然arguments很像数组,但它是一个对象。数组专有的方法(好比slice和forEach),不能在arguments对象上直接使用。 若是要让arguments对象使用数组方法,真正的解决方法是将arguments转为真正的数组。下面是两种经常使用的转换方法:slice方法和逐一填入新数组。

        function add(one){
            let args = Array.prototype.slice.call(arguments);
            args.forEach(list=>{
                console.log(list)
            })
        }
        add(1,2,3,4,5) 
        function add(one) {
            let args = [];
            for (let i = 0; i < arguments.length; i++) {
                args.push(arguments[i]);
            }
            args.forEach((list)=>{
                console.log(list)
            })
        }
        add(1, 2, 3, 4, 5)

  均可以使用forEach 输出因此的参数值

callee 属性

  arguments对象带有一个callee属性,返回它所对应的原函数,能够经过arguments.callee,达到调用函数自身的目的。这个属性在严格模式里面是禁用的,所以不建议使用 

        function add() {
            console.log(arguments.callee==add)
        }
        add();//true

闭包

  闭包(closure)是 JavaScript 语言的一个难点,也是它的特点,不少高级应用都要依靠闭包实现。 理解闭包,首先必须理解变量做用域。前面提到,JavaScript 有两种做用域:全局做用域和函数做用域。函数内部能够直接读取全局变量。

  函数内部能够读取函数外部的变量,可是函数外部是不能读取到函数内部的变量的,这个在上面已经实验过了

  若是出于种种缘由,须要获得函数内的局部变量。正常状况下,这是办不到的,只有经过变通方法才能实现。那就是在函数的内部,再定义一个函数  

        function person(){
            let age=18;
            function getAge(){
                return age
            }
            return getAge
        }
        let p=person();// 接收getAge
        let pAge=p();
        console.log(pAge);// 18

  上面的代码 咱们在person内部定义了一个age字段 ,可是咱们想在外部取不到这个值,这个时候咱们能够在函数内部定义一个函数,这个函数在person 函数内部,是能够取到person内部的值的,而后咱们再把函数暴露出来,就能够获取到这个值了,那此时getAge函数就是闭包,即可以读取其余函数内部变量的函数。因为在 JavaScript 语言中,只有函数内部的子函数才能读取内部变量,所以能够把闭包简单理解成“定义在一个函数内部的函数”,在本质上,闭包就是将函数内部和函数外部链接起来的一座桥梁。

  闭包的最大用处有两个,一个是能够读取函数内部的变量,另外一个就是让这些变量始终保持在内存中,即闭包可使得它诞生环境一直存在。请看下面的例子,闭包使得内部变量记住上一次调用时的运算结果  

        function createIn(count){
            return function (){
                console.log(++count);
            }
        }
        
        let result=createIn(0);
        result();//1
        result()//2
        result()//3

 

  上面代码中,count是函数createIn的内部变量。经过闭包,count的状态被保留了,每一次调用都是在上一次调用的基础上进行计算。从中能够看到,闭包result使得函数createIn的内部环境一直存在。因此,闭包能够看做是函数内部做用域的一个接口。

  为何会这样呢?缘由就在于result始终在内存中,而result的存在依赖于createIn,所以也始终在内存中,不会在调用结束后,被垃圾回收机制回收

  闭包的另外一个用处,是封装对象的私有属性和私有方法。  

        function Person(name) {
            var _age;

            function setAge(n) {
                _age = n;
            }

            function getAge() {
                return _age;
            }

            return {
                name: name,
                getAge: getAge,
                setAge: setAge
            };
        }

        var p1 = Person('张三');
        p1.setAge(25);
        console.log(p1.getAge()) // 25

  上面代码中,函数Person的内部变量_age,经过闭包getAge和setAge,变成了返回对象p1的私有变量。

  注意,外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,因此内存消耗很大。所以不能滥用闭包,不然会形成网页的性能问题

当即调用的函数表达式(IIFE) 

  在 JavaScript 中,圆括号()是一种运算符,跟在函数名以后,表示调用该函数。好比,print()就表示调用print函数。 有时,咱们须要在定义函数以后,当即调用该函数。这时,你不能在函数的定义以后加上圆括号,这会产生语法错误。

        function say(){
            /* code */
        }();// Uncaught SyntaxError: Unexpected token )

  产生这个错误的缘由是,function这个关键字便可以看成语句,也能够看成表达式。

        // 语句
        function f() {}

        // 表达式
        var f = function f() {}

  为了不解析上的歧义,JavaScript 引擎规定,若是function关键字出如今行首,一概解释成语句。那不是的就是函数表达式喽。所以,JavaScript 引擎看到行首是function关键字以后,认为这一段都是函数的定义,不该该以圆括号结尾,因此就报错了。

  下面函数表达式后面跟上圆括号是能够当即执行的。

        // 表达式 直接输出1
        var f = function () {
            console.log(1)
        }()

  解决方法就是不要让function出如今行首,让引擎将其理解成一个表达式。最简单的处理,就是将其放在一个圆括号里面。

        (function (){console.log(1)})();//1
        (function (){console.log(2)}());//2

  上面两种写法都是以圆括号开头,引擎就会认为后面跟的是一个表示式,而不是函数定义语句,因此就避免了错误。这就叫作“当即调用的函数表达式”(Immediately-Invoked Function Expression),简称 IIFE。 注意,上面两种写法最后的分号都是必须的。若是省略分号,遇到连着两个 IIFE,可能就会报错 

        (function (){console.log(1)})()//1
        (function (){console.log(2)}())//2
        // Uncaught TypeError: (intermediate value)(...) is not a function

  上面代码的两行之间没有分号,JavaScript 会将它们连在一块儿解释,将第二行解释为第一行的参数

  推而广之,任何让解释器以表达式来处理函数定义的方法,都能产生一样的效果,只要让引擎认为它是表达式便可。

        let i =function (){console.log(1)}();//1
        
        true && function (){console.log(2)}();//2
        
        +function (){console.log(3)}();//3

  一般状况下,只对匿名函数使用这种“当即执行的函数表达式”。它的目的有两个:一是没必要为函数命名,避免了污染全局变量;二是 IIFE 内部造成了一个单独的做用域,能够封装一些外部没法读取的私有变量。

eval 命令

  eval命令接受一个字符串做为参数,并将这个字符串看成语句执行。若是参数字符串没法看成语句运行,那么就会报错。放在eval中的字符串,应该有独自存在的意义,不能用来与eval之外的命令配合使用。举例来讲,下面的代码将会报错。若是eval的参数不是字符串,那么会原样返回。

        eval('var a=1');
        console.log(a);//1
        console.log(eval(123));//123
     eval('3x');//报错
     eval('return')//报错

  eval没有本身的做用域,都在当前做用域内执行,所以可能会修改当前做用域的变量的值,形成安全问题 

        var a=0;
        eval('var a=1');
        console.log(a);//1

  为了防止这种风险,JavaScript 规定,若是使用严格模式,eval内部声明的变量,不会影响到外部做用域。

       'use strict'
        var a=0;
        eval('var a=1');
        console.log(a);//0

  若是我不声明,直接修改a,能够看到仍是能够修改为功的。

       'use strict'
       var a=0;
       eval('a=1');
       console.log(a);//1

  上面代码中,严格模式下,eval内部仍是改写了外部变量,可见安全风险依然存在。一般状况下,eval最多见的场合是解析 JSON 数据的字符串,不过正确的作法应该是使用原生的JSON.parse方法。

        let str = '{"name": "qzy", "age": 25}';
        let obj = eval('('+str+')');//let obj=JSON.parse(str);
        console.log(obj); // Object {name: "hanzichi", age: 10}

  eval 解析时为何要加圆括号呢 ?

        let obj='{}';
        console.log(eval(obj));//undefined
        console.log(eval('('+obj+')'));//{}

  上面说过eval 会执行输入的字符串,obj是以{开头、}结尾的。那js 引擎就会把这当成代码块,执行里面的语句。

eval 的别名调用

  前面说过eval不利于引擎优化执行速度。更麻烦的是,还有下面这种状况,引擎在静态代码分析的阶段,根本没法分辨执行的是eval。

        "use strict"
        let my=eval;
        my('var a=1');
        console.log(a);//1

  为了保证eval的别名不影响代码优化,JavaScript 的标准规定,凡是使用别名执行eval,eval内部一概是全局做用域。详细的看下一篇对象的讲解 

        var a = 1;

        function f() {
            var a = 2;
            var e = eval;
            e('console.log(a)');
        }

        f() // 1

  就一句话,没事别用eval。

相关文章
相关标签/搜索