“我报名参加金石计划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)
}
注意:
- 参数变量是默认声明的,在函数体中不能用const、let再次声明,但是可以用var来声明。
- 在使用参数默认值时,函数不能具有同名参数,会报错;但是不使用参数默认值时可以具有同名参数。
- 具有默认值的参数最好放在最后面,这样有默认值的参数就可以被省略。
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
注意:
- 有默认值的参数需要放在最后,否则只会返回有默认值参数前无默认值参数的个数。
- 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.基本使用
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.注意
- 箭头函数没有自己的
this。(这点很重要) - 不可对箭头函数使用
new命令。 - 不能使用
arguments,只能使用rest参数。 - 箭头函数不能用作
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))
正常运行