ES6学习之<函数扩展>

函数扩展

// 函数参数默认值
function fn(x) { x = x || 'hello' } // ES5写法
function fn(x = 'hello') {} 				// ES6写法, 如果参数默认值是表达式形式, 则是惰性求值的
// 解构赋值默认值结合使用
function foo({x = 2, y = 5}) { console.log(x, y) }
foo({x: 1}) // x: 1; y: 5
foo() 			// error
function foo({x = 2, y = 5} = {}) { console.log(x, y) }
foo()				// x: 2; y: 5
// 参数默认值的位置
function f(x = 1, y) { return [x, y] }
f(, 1)					// error
f(undefined, 1)	// [1, 1] x: 1; y: 1
// 函数的length属性, 指该函数预期传入的参数个数,
// 某个参数指定默认值以后. 预期传入的参数个数就不包括这个参数及其后面的参数
(function (a) {}).length							// 1
(function (a, b, c = 5) {}).length		// 2
(function (a, b = 1, c, d) {}).length	// 1
// 函数作用域
// 情况1
let x = 1
function f(y = x) {		// 指向外层的变量
    let x = 2					// 局部变量影响不到默认变量
    console.log(y)
}
f()		// 1
// 情况2
var x = 1
function foo(x, y = function() { x = 2 }) {
    var x = 3
    y()
    console.log(x)
}
foo()		// 3
x				// 1
// 情况3
var x = 1
function foo(x, y = function() { x = 2 }) {
    x = 3
    y()
    console.log(x)
}
foo()		// 2
x 			// 1
// 应用1, 指定某一个参数不得省略
function throwIfMissing() {
    throw new Error('Missing parameter')
}
function foo(param = throwIfMissing()) {
    return param
}
foo() 	// Error: Missing parameter
// 应用2, 将参数默认值设为undefined
function foo(param = undefined) {}

// rest参数, rest参数必须是最后一个参数, 否则报错, 即(a, b, ..., ...rest) “a, b, ...”可有可无
// ES5 arguments变量写法
function sortNumbers() { return Array.prototype.slice.call(arguments).sort() }
// ES6 rest参数写法
const sortNumbers = (...numbers) => numbers.sort()
// 函数length属性不包括rest参数
(function(...args) {}).length					// 0
(function(a, ...args) {}).length			// 1

// 函数内部严格模式
// 只要函数参数使用了默认值、解构赋值或者扩展运算符(即rest参数), 均会报错。因为函数内部的严格模式同时适用于函数体和函数参数。但是, 函数执行时, 先执行函数参数, 再执行函数体。这有导致一个悖论: 只有从函数体之中才能知道参数是否应该以严格模式执行, 但是参数却先于函数体执行。
function doSomething(a, b = a) {
    'use strict'
    // code
} // 报错
const doSomething = function({ a, b }) {
    'use strict'
    // code
}	// 报错
const doSomething = (...rest) => {
    'use strict'
    // code
}	// 报错
// 规避限制方法
// 方法一, 设定全局性的严格模式
'use strict'
const doSomething = (x, y = x) => {
    console.log(x, y)
}
doSomething(1)	// x: 1; y: 1
// 方法二, 把函数包在一个无参数的立即执行函数中
const doSomething = (function() {
    'use strict'
    return function(value = 42) {
        return value;
    }
}())
// 方法二实测, 只要是无参函数均可
const doSomething = function() {
    'use strict'
    console.log(Array.from(arguments))
}
doSomething(1, 2, 3)	// [ 1, 2, 3 ]

// name属性
var f = function() {}					// f.name --- ES5: ""; ES6: "f"
const bar = function baz() {} // bar.name --- ES5: "baz"; ES6: "baz"
bar.bind(null).name						// "bound baz"
(new Function).name						// "anonymous"
(function() {}).name					// ""
(function() {}).bind(null).name	// "bound "

// 箭头函数, ES6允许使用"箭头"(=>)定义函数
// 以下两种函数定义方式等价
let f = v => v
let f = function(v) {
  return v
}
// 如果箭头函数不需要参数或者需要多个参数, 则使用圆括号代表参数部分
let f = () => 5
let sum = (num1, num2) => num1 + num2
// 如果箭头函数的代码块部分多语一条语句, 则使用大括号将其括起来, 并使用return语句返回
let sum = (num1, num2) => { 
  // other code
  return num1 + num2 
}
// 如果箭头函数直接返回一个对象, 则在对象外面加上括号
let getItem = id => ({ id: id, name: 'Test' })
// 注意事项
# 1.函数体内的this对象就是定义时所在的对象, 而不是使用时所在的对象, this对象的指向是可变的, 但在箭头函数中它是固定。箭头函数根本没有自己的this, 内部的this其实是外层代码块的this
# 2.不可以当作构造函数。即, 不可以使用new命令, 否则抛出错误
# 3.不可以使用arguments对象, 该对象在函数体内不存在。可以使用rest参数代替
# 4.不可以使用yield命令, 即箭头函数不能用作Generator函数
function Timer() {
    this.s1 = 0, this.s2 = 0
    setInterval(() => this.s1++, 1000)
    setInterval(function () {
        this.s2++
    }, 1000)
}
let timer = new Timer()
setTimeout(() => console.log('s1: ', timer.s1), 3100)		// s1:  3
setTimeout(() => console.log('s2: ', timer.s2), 3100)		// s2:  0
// 下列代码等价
// ES6
function foo() {
    setTimeout(() => {
        console.log('id: ', this.id)
    }, 100)
}
// ES5
function foo() {
    let _this = this
    setTimeout(function() {
        console.log('id: ', _this.id)
    }, 100)
}
// 除了this, arguments、super和new.target, 箭头函数中也不存在, 均指向外层函数对应变量
function foo() {
    setTimeout(() => { console.log(Array.from(arguments)) }, 100)
}
foo(2, 4, 6, 8) // [ 2, 4, 6, 8 ]
// 箭头函数的call()、apply()、bind()无法改变this的指向
(function() {
    return [
        (() => this.x).bind({ x: 'inner' })()
    ]
}).call({ x: 'outer' })	// "outer"
// 管道机制(pipline), 前一个函数的输出是后一个函数的输入
const pipeline = (...funcs) => val => funcs.reduce((prev, curv) => curv(prev), val)
const plus = a => a + 1
const mult = a => a * 2
const addThenMult = pipeline(plus, mult)
addThenMult(5)	// 12 --- (5 + 1) * 2
// 箭头函数绑定this
// 双冒号(::)是函数绑定运算符, 双冒号左边是一个对象, 右边是一个函数。该运算符会自动将左边的对象作为上下文环境(即, this对象)绑定到右边函数上
foo::bar // 等同于 bar.bind(foo)
foo::bar(...arguments) // 等同于bar.apply(foo, arguments)
// 如果双冒号左边为空, 右边是一个对象的方法, 则等于将该方法绑定在该对象上
let method = ::obj.foo  // 等同于 let method = obj::obj.foo
let log = ::console.log // 等同于 let log = console.log.bind(console)
// 双冒号运算符返回的是原对象, 因此可以采用链式写法
getPlayers()::map(x => x.character())::takeWhile(x => x.strength > 100)::forEach(x => console.log(x))

