深入理解ES6 - 函数

深入理解ES6 - 函数

ECMAScript6 在 ECMAScript5 的基础上对函数进行了大量的改进,使之更灵活。

函数形参的默认值

js 函数无论在定义中申明了多少形参,都可以传入任意数量的参数。ES6 以前,可以用如下方式为参数赋默认值:

function(a,b){
    a = a || 20; //风险:当 a 传入0时,即使合法,a 也会被赋予20
    b = (typeof b !=="undefined") ? b :20; //更安全,但需要额外编码
}

ES6 中的默认参数值

ES6 中设置默认参数值比较简单。注意:在 ES6 中,参数的默认值并不会改变 arguements 对象中的值,这与 ES5 严格模式是一致的。

function demo(url, timeout = 6000, parameter = {a:1}) {
    //调用时如果传入三个参数,则不使用默认值
}

//在已指定默认值的参数后可以继续声明无默认值的参数
function demo2(url, timeout = 6000, parameter) {
    //只有第二个参数不传值,或传入 undefined 时,才会用默认参数
}
demo2('index.action'); //使用 timeout 的默认值,parameter 为 undefined
demo2('index.action', undefined, {a:1}); //使用 timeout 的默认值
demo2('index.action', null, {a:1}); //不使用 timeout 的默认值

默认参数表达式

可以通过函数执行得到默认参数的值。

function add(a, b = getValue()) {
  return a+ b
}
add(1); //假设 getValue() 返回5,则 add(1) 结果是6。

上面的代码,js 引擎在解析函数申明时,不会执行 getValue() 方法,只有当调用 add() 函数且不传第二个参数时才会调用。正因为默认参数是在函数调用时求值,故可以使用先定义的参数作为后定义的参数的默认值,反之不成立。

//也可以将 a 作为参数运算后赋值给 b,如 b = getValue(a)。
function add(a, b = a) {
  return a + b;
}
add(1,2); //3
add(1);   //2

function add2(a = b, b) {
  return a + b;
}
add2(1,2); //3,不会报错,因为 a 传入了合法值,没有运算 a = b
add2(undefined,2); //报错,因为运算 a = b 时,b 未定义。(b 在“临时死区”中)

函数参数有自己的作用域和临时死区,与函数体的作用域是各自独立的,也就是说,参数的默认值不可以访问函数体内申明的变量。

无名参数

JavaScript 提供 arguments 对象来检查函数的所有参数,所以不是每一用到的参数都需要定义。ES6中的不定参数和展开运算符就是很好的解决方案。

不定参数

申明函数时,参数前加三个点,表明这是个不定参数。该参数是一个数组,包含这自它之后传入的所有参数。关于不定参数的注意事项:

  •  函数最多只能有一个不定参数,且要放在参数末尾;
  •  不定参数不能用于对象字面量 setter 中,因为 setter 有且只能有一个参数;
  •  不定参数不会影响函数的 length 属性,该属性统计的是函数命名参数的数量,不包含不定参数;
  •  无论是否使用不定参数,arguments 对象总是包含所有传入的参数。
function Demo3(name, ...args){
    console.log(args.length); //2
    console.log(arguments.length); //3
    console.log(args[0],arguments[0]); //a demo
    console.log(args[1],arguments[1]); //b a
}

Demo3('demo','a','b');
console.log(Demo3.length); //1,仅包含 name,不包含不定参数

展开运算符

展开运算符与不定参数很相似,它可以简化使用数组给参数传参的编码。它可以通过指定一个数组,然后将数组的每个元素作为独立的参数传入函数。

let a = 10, b = 20;
console.log(Math.max(a,b));	//20

//如何获取数组中的最大元素呢
let arr = [10, 30, 6];

//ES5 的可以通过 apply() 实现
console.log(Math.max.apply(Math, arr));	//30
		
//ES6 的展开运算符可以简化上述操作
console.log(Math.max(...arr));	//30 等价于 Math.max(10, 30, 6)
console.log(Math.max(...arr, 666));	//666 限制最小返回值为666

函数的其它属性

name属性

ES6 为所有函数新增了 name 属性用于标记函数,且每个函数的 name 属性都有一个合适的值。不过请注意:函数 name 属性的值不一定引用同名变量,它只是协助调试用的额外信息,所以不能使用 name 属性的值来获取对函数的引用。

function doThing(){};
var doSomethimg = function(){};
var doAnother = function doAnotherElse(){};

console.log(doThing.name);	//doThing 对应声明时的函数名
console.log(doSomethimg.name);	//doSomethimg 匿名函数表达式对应被赋值的变量名称
console.log(doAnother.name);	//doAnotherElse 函数申明时的函数名比被赋值的变量名权重高

