基本概念4-函数

jian1、.函数的概念:

函数是被设计为执行特定任务的代码块,函数会在某代码调用它时被执行。在es6出现前,js的函数语法一直没太大的变化,从而遗留了很多问题和隐晦的做法,导致实现一些基本的功能经常要编写很多代码,es6终于大力度的更新了函数特效,在es5的基础上进行了许多改进,让使用js的编程可以更少的出错,同时也更加灵活

2.函数形参的默认值

js函数有个特别的地方,无论在函数定义中声明了多少形参,都可以传入任意数量的参数,也可以在定义函数时添加针对参数数量的处理逻辑,当已定义的形参无对应的传入参数时为其指定一个默认值。这一节讲解在es6前后添加默认参数的方式,以及一些arguments对象。如何使用表达式作为参数,参数临时死区的重要内容。

2-1-1 在es5中模拟默认参数

案例1:

这个示例中,timeout和callback为可选参数,如果不传入相应的参数会给它们赋予一个默认值

注这里有个小bug,如果我们给timeout传入值0,即使这个值时合法的,也会被视为一个价值,将timeout赋值2000

function makeRequest(url,timeout,callback){

            timeout=timeout || 2000;

            callback= callback || function(){}

            console.log(url,timeout,callback)

 }

 makeRequest('/foo',0, function(){ })//index1.html:14 /foo 2000 ƒ (){}

es5修改方案1

//可以通过typeof检查参数类型的方式来修改这个

function makeRequest(url,timeout,callback){

timeout = (typeof timeout !== 'undefined') ?  timeout : 2000;

callback = (typeof callback !== 'undefined') ? callback :function(){};

console.log(url,timeout,callback)

  }

makeRequest('/foo',0);///foo 0 function

2-1-2 es6中的默认参数值

es6简化了为形参提供默认值的过程,如果没为参数传入值则为其提供一个初始值

//es6修改方案2

 //在这个函数中,只有第一个参数被认为总是要为其传入值,其他两个都有默认值,而且不需要添加任何校验值是否缺失代码

function makeRequest(url,timeout = 2000,callback = function(){}){

         console.log(url,timeout,callback)

}

makeRequest('/foo',0 ,function(){console.log(1)})//foo 0 ƒ (){console.log(1)}

makeRequest('/foo')//使用默认值

makeRequest('/foo',null)//不使用timeout默认值

2-1-3默认值对arguments的影响

  使用默认参数值时,arguments对象的行为与以往的不同,

        es5非严格模式下,函数 命名的参数的变化体现在arguments,

        以下这段代码解释了这种运行机制

        注:1.在非严格模式下,命名参数的变化会更新到arguments

           2.在严格模式下,无论参数如何变化,arguments对象不在随之改变

           3.在es6中,如果一个函数使用默认参数,则无论是否显示定义了严格模式,都会    按着严格模式走

           4.只传入一个参数时,例如 函数mixArgs3,正如你期待的那个样arguments.lengths的值为1,arguments[1]的值为undefind,所有arguments[0]全等,改变first和second并不会影响arguments,可以通过arguments的参数 恢复初始值,无论当前是否时严格迷失


        function mixArgs(first,second){

            console.log(first==arguments[0]);//true

            console.log(second==arguments[1]);//true

            first = 'c';

            second = 'd';

            console.log(first==arguments[0]);//true

            console.log(second==arguments[1]);//true

        }

        mixArgs(2,3);//true,true,true,true

        function mixArgs1(first,second){

            "use strict"

            console.log(first==arguments[0]);//true

            console.log(second==arguments[1]);//true

            first = 'c';

            second = 'd';

            console.log(first==arguments[0]);//false

            console.log(second==arguments[1]);//false

        }

        mixArgs1(3,5);//true,true,false,false

        function mixArgs2(first,second='b'){


            console.log(first==arguments[0]);

            console.log(second==arguments[1]);

            first = 'c';

            second = 'd';

            console.log(first==arguments[0]);

            console.log(second==arguments[1]);

        }

        mixArgs2(3,5); //true,true,false,false

        function mixArgs3(first,second='b'){

        console.log(arguments.length)

        console.log(first==arguments[0]);

        console.log(second==arguments[1]);

        first = 'c';

        second = 'd';

        console.log(first==arguments[0]);

        console.log(second==arguments[1]);

      }

        mixArgs3(3);//1,true,false,false,false

2-1-4默认参数表达式

