美文网首页
(五)函数的扩展

(五)函数的扩展

作者: 做最棒的 | 来源:发表于2018-08-24 10:12 被阅读0次

1、函数参数的默认值

1)ES5怎么制定默认值

function log(x, y) {
  y = y || 'World';
  console.log(x, y);
}

log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello World

上面代码检查函数log的参数y有没有赋值,如果没有,则指定默认值为World。这种写法的缺点在于,如果参数y赋值了,但是对应的布尔值为false,则该赋值不起作用。就像上面代码的最后一行,参数y等于空字符,结果被改为默认值。

为了避免这个问题,通常需要先判断一下参数y是否被赋值,如果没有,再等于默认值。

if (typeof y === 'undefined') {
  y = 'World';
}

2)ES6怎么制定默认值
ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。

function log(x, y = 'World') {
  console.log(x, y);
}

log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello

3)ES6中参数变量是默认声明,所以不能用 letconst 再次声明。

function foo(x = 5) {
  let x = 1; // error
  const x = 2; // error
}
// 为什么变量不能重命名

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

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

// 报错
function foo(x, x, y = 1) {
  // ...
}
// SyntaxError: Duplicate parameter name not allowed in this context

5)默认值是惰性求值的,运行时计算

let x = 99;
function foo(p = x + 1) {
  console.log(p);
}

foo() // 100

x = 100;
foo() // 101

上面代码中,参数p的默认值是x + 1。这时,每次调用函数foo,都会重新计算x + 1,而不是默认p等于 100

2、解构赋值默认值结合使用

function foo({x, y = 5}) {
  console.log(x, y);
}

foo({}) // undefined 5
foo({x: 1}) // 1 5
foo({x: 1, y: 2}) // 1 2
foo() // TypeError: Cannot read property 'x' of undefined

上面代码只使用了对象的解构赋值默认值,没有使用函数参数的默认值。只有当函数foo的参数是一个对象时,变量x和y才会通过解构赋值生成。如果函数foo调用时没提供参数,变量x和y就不会生成,从而报错。通过提供函数参数的默认值,就可以避免这种情况。

function foo({x, y = 5} = {}) {
  console.log(x, y);
}

foo() // undefined 5
function fetch(url, { body = '', method = 'GET', headers = {} }) {
  console.log(method);
}

fetch('http://example.com', {})
// "GET"

fetch('http://example.com')
// 报错

上面代码中,如果函数fetch的第二个参数是一个对象,就可以为它的三个属性设置默认值。这种写法不能省略第二个参数,如果结合函数参数的默认值,就可以省略第二个参数。这时,就出现了双重默认值。

function fetch(url, { body = '', method = 'GET', headers = {} } = {}) {
  console.log(method);
}

fetch('http://example.com')
// "GET"

?为什么会出现上述情况,为什么需要双重默认

// 写法一
function m1({x = 0, y = 0} = {}) {
    console.log(x,y);
  return [x, y];
}

// 写法二
function m2({x, y} = { x: 0, y: 0 }) {
    console.log(x,y);
  return [x, y];
}

m1({y:1});
m2({y:1});
1)切记只有 undefined 的情况下才能触发默认值,包括 null,'',NaN,false 等等都不能。

3、函数的 length 属性

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

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

(function (a = 0, b, c) {}).length // 0
(function (a, b = 1, c) {}).length // 1
1) 函数的length其实就是参数的长度。
2) length属性的返回值,等于函数的参数个数减去指定了默认值的参数个数
3) 如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了
4) 函数的length和arguments.length一样吗
console.log((function (a, b, c = 5) {
    console.log(arguments.length);
}).length) // 2
const abc = function (a, b, c = 5) {
    console.log(arguments.length);
}
abc(1,2,3);

4、作用域

一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)。等到初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的。

var x = 1;

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

f(2) // 2
let x = 1;

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

f() // 1

如果参数的默认值是一个函数,该函数的作用域也遵守这个规则。请看下面的例子。

请某位小伙伴解释?

let foo = 'outer';

function bar(func = () => foo) {
  let foo = 'inner';
  console.log(func());
}

bar(); // outer

