ES5中设置默认值很是不方便, 咱们这样写:javascript
function fun(a){ a = a || 2; console.log(a); } fun(); //2 fun(0); //2 fun(1); //1
以上写法, 若是传入了参数, 但这个参数对应值的布尔型是 false, 就不起做用了。固然你也能够判断 arguments.length
是否为0来避免这个问题, 但每一个函数这样写就太啰嗦了, 尤为参数比较多的时候。在 ES6 中咱们能够直接写在参数表中, 若是实际调用传递了参数, 就用这个传过来的参数, 不然用默认参数。像这样:java
function fun(a=2){ console.log(a); } fun(); //2 fun(0); //0 fun(1); //1
其实函数默认参数这一点最强大的地方在于能够和解构赋值结合使用:编程
//参数传递 function f([x, y, z=4]){ return [x+1, y+2, z+3]; } var [a, b, c] = f([1, 2]); //a=2, b=4, c=7 [[1, 2], [3, 4]].map(([a, b]) => a + b); //返回 [3, 7]
经过上面这个例子不难发现, 不只能够用解构的方法设置初始值, 还能够进行参数传递。固然, 这里也能够是对象形式的解构赋值。若是传入的参数没法解构, 就会报错:segmentfault
function fun1({a=1, b=5, c='A'}){ console.log(c + (a + b)); } fun1({}); //'A6' fun1(); //TypeError, 由于没法解构 //但这样设计函数对使用函数的码农很不友好 //因此, 技巧: function fun2({a=1, b=5, c='A'}={}){ console.log(c + (a + b)); } fun2(); //'A6'
注意, 其实还有一种方法, 但不如这个好, 咱们比较以下:数组
//fun1 比 fun2 好, 不会产生之外的 undefined function fun1({a=1, b=5, c='A'}={}){ console.log(c + (a + b)); } function fun2({a, b, c}={a: 1, b: 5, c: 'A'}){ console.log(c + (a + b)); } //传了参数, 但没传所有参数就会出问题 fun1({a: 8}); //'A13' fun2({a: 8}); //NaN
不过这里强烈建议, 将具备默认值的参数排在参数列表的后面。不然调用时依然须要传参:闭包
function f1(a=1, b){ console.log(a + b); } function f2(a, b=1){ console.log(a + b); } f2(2); //3 f1(, 2); //报错 f1(undefined, 2); //3, 注意这里不能用 null 触发默认值
这里咱们还须要单独讨论一下默认参数对 arguments 的影响:app
function foo(a = 1){ console.log(a, arguments[0]); } foo(); //1 undefined foo(undefined); //1 undefined foo(2); //2 2 foo(null); //null null
很明显,默认参数并不能加到 arguments 中。函数式编程
这个属性ES6 以前就是存在的, 记得length表示预计传入的形参个数, 也就是没有默认值的形参个数:函数
(function(a){}).length; //1 (function(a = 5){}).length; //0 (function(a, b, c=5){}).length; //2 (function(...args){}).length; //0, rest参数也不计入 length
rest 参数形式为 ...变量名
, 它会将对应的所有实际传递的变量放入数组中, 能够用它来替代 arguments:优化
function f(...val){ console.log(val.join()); } f(1, 2); //[1, 2] f(1, 2, 3, 4); //[1, 2, 3, 4] function g(a, ...val){ console.log(val.join()); } g(1, 2); //[2] g(1, 2, 3, 4); //[2, 3, 4]
不然这个函数 g 你的这样定义函数, 比较麻烦:
function g(a){ console.log([].slice.call(arguments, 1).join()); }
这里须要注意2点:
建议:
扩展运算符相似 rest运算符的逆运算, 用 ...
表示, 放在一个(类)数组前, 将该数组展开成独立的元素序列:
console.log(1, ...[2, 3, 4], 5); //输出1, 2, 3, 4, 5
扩展运算符的用处不少:
[...document.querySelectorAll('li')]; //[<li>, <li>, <li>];
function push(arr, ...val){ return arr.push(...val); //调用函数时, 将数组变为序列 }
var arr = [1, 2, 3]; var max = Math.max(...arr); //3 var arr2 = [4, 5, 6]; arr.push(...arr2); //[1, 2, 3, 4, 5, 6] new Date(...[2013, 1, 1]); //ri Feb 01 2013 00: 00: 00 GMT+0800 (CST)
var more = [4, 5]; var arr = [1, 2, 3, ...more]; //[1, 2, 3, 4, 5] var a1 = [1, 2]; var a2 = [3, 4]; var a3 = [5, 6]; var a = [...a1, ...a2, ...a3]; //[1, 2, 3, 4, 5, 6]
var a = [1, 2, 3, 4, 5]; var [a1, ...more] = a; //a1 = 1, more = [2, 3, 4, 5] //注意, 扩展运算符必须放在解构赋值的结尾, 不然报错
var str = "hello"; var alpha = [...str]; //alpha = ['h', 'e', 'l', 'l', 'o'] [...'x\uD83D\uDE80y'].length; //3, 正确处理32位 unicode 字符
建议:使用扩展运算符(...)拷贝数组。
name 属性返回函数的名字, 对于匿名函数返回空字符串。不过对于表达式法定义的函数, ES5 和 ES6有差异:
var fun = function(){} fun.name; //ES5: "", ES6: "fun" (function(){}).name; //""
对于有2个名字的函数, 返回后者, ES5 和 ES6没有差异:
var fun = function baz(){} fun.name; //baz
对于 Function 构造函数获得的函数, 返回 anonymous
:
new Function("fun").name; //"anonymous" new Function().name; //"anonymous" (new Function).name; //"anonymous"
对于 bind 返回的函数, 加上 bound
前缀
function f(){} f.bind({}).name; //"bound f" (function(){}).bind({}).name; //"bound " (new Function).bind({}).name; //"bound anonymous"
箭头函数的形式以下:
var fun = (参数列表) => {函数体};
若是只有一个参数(且不指定默认值), 参数列表的圆括号能够省略; (若是没有参数, 圆括号不能省略)
若是只有一个 return 语句, 那么函数体的花括号也能够省略, 同时省略 return 关键字。
var fun = value => value + 1; //等同于 var fun = function(value){ return value + 1; }
var fun = () => 5; //等同于 var fun = function(){ return 5; }
若是箭头函数的参数或返回值有对象, 应该用 ()
括起来:
var fun = n => ({name: n}); var fun = ({num1=1, num2=3}={}) => num1 + num2;
看完以前的部分, 箭头函数应该不陌生了:
var warp = (...val) => val; var arr1 = warp(2, 1, 3); //[2, 1, 3] var arr2 = arr1.map(x => x * x); //[4, 1, 9] arr2.sort((a, b) => a - b); //[1, 4, 9]
使用箭头函数应注意如下几点:
举几个箭头函数的实例:
实例1: 实现功能如: insert(2).into([1, 3]).after(1)
或insert(2).into([1, 3]).before(3)
这样的函数:
var insert = value => ({ into: arr => ({ before: val => { arr.splice(arr.indexOf(val), 0, value); return arr; }, after: val => { arr.splice(arr.indexOf(val) + 1, 0, value); return arr; } }) }); console.log(insert(2).into([1, 3]).after(1)); console.log(insert(2).into([1, 3]).before(3));
实例2: 构建一个管道(前一个函数的输出是后一个函数的输入):
var pipe = (...funcs) => (init_val) => funcs.reduce((a, b) => b(a), init_val); //实现 2 的 (3+2) 次方 var plus = a => a + 2; pipe(plus, Math.pow.bind(null, 2))(3); //32
实例3: 实现 λ 演算
//fix = λf.(λx.f(λv.x(x)(v)))(λx.f(λv.x(x)(v))) var fix = f => (x => f(v => x(x)(v)))(x => f(v => x(x)(v)));
建议:箭头函数取代 Function.prototype.bind,不该再用 self / _this / that 绑定 this。其次,简单的、不会复用的函数,建议采用箭头函数。若是函数体较为复杂,行数较多,仍是应该采用传统的函数写法。
这里须要强调,如下状况不能使用箭头函数:
let calculator = { array: [1, 2, 3], sum: () => { return this.array.reduce((result, item) => result + item); //这里的 this 成了 window } }; calculator.sum(); //"TypeError: Cannot read property 'reduce' of undefined"
function Cat(name) { this.name = name; } Cat.prototype.sayCatName = () => { return this.name; //和上一个问题同样:这里的 this 成了 window }; let cat = new Cat('Mew'); cat.sayCatName(); //undefined
const button = document.getElementById('myButton'); button.addEventListener('click', () => { this.innerHTML = 'Clicked button'; //这里的 this 本应该是 button, 但不幸的成了 window });
let Message = (text) => { this.text = text; }; let helloMessage = new Message('Hello World!'); //TypeError: Message is not a constructor
let multiply = (a, b) => b === undefined ? b => a * b : a * b; //这个太难读了,太费时间 let double = multiply(2); double(3); //6 multiply(2, 3); //6
ES7 中提出了函数绑定运算, 免去咱们使用 call, bind, apply 的各类不方便, 形式以下:
objName::funcName
如下几组语句两两等同
var newFunc = obj::func; //至关于 var newFunc = func.bind(obj); var result = obj::func(...arguments); //至关于 var result = func.apply(obj, arguments);
若是 ::
左边的对象本来就是右边方法中的 this, 左边能够省略
var fun = obj::obj.func; //至关于 var fun = ::obj.func; //至关于 var fun = obj.func.bind(obj);
::
运算返回的仍是对象, 能够进行链式调用:
$('.my-class')::find('p')::text("new text"); //至关于 $('.my-class').find('p').text("new text");
尾调用是函数式编程的概念, 指在函数最后调用另外一个函数。
//是尾调用 function a(){ return g(); } function b(p){ if(p>0){ return m(); } return n(); } function c(){ return c(); } //如下不是尾调用 function d(){ var b1 = g(); return b1; } function e(){ g(); } function f(){ return g() + 1; }
尾调用的一个显著特色就是, 咱们能够将函数尾部调用的函数放在该函数外面(后面), 而不改变程序实现结果。这样能够减小函数调用栈的开销。
这样的优化在 ES6 的严格模式中被强制实现了, 咱们须要作的仅仅是在使用时候利用好这个优化特性, 好比下面这个阶乘函数:
function factorial(n){ if(n <= 1) return 1; return n * factorial(n - 1); } factorial(5); //120
这个函数计算 n 的阶乘, 就要在内存保留 n 个函数调用记录, 空间复杂度 O(n), 若是 n 很大可能会溢出。因此进行优化以下:
"use strict"; function factorial(n, result = 1){ if(n <= 1) return result; return factorial(n - 1, n * result); } factorial(5); //120
固然也可使用柯里化:
var factorial = (function factor(result, n){ if(n <= 1) return result; return factor(n * result, n - 1); }).bind(null, 1); factorial(5); //120
这个仅仅是一个提案: 为了更好地进行版本控制, 在函数参数尾部加一个逗号, 表示该函很多天后会被修改, 便于版本控制器跟踪。目前并未实现。
这里仅仅讨论 ES6 中的变量做用域。除了 let 和 const 定义的的变量具备块级做用域之外, var
和 function
依旧遵照词法做用域, 词法做用域能够参考博主的另外一篇文章javascript函数、做用域链与闭包
首先看一个例子:
var x = 1; function f(x, y=x){ console.log(y); } f(2); //2
这个例子输出了2, 由于 y 在初始化的时候, 函数内部的 x 已经定义并完成赋值了, 因此, y = x
中的 x
已是函数的局部变量 x 了, 而不是全局的 x。固然, 若是局部 x 变量在 y 声明以后声明就没问题了。
var x = 1; function f(y=x){ let x = 2 console.log(y); } f(); //1
那若是函数的默认参数是函数呢?烧脑的要来了:
var foo = "outer"; function f(x){ return foo; } function fun(foo, func = f){ console.log(func()); } fun("inner"); //"outer"
若是基础好, 那就根本谈不上不烧脑。由于, 函数中的做用域取决于函数定义的地方, 函数中的 this 取决于函数调用的方式。(敲黑板)
但若是这样写, 就是 inner 了, 由于func默认函数定义的时候 fun内的 foo 已经存在了。
var foo = "outer"; function fun(foo, func = function(x){ return foo; }){ console.log(func()); } fun("inner"); //"inner"
技巧: 利用默认值保证必需的参数被传入, 而减小对参数存在性的验证:
function throwErr(){ throw new Error("Missing Parameter"); } function fun(necessary = throwErr()){ //...若是参数necessary没有收到就使用参数, 从而执行函数抛出错误 } //固然也能够这样表示一个参数是可选的 function fun(optional = undefined){ //... }
箭头函数的做用域和定义时的上下文一致, 但能够经过调用方式改变:
window && (window.name = "global") || (global.name = "global"); var o = { name: 'obj-o', foo: function (){ setTimeout(() => {console.log(this.name); }, 500); } } var p = { name: 'obj-p', foo: function (){ setTimeout(function(){console.log(this.name); }, 1000); } } o.foo(); //"obj-o" p.foo(); //"global" var temp = { name: 'obj-temp' } o.foo.bind(temp)(); //"obj-temp" o.foo.call(temp); //"obj-temp" o.foo.apply(temp); //"obj-temp" p.foo.bind(temp)(); //"global" p.foo.call(temp); //"global" p.foo.apply(temp); //"global"