案例1:关于参数的默认值,最有趣的特性可能是非原始值的传参了  。举个例子可以通过函数执行来得到默认参数的值,就行这样, 这个函数传递一个参数时,不传入第二个参数时,函数体才会调用(案例如下)

 function getValue(){

           return 5;

 }

 function add(first,second = getValue()){

           return first + second;

 }

  console.log(add(1,1));//2

  console.log(add(1));//6

案例2:

 在此函数中,如果只传入一个参数,会调用getValue,value+1,第一次调用add(1)时返回了6,value值改变了,第二次调用add(1)得到的结果是7

      let value=1;

       function getValue(){

           return value++;

       }

       function add(first,second=getValue){

           return first + second

       }

       console.log(add(1,1));//2

       console.log(add(1));//6

       console.log(add(1));//7

案例三

引用参数默认值,如果只传入一个参数,这两个参数相等,所以 add(1)为2

 function add(first,second = first){

            return first + second;

 }

console.log(add(1,1));//2

console.log(add(1));//2

案例四

我们也可以将参数first传入一个函数来获得参数second的值,在这个函数中声明second=getValue(first);只有一分参数时,add(1) 返回的时(1+6)也就是7

 function getValue(value){

             return value+5;

  }

function add(first,second = getValue(first)){

            return first + second;

 }

 console.log(add(1,1));//2

 console.log(add(1));//7

案例五

在引用函数默认值时,只允许引用前面参数的值,即先定义的参数可以访问后定义的参数,add(undefined,1)会抛出错误,因为second比first晚定义,不能当做first的默认值所以会报错

 function add(first=second,second){

            return first + seconds

}

console.log(add(1,1));//2

console.log(add(undefined,1));//抛出错误

3.处理无命名参数

3.1.1 不定参数(rest parameters)的特性

注:1.不定参数的加入,不会影响arguments.length

       2.不定参数后不能有其他的命名参数

       3.不定参数不能用于对象字面量setter之中

      4.如果声明函数定义了不定参数,则函数被调用时,arguments对象包含了所有传入           函数的参数

这个函数模仿了Underscore.js库中的pick()方法,返回一个给定对象的副本,包含了原始对象属性的特定子集。在这个事例中只定义了一个参数, 第一个参数传入的是被赋值的源对象,其他参数时被赋值的属性名关于这个函数我们要注意到这几件事

首先,并不容易发现这个函数可以接受任意数量的参数,当然,可以定义更多的参数,但是怎么的也达不到要求;

其次第一个参数为命名参数,已被使用,当你查找需要拷贝的属性名称时,不得不从索引1遍历arguments对象,而不是从0开始,牢记真正的线索引位置不难,但是这总归使我们需要牵挂的问题

而在es6中,通过引入不定参数(rest parameters)的特性来解决这个问题

案例1

 function pick(object) {

            const result = Object.create(null);

            console.log(result)

            for (let i = 1; i < arguments.length; i++) {

                result[arguments[i]] = object[arguments[i]]

            }

            return result;

            console.log(object)

        }

        let book = {

            title: 'Understanding ECMAScript 6',

            author: 'Nicholas C. Zakas',

            year: 2016

        }

        let bookdata = pick(book, 'author', 'year')

        //修改案例

        function pick1(object, ...args) {

            const result = Object.create(null);

            for (let i = 0; i < args.length; i++) {

                result[arguments[i]] = object[arguments[i]]

            }

            return result;

        }

        let bookdata1 = pick(book, 'author', 'year');

        console.log(bookdata1.author)

案例2

不定参数后,不能有其他的命名参数,此案例程序会爆出语法错误

let book = {

            title: 'Understanding ECMAScript 6',

            author: 'Nicholas C. Zakas',

            year: 2016

        }

       function pick(object,...args,last){

           console.log(args)

       }

pick(book,'title')//Rest parameter must be last formal parameter

案例三

如果声明函数定义了不定参数,则函数被调用时,arguments对象包含了所有传入函数的参数,就像这样

function checkArgs(...args){

           console.log(args.length);//2

           console.log(arguments.length);//2

           console.log(args[0],arguments[0]);//a,a

           console.log(args[1],arguments[1]);//b,b

}

checkArgs('a','b');

4.展开运算符

在所有的新功能中,与不定参数最相似的是展开运算符。不定参数可以让你指定多个独立的参数,并通过整合后的数组来访问;而展开运算符可以让你一个指定的数组,将他们打散后作为独立的参数传入函数,展开运算符...可以将数组分割成独立的参数

let values=[25,50,75,100];

console.log(values,...values)//[25, 50, 75, 100], 25 50 75 100

5.name属性

由于在js中多种定义函数的方式,因而辨别函数就是一项具有挑战性的任务。此外,匿名函数表达式的广泛使用更加大了调试难度,开发者经常要追踪难以解读的栈记录。为了解决这些问题,es6中为所有的函数新增了name属性,es6中所有函数的name属性都有一个合适的值

