8 函数的扩展

函数参数的默认值

  • 参数变量是默认声明的,所以不能用let或const再次声明。
  • 使用参数默认值时,函数不能有同名参数。
function foo(x = 5) {
  let x = 1; // error
  const x = 2; // error
}

// 不报错
function foo(x, x, y) {
  // ...
}

// 报错
function foo(x, x, y = 1) {
  // ...
}
// SyntaxError: Duplicate parameter name not allowed in this context
  • 参数默认值不是传值的,而是每次都重新计算默认值表达式的值。
let x = 99;
function foo(p = x + 1) {
  console.log(p);
}

foo() // 100

x = 100;
foo() // 101
  • 与解构赋值默认值结合使用
function foo({x, y = 5} = {}) {
  console.log(x, y);
}

foo() // undefined 5

首先foo的默认值是一个空对象,不管是否传入对象,都可以结构赋值x,y,其中y的默认值是5

  • 要区分参数的默认值和结构赋值的默认值,如果给了参数默认值,当不传参的时候,使用参数默认值,如果传参了,参数默认值就没用了
  • 如果给了解构赋值的默认值,不管是否传参(除了没有参数默认值又不传参的情况),都会对参数进行解构赋值,不管这个参数是传入的参数还是默认参数,都一视同仁进行解构赋值

参数默认值的位置

  • 定义了默认值的参数,应该是函数的尾参数。因为这样比较容易看出来,到底省略了哪些参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的。
    -有默认值的参数不是尾参数时,无法只省略该参数,而不省略它后面的参数,除非显式输入undefined。
// 例二
function f(x, y = 5, z) {
  return [x, y, z];
}

f() // [undefined, 5, undefined]
f(1) // [1, 5, undefined]
f(1, ,2) // 报错
f(1, undefined, 2) // [1, 5, 2]

函数的 length 属性

  • 指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真。
  • 如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了。
(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2

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

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

参数默认值的作用域

函数的参数部分是一个单独的作用域,如果没有对应的变量值会去上级作用域中查找

var x = 1;

function foo(x = x) {
  // ...
}

foo() // ReferenceError: x is not defined

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

作用域的复杂例子

var x = 1;
function foo(x, y = function() { x = 2; }) {
  var x = 3;
  y();
  console.log(x);
}

foo() // 3
x // 1
  • 其中一共有3个x变量,外部x = 1,参数x = 2,内部x = 3
  • 函数内部没有使用过参数x, console.log(x);使用的是函数内部var的那个x。
  • 如果将var x = 3的var去掉,则内部的x使用的是参数x,输出的结果就是2了。

应用

利用参数默认值,可以指定某一个参数不得省略,如果省略就抛出一个错误。

function throwIfMissing() {
  throw new Error('Missing parameter');
}

function foo(mustBeProvided = throwIfMissing()) {
  return mustBeProvided;
}

foo()
// Error: Missing parameter
  • 参数的默认值不是在定义时执行,而是在运行时执行。

rest 参数

  • rest参数直接就是一个数组
    -arguments对象不是数组,而是一个类似数组的对象。所以为了使用数组的方法,必须使用Array.prototype.slice.call先将其转为数组。

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

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

箭头函数

  • 由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。
// 报错
let getTempItem = id => { id: id, name: "Temp" };

// 不报错
let getTempItem = id => ({ id: id, name: "Temp" });

使用注意点

  1. 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
  2. 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
  3. 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
  4. 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

箭头函数可以让this指向固定化
this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的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
const cat = {
  lives: 9,
  jumps: () => {
    this.lives--;
  }
}

cat.jumps()方法是一个箭头函数,这是错误的。调用cat.jumps()时,如果是普通函数,该方法内部的this指向cat;如果写成上面那样的箭头函数,使得this指向全局对象,因此不会得到预期结果。这是因为对象不构成单独的作用域,导致jumps箭头函数定义时的作用域就是全局作用域。

  • 第二个场合是需要动态this的时候,也不应使用箭头函数。
var button = document.getElementById('press');
button.addEventListener('click', () => {
  this.classList.toggle('on');
});

上面代码运行时,点击按钮会报错,因为button的监听函数是一个箭头函数,导致里面的this就是全局对象。如果改成普通函数,this就会动态指向被点击的按钮对象。

尾调用和尾递归

  • 尾调用:尾调用优化”(Tail call optimization),即只保留内层函数的调用帧。如果所有函数都是尾调用,那么完全可以做到每次执行时,调用帧只有一项,这将大大节省内存。
  • 尾递归:对于尾递归来说,由于只存在一个调用帧,所以永远不会发生“栈溢出”错误。

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

function f(x){
  return g(x);
}

尾递归优化过的 Fibonacci 数列实现如下。

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

尾递归优化的实现

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

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

上面就是蹦床函数的一个实现,它接受一个函数f作为参数。只要f执行后返回一个函数,就继续执行。注意,这里是返回一个函数,然后执行该函数,而不是函数里面调用函数,这样就避免了递归执行,从而就消除了调用栈过大的问题。
然后,要做的就是将原来的递归函数,改写为每一步返回另一个函数。

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

上面代码中,sum函数的每次执行,都会返回自身的另一个版本。
现在,使用蹦床函数执行sum,就不会发生调用栈溢出。

trampoline(sum(1, 100000))
// 100001
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
pbfunc是PowerBuilder的一个关键字,用于声明和定义函数。外部函数扩展是指在PowerBuilder中调用其他编程语言编写的外部函数。 在PowerBuilder中,我们可以使用pbfunc关键字声明和定义函数。这些函数通常用于处理业务逻辑、数据操作、界面交互等功能。 然而,有时候我们可能需要调用其他编程语言编写的函数,例如C++、Java等。这些函数可能实现了一些PowerBuilder无法直接实现的功能,如高级算法、图像处理等。 为了实现这一目标,PowerBuilder提供了外部函数扩展的功能。通过外部函数扩展,我们可以在PowerBuilder中调用其他编程语言编写的函数。 外部函数扩展的使用分为两个步骤。首先,我们需要在PowerBuilder中声明一个外部函数。在声明时,我们需要指定函数的名称、参数列表、返回值类型以及所在的外部库路径。 然后,我们可以在PowerBuilder中调用声明的外部函数。PowerBuilder根据外部函数的声明和定义,向外部编程语言发送调用请求,并等待结果返回。 需要注意的是,外部函数扩展不是PowerBuilder的标准功能,需要借助第三方插件或API来实现。在使用外部函数扩展前,我们需要确保所需的插件或API已经正确安装,并且在PowerBuilder中进行了相关配置。 总之,外部函数扩展是一种将其他编程语言的函数嵌入到PowerBuilder中的方式。通过外部函数扩展,我们可以充分利用已有的代码资源,提高程序的灵活性和功能性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值