函数参数的默认值

作用域

var x = ;

function f(x, y = x) {
console.log(y);
} f() //
let x = ;

function f(y = x) {
let x = ;
console.log(y);
} f() //
var x = ;
function foo(x, y = function() { x = ; }) {
var x = ;
y();
console.log(x);
} foo() //
x //
var x = ;
function foo(x, y = function() { x = ; }) {
x = ;
y();
console.log(x);
} foo() //
x //

参数变量是默认声明的,所以不能用letconst再次声明。

使用参数默认值时,函数不能有同名参数

应用

function throwIfMissing() {
throw new Error('Missing parameter');
} function foo(mustBeProvided = throwIfMissing()) {
return mustBeProvided;
} foo()
// Error: Missing parameter

上面代码的foo函数,如果调用的时候没有参数,就会调用默认值throwIfMissing函数,从而抛出一个错误。

2.rest 参数

ES6 引入 rest 参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。

rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。

function push(array, ...items) {
items.forEach(function(item) {
array.push(item);
console.log(item);
});
} var a = [];

注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。

3.箭头函数

x => x * x

上面的箭头函数相当于:

function (x) {
return x * x;
}

如果参数不是一个,就需要用括号()括起来:

// 两个参数:
(x, y) => x * x + y * y // 无参数:
() => 3.14 // 可变参数:
(x, y, ...rest) => {
var i, sum = x + y;
for (i=; i<rest.length; i++) {
sum += rest[i];
}
return sum;
}

如果要返回一个对象,就要注意,如果是单表达式,这么写的话会报错:

// SyntaxError:
x => { foo: x }

因为和函数体的{ ... }有语法冲突,所以要改为:

// ok:
x => ({ foo: x })

箭头函数看上去是匿名函数的一种简写,但实际上,箭头函数和匿名函数有个明显的区别:箭头函数内部的this是词法作用域,由上下文确定。

回顾前面的例子,由于JavaScript函数对this绑定的错误处理,下面的例子无法得到预期结果:

