Open ihtml5 opened 8 years ago
let oPlus = (x,y=5) => { let x = 1; // 语法错误 return x + y; }
3.当传入的参数是undefined时,如果有默认参数,将会调用默认值,传入参数为null时,不会调用默认值
let oPlus = (x,y='world') => { return x+y; } console.clear(); console.log(oPlus('hello ',undefined)); //'hello world' console.log(oPlus('hello ')); //'hello world' console.log(oPlus('hello ',null)); //'hello null'
4.非尾部的参数设置默认值,实际上这个参数是没法省略的
let oPlus = (x='world',y) => { return x+y; } console.clear(); console.log(oPlus()); // 报错 console.log(oPlus(,)); // 1 undefined console.log(oPlus(null)); // null undefined console.log(oPlus(undefined)); 1 undefined
// es5中默认参数的写法 function oPlus(x,y) { if (typeof y === 'undefined') { y = 'world' } return x + y; } // es6中默认值写法 let oPlus = (x,y='world') => { return x+y; } console.clear(); console.log(oPlus()); // 'undefinedworld' console.log(oPlus('hello ')); //'hello world' console.log(oPlus(undefined)); // 'undefinedworld' console.log(oPlus(null)); // 'nullworld' console.log(oPlus(undefined,undefined)); // 'undefinedworld' console.log(oPlus(null,null)); // 0 console.log(oPlus(undefined,'ok')); // 'undefinedok' console.log(oPlus(null,'ok')); // 'nullok' console.log(oPlus(null,undefined)); // 'nullworld' console.log(oPlus(undefined,null)); // NaN
这两种写法稍微有些不同,个人更倾向于第一种
// {x,y=5} = {} 等同于 {x,y} = {y:5} 双重默认值 let destructFunc = ({x,y=5} = {}) => { console.log(x,y); } console.clear(); destructFunc(); // undefined 5 destructFunc({x:1}) // 1 5 destructFunc({x:2,y:10}) // 2 10 let foo = ({x,y=5}) => { console.log(x,y); } foo(); // 报错 foo({x:1}); // 1 5 foo({}) // undefined 5
(x,y=5) => {} // length: 1 (x=5,y) => {} // length: 0 (x,y=5,z) => {} // length: 1 ({x,y=5}) =》 {} // lenght: 1 ({x,y}) =》 {} // lenght: 1 ({x=1,y}) =》 {} // lenght: 1 ({x,y=5} = {}) =》 {} // lenght: 0
默认参数的作用域规则也符合一般变量的作用域规则,即首先是函数作用域,其次是全局作用域
let x = 1; let foo = (y=x) => { let x = 3; console.log(y); } foo();//1 // 参数默认值是函数 let strfoo = 'bar'; let sfunc = (func = () => strfoo) => { let strfoo = 'ccc'; console.log(func()); } // 传值的变量是全局变量是 let sx = 1; let sxfoo = (x, y= () => { sx=x }) => { console.log('inner',x); x = 4; y(); console.log(sx); } sfunc(); // 'bar' sxfoo(5); // 'inner' 5 \n 4 console.log(sx); // 4
一、执行参数部分
二、执行函数体部分
// rest可以使用一个真正的数组方法 let arrPush = (array,..items) => { items.forEach( item => array.push(item)); } let a = []; arrPush(a,1,2,3,4); // 函数长度 let restFunc = (y,...rest) => {}; let restFunc1 = (...rest) => {} console.log(reestFunc.length,restFunc1.length); // 1 0;
1.扩展运算符可以将数组变成以逗号为分隔的字符串 2.如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。
// 将数组转化为逗号分隔的字符串 let a = [1,2,3] console.log(...a) // 1 2 3 console.log(1,2,3); // 合并数组 let b = [4,5] a.push(...b) console.log(a) // 1 2 3 4 5 // 替代apply console.log(Math.max(...a)); // 3 console.log(Math.max.apply(null,a)); // 3 // 与解构赋值一起使用 const [first,...second] = [1,2,3,4]; console.log(first,second);// 1,[2,3,4] const [ifirst,...isecond] = []; console.log(ifirst,isecond); // undefined []; const [...iisecond,iifirst] // 报错 // 处理函数的返回值,函数的返回值只有一个,如果想多个值,可以返回数组 let foo = () => [1,2,3]; console.log(...foo()); // 将字符串转化为真正的数组 console.log(...'helloworld'); /* 任何Iterator接口的对象,都可以用扩展运算符转为真正的数组。 ...document.getElementsByTagName document.childNodes 上面代码中,arrayLike是一个类似数组的对象,但是没有部署Iterator接口,扩展运算符就会报错。这时,可以改为使用Array.from方法将arrayLike转为真正的数组。 */ let arrayLike = { '0': 'a', '1': 'b', '2': 'c', length: 3 }; // TypeError: Cannot spread non-iterable object. let arr = [...arrayLike]; // Map和Set结构,Generator函数都可以使用扩展运算符 let map = new Map([ [1, 'one'], [2, 'two'], [3, 'three'], ]); let arr = [...map.keys()]; // [1, 2, 3] var go = function*(){ yield 1; yield 2; yield 3; }; [...go()] // [1, 2, 3]
将函数名称标准化了,纠正了es5,匿名函数赋值,函数名称为空的现象
let foo = () => {} // function name is 'foo' var foo = function() {} // function name is '' function foo() {} // function name is foo
const doSomething = (() => { 'use strict' return function(value=43) { return value; } })
函数执行的顺序是先执行参数部分,再执行函数体,如果在函数内部常规声明严格模式,由于参数部分先执行,此时严格模式还没生效,而到了函数体部分,严格模式又生效了,这样参数部分和函数体部分就出现不一致的验证的规则,增加了程序的复杂度,es6标准索性禁止设置严格模式。
// 简单的箭头函数 let foo = () => {} // 作为回调函数被执行 [].map(v => v+1); [].map(v => { return v+1;}) //等同于 [].map(function(v) { return v+1}) // 结合扩展符 let a = [1,2,3]; let oitem = (...numbers) => { let sum = 0; numbers.forEach(n => sum+=n); return sum; } console.log(oitem(...a)); //6 //和变量解构一起使用 let oput = ({first,second}) => `${first}${second}`; console.log(oput({ first: 'cuc', second: 'baidu' })); // 'cucbaidu' // 箭头函数的this指向 console.clear(); window['s2'] = 0; class Timer { constructor() { this.s1 = 0; this.s2 = 0; this.count = 0; setInterval( () => { this.s1++; },1000); setInterval(function() { this.s2++; this.count++; },1000); } } let timer = new Timer(); console.log(timer.s2); // 0 setTimeout(() => console.log('s1',timer.s1,window['s1']),2100); // 2 undefined setTimeout(() => console.log('s2',timer.s2,window['s2']),2100); // 0 2 console.log(window['s2']); // 0 /* 声明了一个类,类里面一个使用箭头函数作为回调函数,一个使用普通函数作为回调函数。 箭头函数绑定定义时的上下文环境也就是Timer,而普通函数绑定的是运行时的上下文环境,即window */
function f(x) { return g(x); } function f(x) { if (x<0) { return m(x); } else { return n(x); } } 只有最后一步调用的是函数才是尾调用
尾调用之所以与其他调用不同,就在于它的特殊的调用位置。我们知道,函数调用会在内存形成一个“调用记录”,又称“调用帧”(call frame),保存调用位置和内部变量等信息。如果在函数A的内部调用函数B,那么在A的调用帧上方,还会形成一个B的调用帧。等到B运行结束,将结果返回到A,B的调用帧才会消失。如果函数B内部还调用函数C,那就还有一个C的调用帧,以此类推。所有的调用帧,就形成一个“调用栈”(call stack)。尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用帧就可以了。
尾递归的实现,往往需要改写递归函数,确保最后一步只调用自身。做到这一点的方法,就是把所有用到的内部变量改写成函数的参数。 CODEPEN
// hack尾递归方式一 function currying(func,n) { return function(m) { return func.apply(this,m,n); } } function tailFactorial(n, total) { if (n === 1) return total; return tailFactorial(n - 1, n * total); } const factorial = currying(tailFactorial, 1); console.log(factorial(5)); // 120 // hack 尾递归方式二 function factorial(n,total = 1) { if (n===1) { return total; } return factorial(n-1,total); } console.log(factorial(5)) // 120 // hack 尾递归方式三 let tco = (f) => { let value = null; let active = false; let accumArr = []; return function() { accumArr.push(arguments); while(accumArr.length) { active = true; value = f.apply(this,accumArr.shift()); } active = false; return value; } } let sum = tco((n,m) => { if (m>0) { return sum(n+1,m-1); } else { return n; } }); console.log(sum(1,1000));
函数参数的默认值
规则
3.当传入的参数是undefined时,如果有默认参数,将会调用默认值,传入参数为null时,不会调用默认值
4.非尾部的参数设置默认值,实际上这个参数是没法省略的
代码示例CODEPEN
与解构赋值默认值一起使用
规则
函数的length
规则
示例代码
作用域
规则
默认参数的作用域规则也符合一般变量的作用域规则,即首先是函数作用域,其次是全局作用域
示例代码
原理
从上面可以看出,sxfoo执行时,分以下几步:
一、执行参数部分
二、执行函数体部分
rest
规则
示例代码
扩展运算符
1.扩展运算符可以将数组变成以逗号为分隔的字符串 2.如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。
代码示例CODEPEN
函数name
规则
将函数名称标准化了,纠正了es5,匿名函数赋值,函数名称为空的现象
代码示例
严格模式
规则
原理
箭头函数
规则
代码示例
尾调用
什么是尾调用
原理
尾调用之所以与其他调用不同,就在于它的特殊的调用位置。我们知道,函数调用会在内存形成一个“调用记录”,又称“调用帧”(call frame),保存调用位置和内部变量等信息。如果在函数A的内部调用函数B,那么在A的调用帧上方,还会形成一个B的调用帧。等到B运行结束,将结果返回到A,B的调用帧才会消失。如果函数B内部还调用函数C,那就还有一个C的调用帧,以此类推。所有的调用帧,就形成一个“调用栈”(call stack)。尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用帧就可以了。
尾递归
尾递归的实现,往往需要改写递归函数,确保最后一步只调用自身。做到这一点的方法,就是把所有用到的内部变量改写成函数的参数。 CODEPEN
相关链接