var something = function(){};
console.log(something.bind().name);	//bound something 通过 bind() 创建的函数带有前缀 bound
console.log((new Function()).name);	//anonymous 通过 Function 构造函数创建的函数,带有前缀 anonymous

元属性 new.target

元属性是指非对象的属性。
ES6 中引入了 new.target 这个元属性,可以判断函数是否是通过 new 关键字调用。通过 new 关键字调用函数时,new.target 被赋值为 new 操作符的目标,否则 new.target 的值为 undefined。注意:在函数外使用 new.target 是一个语法错误。

function Demo(name){
    if(typeof new.target !== 'undefined'){
	    this.name = name;
	}else{
	    throw new Error('请通过 new 关键字来调用 Demo');
	}
}

var demo1 = new Demo('demo1');
Demo.call(demo1, 'demo2'); //报错

可以将 typeof new.target !== 'undefined' 替换成 typeof new.target !== Demo 来检查函数是否被某个特定的构造函数所调用。

块级函数

JavaScript 早期版本中,在代码块中声明块级函数是一个语法错误,ES5 严格模式中会给出错误提示,但是 ES6 对其提供了支持。在严格模式下,块级函数会被提升至作用域顶部,出作用域则函数被销毁。非严格模式下,则会提升至外围函数或全局作用域的顶部。

"use strict"
if(true){
	console.log(typeof demo1); //function 块级函数提升至作用域顶部,出if块则销毁
	console.log(typeof demo2); //报错,let 定义的函数表达式不会被提升
	function demo1(){}
	let demo2 = function(){};
}
console.log(typeof demo1); //严格模式下:undefined,非严格模式下:function
console.log(typeof demo2); //undefined

箭头函数

箭头函数(=>)是 ES6 中重要的新特性。与传统函数有些区别,区别的原因主要是为了减少错误来源、简化代码,使引擎可以更好的执行箭头函数。

  • 箭头函数中的 this、super、arguments、new.target 由外围最近一层非箭头函数决定。
  • 不能通过 new 关键字调用,没有 construct 方法,没有 prototype 属性。
  • 函数 this 值不可被改变。
  • 没有 arguments 对象,所以只能通过命名参数或不定参数访问函数的参数。但无论函数在哪个上下文执行,箭头函数都可以访问外围函数的 arguments 对象。

箭头函数语法多变,但所有变种都由参数、箭头、函数体组成。

//一个参数的情况
let demo = arg => arg;
//实际上等价于
let demo = function(arg){
    return arg;
};

//两个参数的情况
let demo1 = (arg1, arg2) => arg1 + arg2;
//没有参数的情况
let demo2 = () => 'hello';
//若箭头函数想要从函数体内向外返回一个对象字面量,就必须将该字面量包裹在圆括号内
//将对象字面量包裹在括号内,标示了括号内是一个字面量而不是函数体
var demo3 = id => ({ id: id, name: "demo3" });
//如果有多行代码,需要显示返回
var demo4 = () => {
    //todo sth.
    return true;
};

//如下情况用箭头函数就很方便
let arr = [22,3,11,66];
let arr1 = arr.sort(function(a, b){ return a- b; }); //[3,11,22,66]
let arr2 = arr.sort((a, b) => a-b); //[3,11,22,66]

箭头函数设计的初衷是“即用即弃”,个人理解与匿名函数类似。

尾调调用优化

尾调用指的是函数作为最后一条语句被返回。ES6 之前尾调用的实现与其他函数类似:创建一个新的栈帧,将其推入调用栈帧来表示函数调用。ES6 缩减了严格模式下调用栈的大小(非严格模式不受影响),如果满足以下条件,尾调用不再创建新的栈帧,而是清除并重用当前栈帧:

  • 尾调用不访问当前栈的变量。
  • 在函数内部,尾调用是最后一条语句。
  • 尾调用的结果作为函数值返

     

"use strict"
//引擎自动优化
function demo1(){
    return doSomething(); 
}

//无法优化,因为没有返回
function demo2(){
    doSomething(); 
}

//无法优化,因为尾调函数又参与了运算,不是作为结果返回
function demo3(){
    return 1 + doSomething(); 
}

//无法优化,调用不在尾部
function demo4(){
    let result = doSomething(); 
    return result; 
}

//无法优化,该函数是个闭包,即引用当前函数变量
function demo5(){
    let num = 1; 
    let func = () => num;
    return func(); 
}

尾调优化的主要应用场景是递归函数。

转载于:https://my.oschina.net/u/2563695/blog/1593445

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值