var obj = {
birth: ,
getAge: function () {
var b = this.birth; //
var fn = function () {
return new Date().getFullYear() - this.birth; // this指向window或undefined
};
return fn();
}

现在,箭头函数完全修复了this的指向,this总是指向词法作用域,也就是外层调用者obj

var obj = {
birth: ,
getAge: function () {
var b = this.birth; //
var fn = () => new Date().getFullYear() - this.birth; // this指向obj对象
return fn();
}
};
obj.getAge(); //

使用注意点

箭头函数有几个使用注意点。

(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。

(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。

(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。

(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

function Timer() {
this.s1 = ;
this.s2 = ;
// 箭头函数
setInterval(() => this.s1++, );
// 普通函数
setInterval(function () {
this.s2++;
}, );
} var timer = new Timer(); setTimeout(() => console.log('s1: ', timer.s1), );
setTimeout(() => console.log('s2: ', timer.s2), );
// s1: 3
// s2: 0

上面代码中,Timer函数内部设置了两个定时器,分别使用了箭头函数和普通函数。前者的this绑定定义时所在的作用域(即Timer函数)

,后者的this指向运行时所在的作用域(即全局对象)。所以,3100毫秒之后,timer.s1被更新了3次,而timer.s2一次都没更新。

另外,由于箭头函数没有自己的this,所以当然也就不能用call()apply()bind()这些方法去改变this的指向。

4.绑定 this

函数绑定运算符是并排的两个冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,

作为上下文环境(即this对象),绑定到右边的函数上面。

foo::bar;
// 等同于
bar.bind(foo); foo::bar(...arguments);
// 等同于
bar.apply(foo, arguments); const hasOwnProperty = Object.prototype.hasOwnProperty;
function hasOwn(obj, key) {
return obj::hasOwnProperty(key);
}

5.尾调用优化

尾调用(Tail Call)是函数式编程的一个重要概念,本身非常简单,一句话就能说清楚,就是指某个函数的最后一步是调用另一个函数。

function f(x) {
if (x > 0) {
return m(x)
}
return n(x);
}

尾递归

function factorial(n) {
if (n === ) return ;
return n * factorial(n - );
} factorial() //

上面代码是一个阶乘函数,计算n的阶乘,最多需要保存n个调用记录,复杂度 O(n) 。如果改写成尾递归,只保留一个调用记录,复杂度 O(1) 。

function factorial(n, total) {
if (n === ) return total;
return factorial(n - , n * total);
} factorial(, ) //

严格模式

ES6 的尾调用优化只在严格模式下开启,正常模式是无效的。

这是因为在正常模式下,函数内部有两个变量,可以跟踪函数的调用栈。

  • func.arguments:返回调用时函数的参数。
  • func.caller:返回调用当前函数的那个函数。

尾调用优化发生时,函数的调用栈会改写,因此上面两个变量就会失真。严格模式禁用这两个变量,所以尾调用模式仅在严格模式下生效

在非严格模式时候我们可以自己模拟

蹦床函数(trampoline)可以将递归执行转为循环执行。

function trampoline(f) {
while (f && f instanceof Function) {
f = f();
}
return f;
}

上面就是蹦床函数的一个实现,它接受一个函数f作为参数。只要f执行后返回一个函数,就继续执行。

注意,这里是返回一个函数,然后执行该函数,而不是函数里面调用函数,这样就避免了递归执行,从而就消除了调用栈过大的问题。

然后,要做的就是将原来的递归函数,改写为每一步返回另一个函数。

function sum(x, y) {
if (y > ) {
return sum.bind(null, x + , y - );
} else {
return x;
}
}

上面代码中,sum函数的每次执行,都会返回自身的另一个版本。

现在,使用蹦床函数执行sum,就不会发生调用栈溢出。

trampoline(sum(, ))
//

蹦床函数并不是真正的尾递归优化,下面的实现才是。

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 > ) {
return sum(x + , y - )
}
else {
return x
}
}); sum(, )
//

ES6之主要知识点(五)函数的更多相关文章

  1. es6的一些知识点

    es6的一些知识点 前言:es6(ECMAscript2015)标准 let.const.var的一些区别 let.const 块级作用域.全局作用域.函数作用域 var 全局作用域.函数作用域 变量 ...

  2. ES6学习笔记<三> 生成器函数与yield

    为什么要把这个内容拿出来单独做一篇学习笔记? 生成器函数比较重要,相对不是很容易理解,单独做一篇笔记详细聊一聊生成器函数. 标题为什么是生成器函数与yield? 生成器函数类似其他服务器端语音中的接口 ...

  3. TS学习随笔(五)->函数

    这篇文章我们来看一下TS里面的函数 函数声明 在 JavaScript 中,有两种常见的定义函数的方式——函数声明(Function Declaration)和函数表达式(Function Expre ...

  4. PostgreSQL学习手册(五) 函数和操作符

    PostgreSQL学习手册(五) 函数和操作符 一.逻辑操作符:    常用的逻辑操作符有:AND.OR和NOT.其语义与其它编程语言中的逻辑操作符完全相同. 二.比较操作符:    下面是Post ...

  5. 石川es6课程---4、箭头函数

    石川es6课程---4.箭头函数 一.总结 一句话总结: 相当于函数的简写,类似python lambda 函数,先了解即可 let show1 = function () { console.log ...

  6. ES6学习总结(五)

    与其说是对象合并,还不如说是JavaScript中对象属性的复制和转移,将多个对象中的属性合并到一个对象中 12345678 var person = { name : 'John', age : 2 ...

  7. 《理解 ES6》阅读整理:函数(Functions)(五)Name Property

    名字属性(The name Property) 在JavaScript中识别函数是有挑战性的,因为你可以使用各种方式来定义一个函数.匿名函数表达式的流行使用导致函数调试困难,在栈信息中难以找出函数名. ...

  8. ES6学习笔记(五)函数的扩展

    1.函数参数的默认值 1.1基本用法 ES6 之前,不能直接为函数的参数指定默认值,只能采用变通的方法. function log(x, y) { y = y || 'World'; console. ...

  9. ES6标准入门 第五章:函数的扩展

    1.函数参数的默认值 (1)基本用法 ES5 中, 不能直接为函数的参数指定默认值.只能采用变通的方法. function log(x, y) { y = y || 'World'; console. ...

随机推荐

  1. double to string 损失精度的问题

    https://blog.csdn.net/magieclarence/article/details/6792511?utm_source=blogxgwz0 类似于这样 double 的精度是一个 ...

  2. MySQL回滚到某一时刻数据的方法

    MySQL回滚到某一时刻数据的方法       对于有归档日志的数据库来说,原理上都具备全库回滚到之前某一时刻的能力.在这方面最好用的Orale数据库,使用Oracle数据库的RMAN工具,可以方便的 ...

  3. mysql双主热备

    先搭建mysql主从模式,主从请参考mysql 主从笔记 然后在在配置文件里添加如下配置 1 log_slave_updates= #双主热备的关键参数.默认情况下从节点从主节点中同步过来的修改事件是 ...

  4. .net下MVC中使用Tuple分页查询数据

    主要是在DAL层写查询分页的代码. 例如DAL层上代码: public Tuple<List<WxBindDto>, int> GetMbersInfo(int start, ...

  5. excel批量删除sql语句

    数据如下表一样 在E1列输入update sql 模板 ="update test set A= '"&A1&"' ,B = '"&B1 ...

  6. 共享商业&技术红利,阿里云SaaS加速器让天下没有难做的SaaS

    9月26日,阿里云在2019杭州云栖大会上发布了SaaS加速器3.0版“一云多端”多个应用平台,展示了阿里云给伙伴带来的多种商业和技术红利.阿里云SaaS加速器将帮助伙伴做好SaaS,卖好SaaS:帮 ...

  7. OBJC依赖库管理利器cocoapods 安装及使用详细图解

    cocoapods: github:https://github.com/CocoaPods/CocoaPods 官方网站:http://www.cocoapods.org/ 1.安装 RubyGem ...

  8. Jenkins 自动部署

      一.安装插件[系统管理 → 插件管理 ] 为了通过SSH上传war包,我们需要安装Publish Over SSH 插件. 二.添加SSH 服务器[系统管理→系统设置] 参数说明: Name:ss ...

  9. day 53 Django基础二之URL路由系统

    Django基础二之URL路由系统   本节目录 一 URL配置 二 正则表达式详解 三 分组命名匹配 四 命名URL(别名)和URL反向解析 五 命名空间模式 一 URL配置 Django 1.11 ...

  10. 微信小程序 tabBar模板

    tabBar导航栏 小程序tabBar,我们可以通过app.json进行配置,可以放置于顶部或者底部,用于不同功能页面的切换,挺好的... 但,,,貌似不能让动态修改tabBar(需求:通过switc ...