木灵鱼儿
阅读:1680
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,) {}; //现在这样写还是会报错的
版权申明
本文系作者 @木灵鱼儿 原创发布在木灵鱼儿 - 有梦就能远航站点。未经许可,禁止转载。
相关推荐
异步队列管理器
造这个轮子其实也是没得办法,搜不到合适的轮子用,就只能自己干了。使用场景我们有N个异步任务promise,他们没有顺序关系,谁先触发都无所谓,但是我们只关心一点,如果某一个任务出错,后续就不要运行了,只有全部都success完成,那么才运行成功后的处理方式then。当然,我们肯定不能使用Promise.all运行N个任务,这等于是同步触发了,如果我有2000个任务,难道你也一口气发2000个任务,那这就不现实了,所以这里我们要引入线程概念,一个进程可以有多个线程,那么进程就是管理器,线程就是我们一次可以发多少个请求。线程是可以配置的,我们可以指定触发多少个线程,当1个线程完成后,我们要填...
Iterator 和 for...of 循环
Iterator(遍历器)的概念JavaScript 原有的表示“集合”的数据结构,主要是数组(Array)和对象(Object),ES6 又添加了Map和Set。这样就有了四种数据集合,用户还可以组合使用它们,定义自己的数据结构,比如数组的成员是Map,Map的成员是对象。这样就需要一种统一的接口机制,来处理所有不同的数据结构。遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。Iterator 的作用有三个:一是为各种数据结构,提供一个统一...
Promise
基本了解Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。Promise对象有以下两个特点。(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfil...
Reflect
Reflect也是es6新增的api,他的出现主要是解决以下几点问题:将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上修改Object对象的一些方法的返回结果,比如:Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false。// 老写法 try { Object.defineProperty(target, property, attributes); //...
Proxy 代理
简介proxy用于修改某些操作的默认行为,由于是在语言层做出了修改,所以属于一种“元编程”,也可以理解为代理层,可以对外界的访问进行过滤改写。var proxy = new Proxy(target, handler);new 出Proxy,接收两个参数,一个是需要被代理的对象target,一个是代理的拦截器配置对象。var proxy = new Proxy({}, { get: function(target, propKey) { return 35; } }); proxy.time // 35 proxy.name // 35 proxy.title // 3...
Symbol
es6引入了一个新的数据类型Symbol,用于表示独一无二的值,他是JavaScript第七种数据类型。Symbol值通过Symbol函数生成,但是不需要new,用于解决属性名容易被人复写的问题。目前对象的属性名都是string类型,也就是字符串,现在新增了symbol类型,只要属性名是symbol类型,就是独一无二的,可以保证不会与其他属性名产生冲突。let s = Symbol(); typeof s //"symbol"基本使用Symbol函数接收一个字符串参数,表示对Symbol实例的描述,主要是为了再控制台显示,而且即便两个Symbol使用同一个字符串描...
Object 对象的扩展
属性的简写es6允许对属性进行简写,可以直接使用变量,变量名直接成为了属性名。var a = "hello"; var b = {a}; //等同于 var b = { a:a }属性里的方法也可以简写var a = { b(){...} } //等同于 var a = { b: function(){ ... } }在CommonJS模块输出变量时,也就是node模块导出时,这种简写就显得十分方便,我们直接导致一个对象,对象里面使用简写。var a = {}; var b = {}; module.exports = {a,b}而属性的赋...
Array 数组扩展
扩展运算符console.log(...[1,2,3]); //1,2,3扩展运算符会将后面跟着的数组转换为参数序列,这种效果一般用于函数参数,数组操作里面。扩展运算符后面甚至可以接表达式,但是表达式一定要有数组抛出const arr = [...(x>0?[1]:[]),2];如果是一个空数组使用扩展运算符,则无任何效果const a = []; console.log([...a,1]); //[1]代替数组的apply方法首先要知道apply除了改变this的指向外,第二个参数是一个数组,apply会将这个数组转为序列参数。如:function test(x,y,z){ ...

关于es6 解构报错的一些经验
es6的解构非常好用,可以省去很多代码。为此我测试了一些场景的错误没有对应的keyconst {data} = {datas:"文本"};这种情况下,有值,但是key无法解析,那么就会返回undefined,也就是data输出是个undefined没有值const {data} =null;这种情况就会报错,这个报错会停止js的运行,所以我们需要用try--catch捕获key对应的值为nullconst {data} ={datas:null};这种情况也不会报错,他会把null拿到综合情况const {data,test} ={datas:null};这种情况并不...
Ajax 表单序列化
什么是表单序列化呢?将所有表单的提交通过一个标准化的方法去获取并且提交出去,那就是序列化,也就是说不同的表单,如注册啊,登录啊,修改资料啊,这些东西可以通过一个通用的方法去处理它。那么表单序列化有几个要求:不能发送禁用的表单字段;只发送勾选的复选框和单选按钮;不发送type是reset、submit、file、button以及字段集;多选选择框中的每个选中的值单独一个条目;对于select元素,如果有value值,就指定value作为发送的字段,如果没有,就指定text值;已经将ajax的代码作为单独的一个文件保存,而调用则使用ajax()的方法,之前也做了一个表单提交的方法,在所有条件...