我转过几个弯 绕过那个小雨楼
拿着蒲扇摆着衣衫渡着紧箍咒
不问天涯不停留 喝过几壶酒
不过年少白头道义放胸口
倘若明天之后 遥看前尘剑封侯
似那天上神仙无所求
朝朝暮暮君如梦醒十分不为何理由
是真是假是惶恐是无休
路过这风雨花满楼 片刻都不停留
我本这书生进京赶考留下许多愁
你问有没有时候 我叹这天道默悠悠
能否与我一醉方休
又过了几个弯 算尽天量道莫慌
踏这田园闻这芳草香
跌跌撞撞仗剑天涯折煞不枉无笔良
是梦是幻是温柔是家乡
路过这风雨花满楼 片刻都不停留
我本这书生进京赶考留下许多愁
你问有没有时候 我叹这天道默悠悠
能否与我一醉方休
路过这风雨花满楼 片刻都不停留
我本这书生进京赶考留下许多愁
你问有没有时候 我叹这天道默悠悠
能否与我一醉方休
谁能与我一醉方休
Function 函数
函数默认参数
es5无法设置默认参数,只能通过函数内部先进行判断,如果没有参数则设置一个值。
function test(x,y){
x = x || "hello ";
y = y || "word";
console.log(x+y);
}
test(); //hello word
这样写的话会有一个问题,就是如果我就要参数为空,就会出现问题
test("","test"); // hello test
所以我们还需要加一个判断
function test(x,y){
x = typeof x === "undefined" ? "hello ": x;
y = typeof y === "undefined" ? "word": y;
console.log(x+y);
}
test("","test"); //test
但这样太麻烦了,所以es6提供了函数函数的默认值写法
function test(x="hello ",y="word"){
console.log(x+y);
}
test(); //hello word
test("","test"); //test
注意事项
- 默认参数不能在函数里面二次声明,否则报错
- 使用了默认参数,所有参数名必须唯一不能重复,否则报错
- 默认参数是惰性的,他会随着每次调用该函数,重新计算,而不是一开始就计算好
function test(x="hello "){
const x = "test"; //报错
}
function test(x="hello",x){ //报错
}
let x = 99;
function test(p=x+1){ //每次都会重新获取x的值
console.log(p);
}
test(); //100
x = 100;
test(); //101
默认参数与解构混合使用
在函数参数那,我们也可以使用解构来直接获取某些数据,并且可以和默认参数一起使用。
function test({x,y=5}){
console.log(x,y);
}
test({}); //undefined,5
test({x:1}); //1,5
test({x:1,y:2}); //1,2
test(); //报错,无法解构
这里我们是对解构的某个值进行默认参数的,事实上我们还可以对整个参数进行默认值设置
function test({x,y=5}={}){
console.log(x,y);
}
text(); //undefined,5
这就形成了双重默认值,并且我们还需要理解下他是什么样的流程。
首先我们并没有传参数,所以这里先使用了第一个默认参数{}
,一个空的对象,然后才会进行结构,运行{x,y=5}
这部分。
你不能理解为没有参数,解构这部分也直接被{}
空对象替换了,可以理解为,替换的只是解构的外层包裹,内层还是一样的,不会消失不见。
这里写两种写法:
fucntion m1({x=0,y=0} = {}) {
console.log(x,y);
}
fucntion m2({x,y} = {x:0,y:0}) {
console.log(x,y);
}
m1和m2的区别就是,第一个初始默认值不同。
m1的第一个默认值是空对象{}
,再解构的时,如果没有对应的值使用默认值。
m2的第一个默认值是带有参数的{x:0,y:0}
,然后解构时直接解构第一个默认值。
m1和m2两个函数在没有传参的情况下,结果是相同的。
但是当我们传入一个空对象时,m2由于解构不到参数,会使一个undefined
m1({}); //0,0
m2({}); //undefined,undefined
因为m1的默认值是在解构的时候设置,而m2是之前就预设,此时我们传入的了对象参数,m2的默认值就拿不到了。
那么我们传入一个不相干的参数结果也会是一样的
m1({z:1}); //0,0
m2({z:1}); //undefined,undefined
默认值参数的位置
通常情况下,定义了默认值得参数,一般是可以省略不传的,所以要写在最后面,因为在函数里面,最后那个是可以不写,也不会影响到其他参数的。
function test(x,y,z=1){
//z可以进行省略
}
function test(x,z=1,y){
//z无法进行省略,因为位置上会影响到后面的参数
}
触发默认参数
当传入undefined的时候才会触发默认值,而null和空字符串不会触发
function test(x=1){
console.log(x);
}
test(""); //""
test(null); //null
test(undefined) //1
函数length
使用了默认参数后,函数的length返回的是没有设置默认参数的参数lenght。
(function test(x,y){}).length //2
(function test(x,y=1){}).length //1
(function test(...arr){}).length //0
(function test(x,y=1,z){}).length //1
(function test(x=1,y,z){}).length //0
通过arguments
获取lenght并不会收到影响。
length的含义是这个函数预期传入的参数个数,当我们设置了默认值,那么这个预期就不一定了,所以不计入数量。
当我们把默认值设置在前面时,后面即便不是默认值也不会计入数量了。
作用域
一旦设置了默认值后,函数的参数在声明初始化的时候,会形成一个独立的作用域,等初始化结束,这个作用域就会消失,这种语法行为在不设置默认参数时,是不会出现的。
var x = 1;
function too(x,y = function(){ x=2 }) {
var x = 3;
y();
console.log(x);
};
too(); //3
console.log(x); //1
使用了默认参数,所以(x,y = function(){ x=2 })
参数会有独立的作用域。
其中y里面的x指向的是他同级作用域下的x,也就是第一个参数x。
而too函数里面,我们又申明了一个x并赋值为3,当y运行的时候,他的这个x指向的是他作用域下的第一个参数x,x赋值为2,而too里面的var x和第一个参数x不在同一个作用域。
所以y是无法修改到var x变量的,他只能修改第一个参数x。
所以:
function too(x,y = function(){ x=2 }) {
y();
console.log(x);
};
too(); //2
而我们运行too函数时,输出3,是因为,var x在第一个参数x后面声明的,就近原则,var x是最快获取到的,所以输出3,这里也可以了解下变量提升,而y这个函数,他本身也只能修改第一个参数,后面的var x他改不了,所以,最终结果是个3
如果我们不在too里面声明一个x,而是直接用参数x,那么结果又不同了
function too(x,y = function(){ x=2 }) {
x = 3;
y();
console.log(x);
};
too(); //2
这里就会输出为2,因为x是同一个x,同一个作用域。
应用
我们可以将默认值来指定某一个值是必填的,如果省略则进行报错
function err(name){
throw new Error(name+"参数为必填参数");
}
function test(x=err("x")){
return x;
}
test(); //报错:x参数为必填参数
另外我们可以给默认参数设置为undefined表示这个参数是可以省略的。
function test(option = undefined){
}
rest
rest用于处理特别多的参数的时候,以前我们用arguments对象获取所有参数,现在我们可以使用rest方法更加便捷的获取。
function add(...values) {
let sum = 0;
for(var val of values) {
sum += val;
}
return sum;
}
add(1,2,3); //6
通过...values
的方式,values成为了一个保存所有参数的数组,然后我们遍历这个数组,进行求和,然后返回结果。
values
这个变量名是可以自定义的。
例子:数字排序
//es5写法
function sortNumber() {
return Array.prototype.slice.call(arguments).sort();
}
//es6
const sortNumber = (...numbers) => numbers.sort();
例子:改写push
function push(arr,...items) {
items.forEach(funtion(item){
arr.push(item);
});
return arr;
};
push([],1,2,3,4); //[1,2,3,4]
注意
rest必须写在最后面,作为最后一个参数,否则报错,并且使用了rest后,lenght也无法检测到参数数量
function test(x,...vals,y){ ... } //报错
(function test(x,...vals){}).length; //1
(function test(...vals){}).length; //0
严格模式
es5开始,函数内部可以设定为严格模式
function test(){
'use strict'
...
}
es6做了点修改,如果函数使用了默认参数,解构赋值,rest扩展运算符,那么函数内部就不能显示设定为严格模式,否则就会报错。
但是可以通过一个方式规避:
全局严格模式
'use strict'
function test(){
...
}
函数包裹
const test = (function(){
'use strict'
return function(x = 1) {
...
}
})()
name
函数的name属性返回的该函数的函数名,但是在es5中,如果我们创建一个变量,并给这个变量赋值一个匿名函数,然后我们再来获取其name,得到的是空,而es6返回的是这个变量名
var f = function(){};
f.name; //es5 ""
f.name; //es6 "f";
如果被赋值这个这函数本身就是一个具名函数,那么name最终返回的是这个具名函数的名字。
var f = function test(){};
f.name; "test";
如果是Function构造函数返回的函数示例,返回的是:anonymous
(new Function).name //'anonymous'
如果是使用bind方法生成的新函数,会带有bound
前缀
function test() {};
test.bind({}).name; //'bound test'
(function(){}).bind({}).name; //bound
箭头函数
es6允许使用箭头来定义函数,箭头函数大大的简化的函数的书写,在一些回调上显得更加简洁明了。
var f = ()=>{};
//等同于
function f() {};
箭头函数还有一些省略写法:
var f1 = val => {}; //单参数
var f2 = (val1,val2) => {}; //多参数和无参数用圆括号
var f3 = val => val; //省略return
//等同于
funciton(val) {
return val;
}
var f4 => ({key:val}); //如果要return一个对象,可以用圆括号包裹用以区分
箭头函数本身也是函数,所以参数上,我们也能使用解构,rest扩展运算符,默认值,这里就不重复说明了。
箭头函数使得表达更加简洁,以前要写好几行的代码,现在可以缩短为一行
const isEven = n => n % 2 == 0;
const square = n => n * n;
注意事项
- this对象是函数定义时所在的对象,而不是使用时所在的对象
- 不可以当做构造函数使用,所以不能使用new
- 不可以使用arguments对象,可以使用rest参数代替
- 不可以使用yield命令,因此箭头函数不能作为generator函数
this对象是函数定义时所在的对象,而不是使用时所在的对象
function foo(){
setTimeout(()=>{
console.log('id:',this.id);
},100);
}
var id = 1;
foo.call({id:2}); //id:2
在foo函数内,有一个定时器,定时器的回调是一个箭头函数,按照第一条的说明,this对象是定义时所在的对象,也就是说this指向的是foo。
然后我们给全局window上定义了一个id变量,值为1;
然后我们将foo的this指向了一个对象,于是我们得到的是id:2
如果是普通函数,定时器里面的函数this指向的是window,按道理就要输出id:1
箭头函数可以让this指向固定化,这种特性非常有利于封装回调。
简单来讲,箭头函数没有自己的this,他的this永远是外层代码块的this,正因为没有this,所以不能用作构造函数。
像arguments、super、new.target这三个变量,在箭头函数里面也是没有的,但是可以获取到外层代码块的变量。
function foo(){
setTimeout(()=>{
console.log("arguments:",arguments);
},100)
}
foo(1,2,3,4); //arguments:,[1,2,3,4]
另外由于箭头函数没有自己的this,所以我们也不能改变箭头函数自己的this指向,所以call、apply、bind这些改变this指向的语法也无法使用。
绑定this
箭头函数绑定this对象,大大的减少了显示绑定this对象的写法,如:call、apply、bind,但是箭头函数并非适合所有场合,所以es7提出了“函数绑定”运算符,用来取代这三个方法,但是目前还是提案,不过bable转码器已经支持了。
函数绑定运算法是两个冒号:::
,双冒号左边是一个对象,右边是一个函数,改运算符会将左边的对象做为this的上下文环境。
foo::bar
//等同于
bar.bind(foo);
foo::bar(...arr)
//等同于
bar.apply(foo,arguments)
如果左边的为空,右边是一个对象的方法,那么就表示右边这个对象作为上下文。
::obj.foo;
//等同于
obj::obj.foo;
这样的话,双冒号返回的的还是原对象,类似于返回了一个this一样,可以拿来做链式写法。
var s = {
test:()=>{
console.log(1);
}
}
s.test()::test()::test()
这个是我自己想的,可能会有问题,也找不到地方测试。
尾部调用
指的是函数最后一步调用另一个函数,这个最后一步可以不是写在最后面,但他必须是函数最后一步。且无其他运算,如果你最后一步调用一个函数再操作,那就算不得。
function f(x) { //不算
let y = g(x);
return y;
};
function f(x) { //不算
return g(x) + 1;
};
function f(x) { //不算
g(x);
};
这里第一个和第二个不算,第三个的话,我们要明白他其实还要一步
function f(x) { //不算
g(x);
return undefined;
};
所以第三种也不算,他不是最后一步。
尾调用优化
尾调用之所以和其他不同,是因为js的函数运行,如果a函数里面运行了b函数,那么内存中a的运行区域上还有b函数,b如果有c,那么就会继续堆,从而形成一个调用栈,只有当c运行完了,c才会从内存中消失,然后运行b,所以,如果相关联的太多了,就会出现栈溢出。
尾部调用的话,说明他的这段函数已经不需要继续保存了,所以a会先消失,而不是要等后面所有的b、c函数运行完毕再消失。
如果是递归的话,a函数无限调用自身,传统的话太多就会直接溢出了,而使用尾部调用优化,那么就等于a运行完毕了,再开一个a,原来的消失,相对来说性能会更好。
如何实现优化呢
- 尾部调用的函数内部不要有外部的变量,可以用参数的形式传,但是不要直接在里面调用
- es6的尾调用优化只有在函数开启严格模式下才有效
为什么只能在严格模式下呢,是因为正常函数下,有两个变量可以跟踪函数的调用栈,导致原来的无法内存区域依旧保持存在。
- func.arguments 返回调用时函数的参数
- func.caller 返回调用当前函数的那个函数
而严格模式会禁用这两个变量,所以尾部优化只有在严格模式下有效
function test(){
"use strict"
test.caller; //报错
test.arguments; //报错
}
test();
尾调用优化实现
尾部优化只有在严格模式下有效,那么其他环境怎么办呢?
答案就是自己动手了,从原来上讲,优化的原理就是减少栈的堆叠,或者说是调用。
这是一个正常的递归函数:
function sum(x,y){
if(y>0){
return sum(x+1,y-1);
}else {
return x;
}
}
sum(1,100000); //报错,溢出
优化1:蹦床函数
//蹦床
function trampoline(f){
while(f && f instanceof Function) {
f = f();
}else {
return f;
}
}
//重复函数
function sum(x,y){
if(y>0){
return sum.bind(nyll,x+1,y-1);
}else {
return x;
}
}
trampoline(sum(1,100000)); //100001
蹦床函数并不是真正的尾部优化
尾部优化
function tco(f) {
var value;
var active = false;
var accumulated = [];
return function accumulator() {
accumulated.push(arguments);
if(!active) {
active = true;
while(accumulated.length){
value = f.apply(this,accumulated.shift());
}
active = false;
return value;
}
};
}
var sum = tco(function(x,y){
if( y > 0){
return sum(x+1,y-1);
}else {
return x;
}
});
sum(1,100000); //100001
函数参数的尾逗号
es7有一个提案,就是函数的参数,支持最后一个参数后面带一个逗号。这样可以使函数参数和数组参数的尾逗号规则保持一致。
function test(x,y,) {}; //现在这样写还是会报错的
评论(0)