上面代码中,函数bar的参数func的默认值是一个匿名函数,返回值为变量foo。函数参数形成的单独作用域里面,并没有定义变量foo,所以foo指向外层的全局变量foo,因此输出outer。

如果写成下面这样,就会报错。

function bar(func = () => foo) {
  let foo = 'inner';
  console.log(func());
}

bar() // ReferenceError: foo is not defined

5、应用

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

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

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

foo()
// Error: Missing parameter

可以将参数默认值设为undefined,表明这个参数是可以省略的。

function foo(optional = undefined) { ··· }

6、rest 参数

ES6 引入 rest 参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。

function add(...values) {
  let sum = 0;

  for (var val of values) {
    sum += val;
  }

  return sum;
}

add(2, 5, 3) // 10
1) rest 参数代替arguments变量的例子。
// arguments变量的写法
function sortNumbers() {
  return Array.prototype.slice.call(arguments).sort();
}

// rest参数的写法
const sortNumbers = (...numbers) => numbers.sort();
1) rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错
// 报错
function f(a, ...b, c) {
  // ...
}
2) 函数的length属性,不包括 rest 参数
(function(a) {}).length  // 1
(function(...a) {}).length  // 0
(function(a, ...b) {}).length  // 1

6、name 属性

自己看。

7、箭头函数

JavaScript 中,函数可以用箭头语法(”=>”)定义,有时候也叫“lambda表达式”。
这种语法主要意图是定义轻量级的内联回调函数

// Arrow function:
[5, 8, 9].map(item => item + 1); // -> [6, 9, 10]

// Classic function equivalent:
[5, 8, 9].map(function(item) {
  return item + 1;
}); // -> [6, 9, 10]
1) 箭头函数的演变
var f = () => { return 5; }
// 等同于
var f = () => 5;
// 等同于
var f = function () { return 5 };

var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
  return num1 + num2;
};
const abc = (a) => (b) =>a+b;
console.log(abc(3)(4));
// 田田解释上面语句
2) 箭头函数的返回值
// 有返回值
var f = () => { return 5; }
// 等同于
var f = () => 5;

// 无返回值
var sum = (num1, num2) => void (num1 + num2); 
console.log(sum(3,4));

// 两种方法无区别

// 无返回值
var sum = (num1, num2) => {
    num1 + num2;
}; 
console.log(sum(3,4));
3) 箭头函数的简写
// 省略return
var f = () => { return 5; }
// 等同于
var f = () => 5;

// 省略圆括号
var f = i =>console.log(i);

// 省略void
var f = i =>void(console.log(i));
var f = i =>void(console.log(i));

如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。

4) 箭头函数的常用方法

箭头函数的一个用处是简化回调函数。

// 正常函数写法
[1,2,3].map(function (x) {
  return x * x;
});

// 箭头函数写法
[1,2,3].map(x => x * x);

另一个例子是

// 正常函数写法
var result = values.sort(function (a, b) {
  return a - b;
});

// 箭头函数写法
var result = values.sort((a, b) => a - b);

下面是 rest 参数与箭头函数结合的例子。

const numbers = (...nums) => nums;

numbers(1, 2, 3, 4, 5)
// [1,2,3,4,5]

const headAndTail = (head, ...tail) => [head, tail];

headAndTail(1, 2, 3, 4, 5)
// [1,[2,3,4,5]]
5) 箭头函数的辨识方法

尽管箭头函数和传统函数的语法不同。

var abc = (a,b)=>a+b;
console.log(typeof abc)
console.log(abc instanceof Function) //true

箭头函数与其他函数并无二致。

箭头函数也可以使用call(),apply和bind()方法 ,但与其他函数不同的是,箭头函数的this值不会受这些方法的影响。

var sum = (a,b)=>a+b;
console.log(sum.call(null,1,2));
console.log(sum.apply(null,[1,2]));
const bindfun =sum.bind(null,1,2);
console.log(bindfun());
6) 箭头函数的总结