// 尾调用
// 指某个函数的最后一步调用另一个函数
function f(x) {
  return g(x)
}
// 以下情况不属于尾调用
// 情况一, 调用函数g后还有赋值操作
function f(x) {
  let y = g(x)
  return y
}
// 情况二, 调用后还有操作
function f(x) {
  return g(x) + 1
}
// 情况三, 没有显式return的函数, 默认最后补上return undefined
function f(x) {
  g(x)
}
// 尾调用不一定出现在函数尾部, 函数m和n都属于尾调用, 它们都属于函数f的最后一步操作
function f(x) {
  if (x > 0) {
    return m(x)
  }
  return n(x)
}
// 函数调用会在内存中形成调用帧, 函数嵌套则会形成调用栈(call stack), 即函数A内部调用函数B, A的调用帧上方会形成B的调用帧(栈顶是B, 栈底是A)。A是B的外层函数。尾调用是外层函数(A)的最后一步操作, 因此不用保留外层函数(A)的调用帧。因此, 内层函数(B)的调用帧可以取代外层函数(A)的调用帧。
function f() {
  let m = 1
  let n = 2
  return g(m + n)
}
// 等同于
function f() {
  return (3)
}
// 等同于
f()
// 等同于
g(3)
// 上述代码, 如果函数g不是尾调用, 函数f就需要保存内部变量m和n的值、g的调用位置等信息。由于调用g之后, 函数f就结束了, 所以这一步完全可以删除f的调用帧, 只保留g的调用帧。这就叫做”尾调用优化“(Tail Call Optimization), 即只保留内层函数的调用帧, 作用:节约内存。
// 注: 只有不再用到外层函数的内部变量, 内层函数的调用帧才会取代外层函数的调用帧, 否则就无法进行"尾调用优化"
// 不会进行尾调用优化, 因为内层函数inner用到了外层函数的变量one
function addOne(a) {
  let one = 1
  function inner(b) {
    return b + one
  }
  return inner(a)
}
// *注: ES6的尾调用优化只有在严格模式下开启(暂时只有Safari支持)。正常模式下只能将”递归“转化为”循环“。

// 尾递归
// 指函数尾调用自身。尾递归只存在一个调用帧, 所以永远不会发生"栈溢出"。
// 尾递归优化('use strict'严格模式, 实测: Safari可行; 以下示例在其他浏览器中仍停留在理论阶段)
'use strict'
function fibonacci(n) {
    if (n <= 1) { return 1 }
    return fibonacci(n - 1) + fibonacci(n - 2)
}
fibonacci(100000)   // 堆栈溢出
function fibonacci(n, ac1 = 1, ac2 = 1) {
    if (n <= 1) { return ac2 }
    return fibonacci(n - 1, ac2, ac1 + ac2)
}
fibonacci(100000)		// Infinity
// 尾递归优化(正常模式, 实测: 所有浏览器都可行)
// 进入尾递归优化的过程, active变量被激活。每一轮递归sum返回都是undefined, 所以避免递归执行; 而accumulated存放sum执行的参数, 值一直被保存。本质上是将“递归”改为”循环“。
function tco(f) {
    let value;
    let 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;
        }
    }
}
const sum = tco(function(x, y) {
    if (y > 0) {
        return sum(x + 1, y - 1);
    } else {
        return x
    }
});
sum(1, 100000)		// Infinity
// 蹦床函数(trampoline), 将递归转换为循环执行
function sum(x, y) {
    if (y > 0) {
        return sum.bind(null, x + 1, y - 1); 
    } else {
        return x
    }
}
function trampoline(f) {
    while (f && f instanceof Function) {
        f = f();
    }
    return f;
}
trampoline(sum(1, 100000))	// Infinity

// 函数参数的尾逗号
// 新添加一个参数, 需要在原来最后一个参数后面加逗号, 对版本管理系统来说, 添加逗号那一行会发生变动, 显得冗余
function clownsEverywhere(
    param1, 
    param2,
) {}
clownsEverywhere(
    'foo', 
    'bar',
)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值