【ES6】学习笔记:函数扩展

162 阅读7分钟

“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情

博文类型: 学习向
背景: 学习ES6语法并做学习笔记
目标: 跟着ES6教程实操,并记录实操结果

函数参数的默认值

1.基本用法

ES6之前是不能直接为函数的参数指定默认值,只能这样

function a(x,y){
    if(typeof y==='undefined'){
        y='123'
    }
}

ES6使设置函数参数默认值更加的简洁:

function a(x,y=2){
    consloe.log(y)
}

注意:

  1. 参数变量是默认声明的,在函数体中不能用const、let再次声明,但是可以用var来声明。
  2. 在使用参数默认值时,函数不能具有同名参数,会报错;但是不使用参数默认值时可以具有同名参数。
  3. 具有默认值的参数最好放在最后面,这样有默认值的参数就可以被省略。

2.解构赋值与函数默认值

对传入的参数进行结构赋值,结构赋值是有默认值的,但是传入的参数没有默认值,导致不传参数会报错。

function fn({x,y=1}){
    console.log(x,y)
}
fn({})//undefined 1
fn({x:1})//1 1
fn({x:1,y:2})//1 2
fn()//报错

为了解决不传参会报错的问题,需要给参数一个默认值,如下:

function fn({x,y=1}={x:0,y:0}){
    console.log(x,y)
    } 
fn({})//undefined 1
fn({x:1})//1 1 
fn({x:1,y:2})//1 2 
fn()//0 0

当不传参的时候,默认值会变为解构赋值的对象。

3.函数的length属性

返回没有默认值参数的数量。

function  a(x,y) {
    
}
console.log(a.length);//2

注意:

  1. 有默认值的参数需要放在最后,否则只会返回有默认值参数前无默认值参数的个数。
  2. rest参数无法获取无默认值参数的数量,只会返回0。

4.函数的name属性

返回函数的名称。
这个属性ES5和ES6都有,但是两者有些不同。

//ES5和ES6
function a(){
}
console.log(a.name);//a
console.log(a.bind({}).name);//bound a
console.log((function(){}).bind({}).name);//bound
console.log((new Function).name)//anonymous

let b=function(){}
//ES5
console.log(b.name);//''
//ES6
console.log(b.name);//b

let c=function baz(){}
//ES5
console.log(b.name);//baz
//ES6
console.log(b.name);//baz

4.作用域

设置了默认值,函数进行声明初始化时,参数会形成一个单独的作用域。在声明结束时,这个作用域也会消失。没有设置默认值的话不会形成作用域。

let x=2
function a(x,y=x) {
    console.log(y);
}
a()//undefined
a(2)//2

rest参数

ES6引入了rest参数(形如...参数名),用于获取传入的所有参数并返回一个数组。

function a(...value) {
    console.log(value);
}
function b(){
    console.log(arguments);
}
a(2)//[2]
a(1,2,3)//[1, 2, 3]
b(1,2,3)//Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]

注意:
1.rest参数返回的是一个数组,而以前用的arguments返回的是一个类似数组的对象,arguments不能直接调用数组的方法,需要先转换成数组才行,而rest参数可以直接使用数组的方法。
2.rest参数只能是最后一个参数,否则会报错。

严格模式

从ES5开始函数内部是可以被设置为严格模式的。但是ES6做了一点修改:函数参数使用了默认值解构赋值扩展运算符那么函数内部就不能显式设定为严格模式。
也就是说以下函数都将会报错:

function a(x,y=1){
    'use strict'
}//报错
function b({x,y}){
    'use strict'
}//报错
function c(...values){
    'use strict'
}//报错

为什么要这样规定呢?

  1. 函数体内部的严格模式,同时适用于函数体以及函数参数。但是函数执行时,是先执行函数参数再执行函数体,在没有执行函数体时是无法知晓函数参数是否是严格模式。
  2. 这样规定简单方便,不用再去改解析代码的先后顺序了。

那实在是想用严格模式怎么办?