5-1-1 

function doSomething(){

}

let doAnotherThing=function(){

}

console.log(doSomething.name);//doSomething

console.log(doAnotherThing.name);//doAnotherThing

5-1-2 name属性的特殊情况

尽管确定函数声明和函数表达式的名称很容易,es6还是做了更多的改进来确保所有的函数都有合适的名称,通过Function() 构造创建的函数,其名称将是‘anonymous’

eg

var dosomething=function doSomethingElse(){

        }

        var person={

            get firstName(){

                return 'Nicholas'

            },

            sayName:function(){

                console.log(this.name)

            }

        }

        console.log(dosomething.name);//doSomethingElse

        console.log(person.sayName.name);//sayName

        console.log(person.firstName.name);

        console.log(dosomething.bind().name);//bound doSomethingElse

        console.log((new Function()).name);//anonymous

5.明确函数的多种用途

es5级早期的版本中的函数具有多重功能,可以结合new使用,函数内的this值将指向一个新对象,函数最终会返回这个新对象

function Person(name){

            this.name=name;

        }

        var person = new Person('Nicholas');

        var notAPerson=Person('Nicholas');

        console.log(person);//{"name": "Nicholas"}

        console.log(notAPerson)//undefined

6.在es5中判断函数被调用的方法

在es5中,如果想确定一个函数是否通过new关键字来调用(或者说判断这个函数是否为构造函数所调用),最流行的方法instanceof,调用Person.call()时将变量person传入作为第一个参数,相当于在Person函数里将this设为了person实例。对于函数本身,无法区分是通过Person.call() (或者是Person.applay())还是new 关键字调用得到的Person实例

 function Person(name){

 if(this instanceof Person){

               this.name=name;

               console.log(name)

           }else{

               throw new Error('必须通过new关键字来调用Person')

           }

       }

       var person=new Person('Nicholas');

       //var notAPerson=Person('Nicholas');//抛出错误

       var notAPerson=Person.call(person,'jiaojiao')

7.元属性(Metaproperty) new.target

7-1为了解决函数是否通过new关键字调用的问题,es6引用了new.target原属性,可以通过new.target是否定义安全的检测这个函数是否通过new 关键字调用

function Person(name){

          if(typeof new.target !== 'undefined'){

              this.name=name;

          }else{

            throw new Error('必须通过new关键字来调用Person')

          }

      }

var person=new Person('Nicholas');

  var notAPerson=Person.call(person,'xiaoxin');//抛出错误

7-2 在这段代码中,如果要让程序正确的运行,new.target一定是Person。当调用new AnotherPerson('Nicholas')时,真正调用的是 Person.call(this,name),没有使用new关键字,因此new.target的值时undefined会抛出语法错误

 function Person(name){

          if(typeof new.target !== 'undefined'){

              this.name=name;

          }else{

            throw new Error('必须通过new关键字来调用Person')

          }

      }

      function AnotherPerson(name){

          Person.call(this,name)

      }

      var person=new Person('Nicholas');

      var anotherPerson=new AnotherPerson('Nicholas');//抛出语法错误

8.块级函数

8-1 在严格模式下,在定义函数代码块内,块级函数会被提升至顶部,所以typeof doSomething的值为‘function’,这样佐证来了,即使你在函数定义的位置使用他,还是能返回正确的结果,在严格模式下一旦if代码块结束执行,doSomething函数将不复存在,

'use strict'

    if(true){

        console.log(typeof doSomething)//function

        function doSomething(){

            console.log(1)

        }

    }

    console.log(typeof doSomething)//undefined

8-2 在非严格模式下,这些函数不在提升至代码块的顶部,而是提升至外围函数或全局作用域的顶部

 if(true){

        console.log(typeof doSomething)//function

        function doSomething(){

            console.log(1)

        }

    }

 console.log(typeof doSomething)//function

8-3块级函数的使用场景

块级函数与let函数的表达式类似,一旦执行过程流出代码块,函数定义立即被移除。二者的区别是,在该代码块中,用let 定义的函数表达式不会被提升。

  if(true){

           console.log(typeof doSomething)//会抛出语法错误

           let doSomething=function(){

           }

           doSomething();

       }

9.箭头函数

在es6中,箭头函数时其中最有趣的新特性。顾明思义,箭头函数时一种使用箭头(=>)定义函数的新语法,但是它与传统的js函数有些许不同,主要集中在以下节方面

