ES6 函数的扩展

(一)与结构赋值的默认值结合使用

//先来看一个例子
function foo({x,y=1}){
        console.log(x,y)
}
当函数参数为一个对象,变量才会通过解构赋值生成
function foo(url,{name="", age=88, other={} }){
        console.log(age);
}
不能省略第二个参数
function foo(url,{name='猫咪是大王'}={}){
        console.log(name);
    }
第二个参数可以省略

对函数的参数设置默认值的两种写法区别

function methods1({x=0,y=0}={}){//设置对象解构赋值,函数参数的默认值为空对象
        return [x,y];
    }
    function methods2({x,y}={x:0,y:0}){//没有设置对象解构赋值,函数参数的默认值是一个有具体属性的函数
        return [x,y];
    }
函数无参数

变量都传值

x传值y不传

x、y都无值

(二)参数默认值的位置

一般定义默认值的参数应该是函数的尾参数,这样比较容易看出省略哪些参数,如果是非尾部的参数设置默认值,则这个参数无法省略

function foo(x=0,y){
        return [x,y];
    }
function foo(x=1,y=66,z){
        return [x,y,z]
    }
undefined触发该参数等于默认值,而null没有

(三)函数的length属性

指定了默认值后,函数的length属性将返回没有指定默认值属性参数个数,即length属性将失真

var f=function (a){console.log(a)}
    console.log(f.length)
    var ff=function (a=0){console.log(a)}
    console.log(ff.length)
    var fff=function (a,b=0,c=0){console.log(a)}
    console.log(fff.length)

特殊情况:如果设置了默认值的参数不是为参数,那么length属性也不再计入后面的参数

var f=function (a=1,b,c){console.log(a)}
    console.log(f.length)

(四)作用域

一旦设置参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域,初始化结束后,这个作用域就会消失,但是这种语法在不设置参数默认值值时是不会出现的

var x=1;
    function foo(x,y=x){
        console.log(y);
    }
//调用函数foo时,参数常量形成一个单独的作用域,在这个作用域里面,默认值变量x指向第一个参数x,而不是全局变量x
let x=1;
 function foo(y=x){
        let x=88;
        console.log(y)
    }
//函数foo调用时,参数y=x形成一个单独的作用域,在这个作用域里面,变量x本身没有定义,
//所以指向外层的全局变量x,因而函数调用时,函数体内部的局部变量x影响不到默认值变量x


如果此时全局变量x不存在则报错

如果参数默认值为一个函数,该函数的作用域也遵循这个规则

    let foo="out";
    function oFoo(func=x => foo){
        let foo="in";
        console.log(func())
    }
    var x=1;
    function foo(x,y = function(){x=2}){
        var x=3;
        y();
        console.log(x)
    }
//foo的参数形成一个单独的作用域,这个作用域轴线声明了变量x,然后声明了变量y,
//y的默认值是一个匿名函数,这个匿名函数内部的变量x指向同一个作用域的第一个参数写x,
//而foo内部又声明了一个内部变量x,该变量与第一个参数x由于不是同一个作用域,所以不是
//同一个变量。执行y后,内部变量x和外部全局变量x的值都没变

(五)rest参数

rest参数用于获取函数的多与参数,它搭配的变量是一个数组,该变量将多余的参数放入其中

function add(...values){
        let sum=0;
        for(var val of values){
            sum +=val;
        }
        return sum;
    }

注意:
1.函数的length属性不包括rest参数
2.rest参数只能是最后一个参数,它之后不能再有其他参数,否则会报错

(六)name属性

函数的name属性返回该函数的函数名

function foo(){}

构造函数返回的函数实例的name属性

bind返回的函数,name属性值会加上bound前缀

(七)箭头函数

ES6允许使用 箭头 (=>)来定义函数

var foo = a => a
//上面代码等同于
var foo=function(a){
  return a;
}

//如果箭头函数不需要参数或者需要多个参数,可以使用圆括号来代表参数部分
var foo= () => 666
//等同于
var foo = function(){
  return 66;
}

var sum = (a1,a2) => a1+a2;
//等同于
var sum=function(a1,a2){
  return a1+a2;
}

//如果箭头函数的代码部分多于一条语句,则要使用大括号将其括起来,并使用return语句返回
var sum = (a1,a2) => { return a1+a2;}

//由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号
var getArticles = id => ({ id:id, name:"猫咪是大王"});

//箭头函数也可以与变量的解构赋值结合使用
const full = ({ first, last}) => first + ' ' +last;
//等同于
function full(person) {
  return person.first+ ' '+person.last;
}

箭头函数的一个用处就是简化回调函数

[1,2,3].map(x => x * x);
//等同于
[1,2,3].map(function(){
  return x * x;
})

var result = values.sort((a,b) => a-b)
//等同于
var result=values.sort(function(a,b){
  return a - b;
})

rest 参数与箭头函数结合

const num=(...nums) => nums
const headAndfoot=(head,...foot) =>[head,foot]

注意:
1.函数体内的this对象就是定义时所在的对象,而不是使用时所在对象
this对象的指向是可变的,但是在箭头函数中是固定的

    function foo(){
        setTimeout(() => {
            console.log('id', this.id);
        },1000)
    }
    var id=66;
//如果是普通函数,执行this应该指向全局对象window,输出66,但是箭头函数导致this总是指向函数定义生效时所在的对象,所以输出88
//箭头函数可以让setTimeout里面的this绑定定义时所在的作用域而不是指向运行时所在的作用域
    function Timer(){
        this.a1=0;
        this.a2=0;
        // 箭头函数
        setInterval(() => this.a1++, 1000);
        // 普通函数
        setInterval(function(){
            this.a2++;
        },1000);
    }
    var timer = new Timer();
    setInterval(() => console.log('a1:',timer.a1),3100);
    setInterval(() => console.log('a2:',timer.a2),3100);

