(一)与结构赋值的默认值结合使用
//先来看一个例子
function foo({x,y=1}){
console.log(x,y)
}
当函数参数为一个对象,变量才会通过解构赋值生成
function foo(url,{name="", age=88, other={} }){
console.log(age);
}
不能省略第二个参数
function foo(url,{name='猫咪是大王'}={}){
console.log(name);
}
第二个参数可以省略
对函数的参数设置默认值的两种写法区别
function methods1({x=0,y=0}={}){//设置对象解构赋值,函数参数的默认值为空对象
return [x,y];
}
function methods2({x,y}={x:0,y:0}){//没有设置对象解构赋值,函数参数的默认值是一个有具体属性的函数
return [x,y];
}
函数无参数
变量都传值
x传值y不传
x、y都无值
(二)参数默认值的位置
一般定义默认值的参数应该是函数的尾参数,这样比较容易看出省略哪些参数,如果是非尾部的参数设置默认值,则这个参数无法省略
function foo(x=0,y){
return [x,y];
}
function foo(x=1,y=66,z){
return [x,y,z]
}
undefined触发该参数等于默认值,而null没有
(三)函数的length属性
指定了默认值后,函数的length属性将返回没有指定默认值属性参数个数,即length属性将失真
var f=function (a){console.log(a)}
console.log(f.length)
var ff=function (a=0){console.log(a)}
console.log(ff.length)
var fff=function (a,b=0,c=0){console.log(a)}
console.log(fff.length)
特殊情况:如果设置了默认值的参数不是为参数,那么length属性也不再计入后面的参数
var f=function (a=1,b,c){console.log(a)}
console.log(f.length)
(四)作用域
一旦设置参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域,初始化结束后,这个作用域就会消失,但是这种语法在不设置参数默认值值时是不会出现的
var x=1;
function foo(x,y=x){
console.log(y);
}
//调用函数foo时,参数常量形成一个单独的作用域,在这个作用域里面,默认值变量x指向第一个参数x,而不是全局变量x
let x=1;
function foo(y=x){
let x=88;
console.log(y)
}
//函数foo调用时,参数y=x形成一个单独的作用域,在这个作用域里面,变量x本身没有定义,
//所以指向外层的全局变量x,因而函数调用时,函数体内部的局部变量x影响不到默认值变量x
如果此时全局变量x不存在则报错
如果参数默认值为一个函数,该函数的作用域也遵循这个规则
let foo="out";
function oFoo(func=x => foo){
let foo="in";
console.log(func())
}
var x=1;
function foo(x,y = function(){x=2}){
var x=3;
y();
console.log(x)
}
//foo的参数形成一个单独的作用域,这个作用域轴线声明了变量x,然后声明了变量y,
//y的默认值是一个匿名函数,这个匿名函数内部的变量x指向同一个作用域的第一个参数写x,
//而foo内部又声明了一个内部变量x,该变量与第一个参数x由于不是同一个作用域,所以不是
//同一个变量。执行y后,内部变量x和外部全局变量x的值都没变
(五)rest参数
rest参数用于获取函数的多与参数,它搭配的变量是一个数组,该变量将多余的参数放入其中
function add(...values){
let sum=0;
for(var val of values){
sum +=val;
}
return sum;
}
注意:
1.函数的length属性不包括rest参数
2.rest参数只能是最后一个参数,它之后不能再有其他参数,否则会报错
(六)name属性
函数的name属性返回该函数的函数名
function foo(){}
构造函数返回的函数实例的name属性
bind返回的函数,name属性值会加上bound前缀
(七)箭头函数
ES6允许使用 箭头 (=>)来定义函数
var foo = a => a
//上面代码等同于
var foo=function(a){
return a;
}
//如果箭头函数不需要参数或者需要多个参数,可以使用圆括号来代表参数部分
var foo= () => 666
//等同于
var foo = function(){
return 66;
}
var sum = (a1,a2) => a1+a2;
//等同于
var sum=function(a1,a2){
return a1+a2;
}
//如果箭头函数的代码部分多于一条语句,则要使用大括号将其括起来,并使用return语句返回
var sum = (a1,a2) => { return a1+a2;}
//由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号
var getArticles = id => ({ id:id, name:"猫咪是大王"});
//箭头函数也可以与变量的解构赋值结合使用
const full = ({ first, last}) => first + ' ' +last;
//等同于
function full(person) {
return person.first+ ' '+person.last;
}
箭头函数的一个用处就是简化回调函数
[1,2,3].map(x => x * x);
//等同于
[1,2,3].map(function(){
return x * x;
})
var result = values.sort((a,b) => a-b)
//等同于
var result=values.sort(function(a,b){
return a - b;
})
rest 参数与箭头函数结合
const num=(...nums) => nums
const headAndfoot=(head,...foot) =>[head,foot]
注意:
1.函数体内的this对象就是定义时所在的对象,而不是使用时所在对象
this对象的指向是可变的,但是在箭头函数中是固定的
function foo(){
setTimeout(() => {
console.log('id', this.id);
},1000)
}
var id=66;
//如果是普通函数,执行this应该指向全局对象window,输出66,但是箭头函数导致this总是指向函数定义生效时所在的对象,所以输出88
//箭头函数可以让setTimeout里面的this绑定定义时所在的作用域而不是指向运行时所在的作用域
function Timer(){
this.a1=0;
this.a2=0;
// 箭头函数
setInterval(() => this.a1++, 1000);
// 普通函数
setInterval(function(){
this.a2++;
},1000);
}
var timer = new Timer();
setInterval(() => console.log('a1:',timer.a1),3100);
setInterval(() => console.log('a2:',timer.a2),3100);
2.箭头函数不可以当构造函数,不能使用new命令,否则报错
var foo=() => 88;
3.不可以用arguments对象,该对象在函数体不存在,如果要用,可以用rest参数代替
var foo=() => {
console.log(arguments);
return 1;
}
4.不可以用yield命令,因此箭头函数不能用作Generator函数
一个Generator函数与普通function的区别就是函数名前面多了一个星号 * 但是执行时有很大不同,
与yield命令配合,可以实现暂停执行的功能
以下代码中有几个this
function foo(){
return () =>{
return () =>{
return () =>{
console.log('id',this.id);
};
};
};
}
var f =foo.call({id:1});
var f1=f.call({id:2})()();
var f2=f().call({id:3})();
var f3=f()().call({id:4});
上面的代码只有一个this,就是foo上午this,所以f1,f2,f3都输出同样的结果。因为所有的内层函
数都是箭头函数,都没有自己的this,他们的this都是最外层foo函数的this
除了this外,arguments,super,new.target在箭头函数中也是不存在的
箭头函数没有自己的this,当然也不能用call()、apply()、bind()这些方法去改变this的指向
嵌套的箭头函数
//es5语法
function insert(value){
return {into:function (array){
return {after:function(afterValue){
array.splice(array.indexOf(afterValue) +1,0,value);
}};
}};
}
insert(2).into([1,3]).after(1);
//es6语法
let insert=(value) => ({into:(array) => ({after:(afterValue)=>{
array.splice(array.indexOf(afterValue) + 1, 0, value);
return array;
}})})
(八)绑定this
箭头函数可以绑定this对象,大大减少了显式this对象的写法(call、apply、bind),但是并非适用于所有场合
函数绑定运算符是并排的双冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象作为上下文环境(即this对象)绑定到右边的函数上。
foo::bar;
//等同于
bar.bind(foo);
foo::bar(...arguments);
//等同于
bar.apply(foo,arguments);
如果双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定在该对象上
var method=obj::obj.foo;
//等同于
var method = ::obj.foo;
let log=::console.log;
//等同于
var log=console.log.bind(console)
由于双冒号运算符返回的还是原对象,因此可以采用链式写法
import {map, takeWhile,forEach} from "iterlib";
getPlayers()
::map(x=> x.character())
::takeWhile(x=> x.strength>100)
::forEach(x => console.log(x))
let {find,html}=jake;
docment.querySelectorAll("div.myClass")
::find("p")
::html("lalala")
(九)尾调用优化
尾调用是函数式编程的一个重要概念,是指某个函数的最后一步调用另一个函数
function f(x){
return g(x)
}
一些错误的实例
//1.
function f(x){
let y=g(x);
return y;
} //调用函数g之后还有操作
//2.
function f(x){
return g(x)+1;
} //调用函数g之后还有操作
//3.
function f(x){
g(x);
}
//等同于
function f(x){
g(x);
return undefined;
}
尾调用不一定出现在尾部,只要是最后一步操作即可
function f(x){
if(x>0){
return m(x)
}
return n(x);
}//函数m和n都属于尾调用,因为他们都是函数f的最后一步操作
尾调用优化
- 尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,直接用内层函数的调用帧,取代外层函数的调用帧就可以了。
function f(){
let m=1;
let n=2;
return g(m+n);
}
f();
//等同于
function f(){
return g(3);
}
f();
//等同于
g(3)
尾递归
函数自己调用自己称为递归,尾调用自身就称为尾递归
递归非常耗内存,我在菜鸟题中有提过,因为需要同时保存成百上千个调用帧,所以很容易发生栈溢出,但是对于尾递归来说,只存在一个调用帧,所以永远不会发生栈溢出错误
function f(n,f1=1,f2=1){
if(n<=1){
return f2;
}
return f(n-1,f2,f1+f2)
}
(十)严格模式
ES6的尾调用只在严格模式下开启,正常模式下无效,因为在正常模式下函数内部会有两个变量,可以跟踪函数的调用栈
- func.arguments:返回调用时函数的参数
- fun.caller:返回调用当前函数的那个函数
尾调用优化发生时,函数的调用栈会改写,因此上面两个变量就会失真。严格模式禁用这两个变量,所以尾调用仅在严格模式下生效
网友评论