ES6 逐点突破系列 -- 函数的扩展

}

f() // 1

var x = 1;

function foo(x, y = function() { x = 2; }) {

var x = 3;

y();

console.log(x);

}

foo() // 3

x // 1

上面代码中,函数foo的参数形成一个单独作用域。这个作用域里面,首先声明了变量x,然后声明了变量y,y的默认值是一个匿名函数。这个匿名函数内部的变量x,指向同一个作用域的第一个参数x。函数foo内部又声明了一个内部变量x,该变量与第一个参数x由于不是同一个作用域,所以不是同一个变量,因此执行y后,内部变量x和外部全局变量x的值都没变。

如果将var x = 3的var去除,函数foo的内部变量x就指向第一个参数x,与匿名函数内部的x是一致的,所以最后输出的就是2,而外层的全局变量x依然不受影响。

var x = 1;

function foo(x, y = function() { x = 2; }) {

x = 3;

y();

console.log(x);

}

foo() // 2

x // 1

P.S. 下面代码中,参数x = x形成一个单独作用域。实际执行的是let x = x,由于暂时性死区的原因,这行代码会报错"x 未定义"。

var x = 1;

function foo(x = x) {

// ...

}

foo() // ReferenceError: x is not defined

<>2. reset参数


arguments对象不是数组,而是一个类似数组的对象。所以为了使用数组的方法,必须使用Array.prototype.slice.call先将其转为数组。rest 参数就不存在这个问题,它就是一个真正的数组,数组特有的方法都可以使用。

函数的length属性,不包括 rest 参数。

(function(a) {}).length // 1

(function(...a) {}).length // 0

(function(a, ...b) {}).length // 1

P.S. rest 参数之后不能再有其他参数(即只能是最后一个参数)

<>3. 严格模式


ES2016 做了一点修改,规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式

<>4. name属性


函数的name属性,返回该函数的函数名。

需要注意的是,ES6 对这个属性的行为做出了一些修改。如果将一个匿名函数赋值给一个变量,ES5 的name属性,会返回空字符串,而 ES6 的name属性会返回实际的函数名。

var f = function () {};

// ES5

f.name // ""

// ES6

f.name // "f"

bind返回的函数,name属性值会加上bound前缀。

function foo() {};

foo.bind({}).name // "bound foo"

(function(){}).bind({}).name // "bound "

<>5. 箭头函数


注意点

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

function foo() {

return () => {

return () => {

return () => {

console.log('id:', this.id);

};

};

};

}

var f = foo.call({id: 1});

var t1 = f.call({id: 2})()(); // id: 1

var t2 = f().call({id: 3})(); // id: 1

var t3 = f()().call({id: 4}); // id: 1

上面代码之中,只有一个this,就是函数foo的this,所以t1、t2、t3都输出同样的结果。因为所有的内层函数都是箭头函数,都没有自己的this,它们的this其实都是最外层foo函数的this。

除了this,以下三个变量在箭头函数之中也是不存在的,指向外层函数的对应变量:arguments、super、new.target。

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

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

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

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

<>6. 尾调用优化


尾调用(Tail Call)是函数式编程的一个重要概念,指某个函数的最后一步是调用另一个函数。

function f(x){

return g(x);

}

函数调用会在内存形成一个"调用记录",又称"调用帧"(call frame),保存调用位置和内部变量等信息。如果在函数A的内部调用函数B,那么在A的调用帧上方,还会形成一个B的调用帧。等到B运行结束,将结果返回到A,B的调用帧才会消失。如果函数B内部还调用函数C,那就还有一个C的调用帧,以此类推。所有的调用帧,就形成一个"调用栈"(call stack)。

尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用帧就可以了。

尾递归

function Fibonacci (n) {

if ( n <= 1 ) {return 1};

return Fibonacci(n - 1) + Fibonacci(n - 2);

}

Fibonacci(10) // 89

Fibonacci(100) // 超时

Fibonacci(500) // 超时

function Fibonacci2 (n , ac1 = 1 , ac2 = 1) {

if( n <= 1 ) {return ac2};

return Fibonacci2 (n - 1, ac2, ac1 + ac2);

}

Fibonacci2(100) // 573147844013817200000

Fibonacci2(1000) // 7.0330367711422765e+208

Fibonacci2(10000) // Infinity

改写递归函数

两个方法可以解决这个问题。方法一是在尾递归函数之外,再提供一个正常形式的函数。

function tailFactorial(n, total) {

if (n === 1) return total;

return tailFactorial(n - 1, n * total);

}

function factorial(n) {

return tailFactorial(n, 1);

}

factorial(5) // 120

函数柯里化

function currying(fn, n) {

return function (m) {

return fn.call(this, m, n);

};

}

function tailFactorial(n, total) {

if (n === 1) return total;

return tailFactorial(n - 1, n * total);

}

const factorial = currying(tailFactorial, 1);

factorial(5) // 120

第二种方法就简单多了,就是采用 ES6 的函数默认值。

function factorial(n, total = 1) {

if (n === 1) return total;

return factorial(n - 1, n * total);

}

factorial(5) // 120

严格模式

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

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

func.arguments:返回调用时函数的参数。

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

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

function restricted() {

'use strict';

restricted.caller; // 报错

restricted.arguments; // 报错

}

restricted();

尾递归优化的实现

function tco(f) {

var value;

var active = false;

var accumulated = [];

return function accumulator() {

accumulated.push(arguments);

总结

阿里十分注重你对源码的理解,对你所学,所用东西的理解,对项目的理解。

相关推荐
u01040583610 分钟前
构建可扩展的Java Web应用架构
java·前端·架构
swimxu1 小时前
npm 淘宝镜像证书过期,错误信息 Could not retrieve https://npm.taobao.org/mirrors/node/latest
前端·npm·node.js
qq_332394201 小时前
pnpm的坑
前端·vue·pnpm
雾岛听风来1 小时前
前端开发 如何高效落地 Design Token
前端
不如吃茶去1 小时前
一文搞懂React Hooks闭包问题
前端·javascript·react.js
alwn1 小时前
新知识get,vue3是如何实现在style中使用响应式变量?
前端
来之梦1 小时前
uniapp中 uni.previewImage用法
前端·javascript·uni-app
野猪佩奇0071 小时前
uni-app使用ucharts地图,自定义Tooltip鼠标悬浮显示内容并且根据@getIndex点击事件获取点击的地区下标和地区名
前端·javascript·vue.js·uni-app·echarts·ucharts
生活、追梦者2 小时前
html+css+JavaScript 实现两个输入框的反转动画
javascript·css·html
2401_857026232 小时前
拖动未来:WebKit 完美融合拖放API的交互艺术
前端·交互·webkit