有两种方式:

  1. 设置全局性的严格模式。
  2. 把函数放在一个立即执行函数里,并给立即执行的函数严格模式。

箭头函数

箭头函数:由箭头(=>)定义的函数。

1.基本使用

1.函数体没有大括号,且只有一个语句,则返回这个语句

var fn=v=>v;
等价于
function fn(v){
    return v
}

2.单个传参参数可不带括号,多个传参或不传参则需带上括号

var fn=()=>5;
var fn2=(v1,v2)=>v1+v2

如果有多个语句,则函数体需要用大括号括起来。

var fn2=(v1,v2)=>{let a=v1+v2;let b=v1*v2}

3.如果想返回一个对象,函数体需要用括号括起来

let fn = ()=>({a:1})

4.一行语句且不需要返回

let fun=()=>void doSomething();

5.箭头函数与解构赋值

let fn=({first,second})=>first+second;

2.注意

  1. 箭头函数没有自己的this。(这点很重要)
  2. 不可对箭头函数使用new命令。
  3. 不能使用arguments,只能使用rest参数。
  4. 箭头函数不能用作Generator函数。

3.箭头函数的this指向

普通函数: 内部的this指向是函数运行时所在的对象 (什么叫运行时所在的对象?就是这个对象可以点调用这个函数例如obj.fn(),那么这个函数就指向这个对象。像是函数里定义并调用一个新函数,这个新函数并不能被点调用,这个新函数的this就是指向global) 。this指向可以变。

箭头函数: 内部的this指向是定义时上层作用域中的this (什么叫做上层作用域,就是把这个箭头函数包起来的大括号)。this指向不能变。

this.x=1
global.x=3
let fn=()=>{console.log(this.x);}
let fn2=function(){console.log(this.x);}
let fn3=function(){
	let a=function(){
		console.log(this.x);
	}
	let b=()=>{console.log(this.x);}
	a()
	b()
}
let obj={
	x:4,
	fn:function(){console.log(this.x);},
	fn2:()=>{console.log(this.x)}
}
fn()//1
fn2()//3
fn3()//3 3
fn.call({x:2})//1
fn2.call({x:2})//2
fn3.call({x:2})//3 2
obj.fn()//4
obj.fn2()//1

fn: 指向全局this。
fn2: 指向全局global。 fn3: a指向全局global,b指向fn3的this,在没手动改变this指向时,fn3指向global,改变后指向新的对象。
obj: fn指向obj,因为obj可以点调fn。fn2指向全局this,因为对象的大括号不是一个作用域。

尾调用优化

1.什么是尾调用?

一个函数在函数最后一步操作返回另一个函数。重点是最后一步而不是最后那一行代码,是返回一个函数,而不是调一个函数且不返回。

function a(){
	let x=1
	let y=2
	let a=(x,y)=>x+y
	return a(x,y)
}

2.什么是尾调优化?

函数调用的时候会形成一个调用记录,也叫做调用帧,用于记录函数调用的位置以及变量信息等。调用函数A,形成一条A的调用帧,函数A中调用了函数B,那么在A调用帧的上方生成一个B的调用帧。只有函数B调用结束,B调用帧才会消失,如果B函数内部又调用了另一个函数则一次类推。所有的调用帧就形成了“调用栈”。
尾调用由于函数是最后一步操作,所以不用保存外层函数的调用帧,因为外层函数的调用位置、内部变量等信息在内层函数中没有用到,所以可以直接用内层函数的调用帧来取代外层函数的调用帧。
总之,尾调优化就是只保留内层函数的调用帧,节省内存。
注意: 只有不再用到外层函数的内部变量,内部函数的调用帧才会替换外层函数的调用帧,否则无法进行“尾调优化”

3.尾递归

尾调用自身就是尾递归。
没用尾递归:

fn=function(n){
	if(n<=1)return 1
	return fn(n-2)+fn(n-1)
}
console.log(fn(100))

程序直接卡死 用了尾递归:

fn=function(n,x=1,y=1){
	if(n<=1)return y
	return fn(n-1,y,x+y)
}
console.log(fn(100))

正常运行