箭头函数的来源
箭头函数的渊源可以追溯到上古时期一个叫lambda演算的东西。lambda演算是数学家提出来的,有些数学家跟我们程序员一样也很懒,数学定理那么多,今天要证三角定律,明天要证勾股定律,累不累!那能不能将所有的证明问题用一个统一的体系进行形式化描述,然后由机器来完成自动推导呢?lambda演算就是干这个的,图灵也搞了一套体系叫图灵机,两者是等价的。

关于lambda演算说了这么多,好像跟今天要讲的箭头函数没什么关系?其实是有关系的,lambda演算深刻影响了箭头函数的设计。数学家们喜欢用 纯函数 式编程语言,纯函数的特点是没有副作用,给予特定的输入,总是产生确定的输出,甚至有些情况下通过输出能够反推输入。要实现纯函数,必须使函数的执行过程不依赖于任何外部状态,整个函数就像一个数学公式,给定一套输入参数,不管是在地球上还是火星上执行都是同一个结果。

6.1)没有this,super,arguments,和new.target绑定

箭头函数中的this、super、arguments及new.target这些值由外围最近一层非箭头函数决定。

6.2)不能通过new关键字调用

箭头函数 箭头函数没有[[Construct]]方法,所以不能被用作构造函数,如果能通过new关键字条用箭头函数,程序会抛出错误

6.3)没有原型

由于不可以通过new关键字调用箭头函数,因而没有构建原型的需求,所以箭头函数不存在prototype这个属性

6.4)不可以改变this的绑定

函数内部的this值不可被改变,在函数的生命周期内时钟保持一致。

6.5)不支持arguments对象

箭头函数没有arguments绑定,所以你必须通过命名参数和不定参数这两种形式访问函数的参数

6.6)不支持重复的命名参数

无论严格模式还是非严格模式

7) 尾调用优化

尾调用
尾调用指的是函数作为另一个函数的最后一条语句被条用。

function doSomething(){
     return doSomethingOther(); //尾调用
}

为什么要尾条用
ES5引擎中,尾调用的实现与其他函数调用的实现类似:创建一个新的栈帧,将其推入调用栈来表示函数调用。也即是说,在循环调用中,每一个未用完的栈帧都会被保存在内存中,当调用栈变得过大时会造成程序问题。(栈溢出或内存溢出)

1)怎么做才是尾调用优化
1.1)尾调用不访问当前帧栈的变量(也就是说函数不是一个闭包)
1.2)在函数内部,尾调用是最后一条语句
1.3)尾调用的结果作为函数值返回

满足上述三个条件,可以被javascript引擎自动优化

"use strict"
function doSomething() {
    return doSomethingOther(); //尾调用
}

示例

//不能优化
"use strict"
function doSomething() {
   doSomethingOther(); 
}
//不能优化
"use strict"
function doSomething() {
    return 1+doSomethingOther(); 
}
//不能优化
"use strict"
function doSomething() {
    var  result = doSomethingOther();
    return result; 
}
//不能优化
"use strict"
function doSomething() {
    var num = 1,func=()=>num;
// 无法优化,该函数是一个闭包
return func();
}

递归函数是其最主要应用场景
阶乘函数实例

function factorial(n) {
    if(n<=1){
        return 1;
    } else {
        // 不能优化
        return n* factorial(n-1);
    }
}
//能优化
function factorial(n, p =1) {
    if(n<=1){
        return 1*p;
    } else {
        const result = n* p;
        return factorial(n-1, result);
    }
}
console.log(factorial(10));
8) 项目实战
8.1) rest运算符实战
const params = { ...values };           
params.coverImgStorageId = coverImgObj.storageId;
params.shareImgStorageId = shareImgObj.storageId;
const content = JSON.stringify(editorList);
params.content = content;

delete params.editorList;
delete params.coverImg;
delete params.shareImg;

//优化后代码
const {editorList, coverImg, shareImg, ...params} = values;
params.coverImgStorageId = coverImgObj.storageId;
params.shareImgStorageId = shareImgObj.storageId;
const content = JSON.stringify(editorList);
params.content = content;
8.2)斐波那契数列尾递归调用优化

作业

相关文章

网友评论

      本文标题:(五)函数的扩展

      本文链接:https://www.haomeiwen.com/subject/wrfbiftx.html