2.箭头函数不可以当构造函数,不能使用new命令,否则报错

var foo=() => 88;

3.不可以用arguments对象,该对象在函数体不存在,如果要用,可以用rest参数代替

var foo=() => {
        console.log(arguments);
        return 1;
}

4.不可以用yield命令,因此箭头函数不能用作Generator函数

一个Generator函数与普通function的区别就是函数名前面多了一个星号 * 但是执行时有很大不同,
与yield命令配合,可以实现暂停执行的功能

以下代码中有几个this

    function foo(){
        return () =>{
            return () =>{
                return () =>{
                    console.log('id',this.id);
                };
            };
        };
    }
    var f =foo.call({id:1});

    var f1=f.call({id:2})()();
    var f2=f().call({id:3})();
    var f3=f()().call({id:4});
上面的代码只有一个this,就是foo上午this,所以f1,f2,f3都输出同样的结果。因为所有的内层函
数都是箭头函数,都没有自己的this,他们的this都是最外层foo函数的this

除了this外,arguments,super,new.target在箭头函数中也是不存在的
箭头函数没有自己的this,当然也不能用call()、apply()、bind()这些方法去改变this的指向

嵌套的箭头函数

//es5语法
    function insert(value){
        return {into:function (array){
            return {after:function(afterValue){
                array.splice(array.indexOf(afterValue) +1,0,value);
            }};
        }};
    }
    insert(2).into([1,3]).after(1);

//es6语法
    let insert=(value) => ({into:(array) => ({after:(afterValue)=>{
        array.splice(array.indexOf(afterValue) + 1, 0, value);
        return array;
    }})})

(八)绑定this

箭头函数可以绑定this对象,大大减少了显式this对象的写法(call、apply、bind),但是并非适用于所有场合
函数绑定运算符是并排的双冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象作为上下文环境(即this对象)绑定到右边的函数上。

foo::bar;
//等同于
bar.bind(foo);

foo::bar(...arguments);
//等同于
bar.apply(foo,arguments);

如果双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定在该对象上

var method=obj::obj.foo;
//等同于
var method = ::obj.foo;

let log=::console.log;
//等同于
var log=console.log.bind(console)

由于双冒号运算符返回的还是原对象,因此可以采用链式写法

    import {map, takeWhile,forEach} from "iterlib";
    getPlayers()
    ::map(x=> x.character())
    ::takeWhile(x=> x.strength>100)
    ::forEach(x => console.log(x))
    
    let {find,html}=jake;
    docment.querySelectorAll("div.myClass")
    ::find("p")
    ::html("lalala")

(九)尾调用优化

尾调用是函数式编程的一个重要概念,是指某个函数的最后一步调用另一个函数

function f(x){
  return g(x)
}

一些错误的实例

//1.
function f(x){
  let y=g(x);
  return y;
}  //调用函数g之后还有操作

//2.
function f(x){
        return g(x)+1;
    } //调用函数g之后还有操作

//3.
function f(x){
        g(x);
    }
//等同于
function f(x){
        g(x);
        return undefined;
    }

尾调用不一定出现在尾部,只要是最后一步操作即可

function f(x){
        if(x>0){
            return m(x)
        }
        return n(x);
    }//函数m和n都属于尾调用,因为他们都是函数f的最后一步操作

尾调用优化

  • 尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,直接用内层函数的调用帧,取代外层函数的调用帧就可以了。
    function f(){
        let m=1;
        let n=2;
        return g(m+n);
    }
    f();

    //等同于
    function f(){
        return g(3);
    }
    f();

    //等同于
    g(3)

尾递归
函数自己调用自己称为递归,尾调用自身就称为尾递归
递归非常耗内存,我在菜鸟题中有提过,因为需要同时保存成百上千个调用帧,所以很容易发生栈溢出,但是对于尾递归来说,只存在一个调用帧,所以永远不会发生栈溢出错误

    function f(n,f1=1,f2=1){
        if(n<=1){
            return f2;
        }
        return f(n-1,f2,f1+f2)
    }

(十)严格模式

ES6的尾调用只在严格模式下开启,正常模式下无效,因为在正常模式下函数内部会有两个变量,可以跟踪函数的调用栈

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

推荐阅读更多精彩内容

  • 函数参数的默认值 基本用法 在ES6之前,不能直接为函数的参数指定默认值,只能采用变通的方法。 上面代码检查函数l...
    陈老板_阅读 441评论 0 1
  • 函数参数的默认值 基本用法 在ES6之前,不能直接为函数的参数指定默认值,只能采用变通的方法。 上面代码检查函数l...
    呼呼哥阅读 3,339评论 0 1
  • 1、函数参数默认值 2、rest参数 3、严格模式 4、name属性 5、箭头函数 6、双冒号运算符 7、尾调用优...
    SunshineBrother阅读 187评论 0 0
  • 前面的话 函数是所有编程语言的重要组成部分,在ES6出现前,JS的函数语法一直没有太大的变化,从而遗留了很多问题,...
    CodeMT阅读 805评论 0 1
  • 分答平台,李尚龙的回复分享。 问: 每天坚持简书,英语打卡,跑步,知道自己每天的list.但是找不到小目标和大目标...
    苏小文S阅读 276评论 1 3