1.没有this,super , arguments和new.target绑定,箭头函数的this,super,arguments,new.target这些值由外围最近一层非箭头函数决定

2.不能通过new关键字调用 箭头函数Construct方法,所以不能被用作构造函数,如果通过new 关键字调用箭头函数,程序会抛出错误

3.没有原型 由于不能通过new关键字调用箭头函数,因而没有构建原型的需求,所以箭头函数不存在prototype这个属性

4.不可以改变this的绑定 函数内部的this值不可被改变,在函数的生命周期内始终保持一致

5.不支持arguments对象 箭头函数没有arguments绑定,所以不能通过命名参数和不定参数这两种形式访问函数的参数

6.不支持重复命名参数 无论在严格还是非严格模式下,箭头函数都不支持重复命名的参数

9-1 箭头函数的语法

9.1.1当箭头函数只有一个参数时,可以直接写参数名,箭头紧随其后箭头右侧表达式被求值后立即返回

let reflect=value => value; 

等价于

let reflect=function(value){

            return value;

}

9.1.2如果要传入两个或者两个以上的参数,要在参数的两侧添加一对小括号

let sum = (num1,num2) => num1+num2

等价于

let sum =function(num1,num2){

            return num1+num2;

}

9.1.3

//如果函数没有参数,也要在声明的时候写一组没有内容的小括号

let getName = () => 'xiaoming';

let getName=function(){

            return 'xiaoming'

}

9.1.4

//如果你想创建一个新的对象,需要写一对没有内容的花括号

let doNothing=()=>{};

        //相当于

        let doNothing=function(){

 }

9.1.5

如果想函数向外返回一个对象字面量,则需要将字面量包裹在小括号里

let getTempItem = id =>({id:id,name:'Temp'});

let getTempItem = function(id){

            return {id:id,name:'Temp'};

 }

9.1.5创建立即执行表达式

let person = ((name) =>{

            return {

                getName:function(){

                    return name;

                }

            }


        })('xiaoming');

        console.log(person.getName())

9.1.6箭头函数没有this绑定

案例一:this.doSomething会报错 this指的是ducument,而不是pageHandler,所以会报错

        let pageHandler={

            id:'123456',

            init:function(){

                document.addEventListener('click',function(event){

                    console.log(this)

                    this.doSomething(event.type)

                },false)

            },

            doSomething:function(type){

                console.log('Handling'+type+"for"+this.id)

            }

        }

        pageHandler.init();

案例二:es5解决方法 可以用bind()的方法将函数的this帮到到pageHandler

        let pageHandler={

            id:'123456',

            init:function(){

                document.addEventListener('click',(function(event){

                    console.log(this)

                    this.doSomething(event.type)

                }).bind(this),false)

            },

            doSomething:function(type){

                console.log('Handling'+type+"for"+this.id)

            }

        }

        pageHandler.init();

案例三:箭头函数的用法,箭头函数没有this绑定,必须通过查找作用域链来决定其值。如果箭头函数被非箭头函数所包含,则this绑定的是最近一层非箭头函数的this;否则this值会被设置为全局对象

let pageHandler={

            id:'123456',

            init:function(){

                document.addEventListener('click',(event)=>{


                    this.doSomething(event.type)

                },false)

            },

            doSomething:function(type){

                console.log('Handling'+type+"for"+this.id)

            }

        }

        pageHandler.init();

案例四:箭头函数缺少正常函数所拥有的prototype属性,他的设计初衷是“即用即弃”,所以不能退用它来定义新的类型

var myType = ()=>{

       }

       object = new myType();//错误,不可以通过new关键字调用箭头函数

  案例五:箭头函数和数组,箭头函数的语法非常简洁 非常适用于数组的处理

      let result=values.sort((a,b) => a-b);

案例六:箭头函数没有arguments绑定

        var myType = (name) => {

            return arguments[0]

        }

        myType(111);//会报错Uncaught ReferenceError: arguments is not defined at myType

案例七:箭头函数的辨识方法

 var comparator=(a,b)=>a-b;

 console.log(typeof comparator);//function

console.log(comparator instanceof Function)//true

案例八:在箭头函数上可以调用 call() applay 及bind的方法,箭头函数的this值不会受这些方法所影响

       var sum =(num1,num2) => num1+num2;

       console.log(sum.call(null,1,2));//3

       console.log(sum.apply(null,[1,2]));//3

       var boundsum=sum.bind(null,1,2);

       console.log(boundsum());//3




最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,463评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,868评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,213评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,666评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,759评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,725评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,716评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,484评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,928评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,233评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,393评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,073评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,718评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,308评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,538评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,338评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,260评论 2 352

推荐阅读更多精彩内容