ihtml5 / blog

个人博客 源码阅读*前端实践 My Blog
MIT License
6 stars 0 forks source link

Learn ES6 [函数篇] #21

Open ihtml5 opened 8 years ago

ihtml5 commented 8 years ago

函数参数的默认值

规则

  1. 参数变量是默认声明的,所以不能用let或const再次声明
 let oPlus = (x,y=5) => {
    let x = 1; // 语法错误
   return x + y;
 }

3.当传入的参数是undefined时,如果有默认参数,将会调用默认值,传入参数为null时,不会调用默认值

let oPlus = (x,y='world') => {
  return x+y;
}
console.clear(); 
console.log(oPlus('hello ',undefined));  //'hello world'
console.log(oPlus('hello '));  //'hello world'
console.log(oPlus('hello ',null));  //'hello null'

4.非尾部的参数设置默认值,实际上这个参数是没法省略的

let oPlus = (x='world',y) => {
  return x+y;
}
console.clear(); 
console.log(oPlus());  // 报错
console.log(oPlus(,));   // 1 undefined
console.log(oPlus(null)); // null undefined
console.log(oPlus(undefined)); 1 undefined

代码示例CODEPEN

// es5中默认参数的写法
function oPlus(x,y) {
    if (typeof y === 'undefined') {
        y = 'world'
    } 
    return x + y;
}
// es6中默认值写法
let oPlus = (x,y='world') => {
  return x+y;
}
console.clear(); 
console.log(oPlus()); // 'undefinedworld'
console.log(oPlus('hello '));  //'hello world'
console.log(oPlus(undefined)); // 'undefinedworld'
console.log(oPlus(null)); // 'nullworld'
console.log(oPlus(undefined,undefined)); // 'undefinedworld'
console.log(oPlus(null,null)); // 0
console.log(oPlus(undefined,'ok')); // 'undefinedok'
console.log(oPlus(null,'ok')); // 'nullok'
console.log(oPlus(null,undefined)); // 'nullworld'
console.log(oPlus(undefined,null)); // NaN

与解构赋值默认值一起使用

规则

  1. 默认参数仍然可以进行二次默认参数设置,比如 {x,y=5} = {}或者 {x,y} = {x:1,y:5}

    这两种写法稍微有些不同,个人更倾向于第一种

// {x,y=5} = {} 等同于 {x,y} = {y:5} 双重默认值
let destructFunc = ({x,y=5} = {}) => {
  console.log(x,y);
}
console.clear();
destructFunc(); // undefined 5
destructFunc({x:1}) // 1 5
destructFunc({x:2,y:10}) // 2 10
let foo = ({x,y=5}) => {
  console.log(x,y);
}
foo(); // 报错
foo({x:1}); // 1 5
foo({}) // undefined 5

函数的length

规则

  1. 函数length的含义指的是预计传入函数的参数个数,如果用...rest表示参数的话,其length = rest.length;
  2. 参数设置默认值后,自身以及后续参数都不纳入函数length统计范围;

    示例代码

 (x,y=5) => {}  // length: 1
 (x=5,y) => {} // length: 0
 (x,y=5,z) => {} // length: 1
 ({x,y=5}) =》 {}  // lenght: 1
 ({x,y}) =》 {}  // lenght: 1
 ({x=1,y}) =》 {}  // lenght: 1
 ({x,y=5} = {}) =》 {}  // lenght: 0

作用域

规则

默认参数的作用域规则也符合一般变量的作用域规则,即首先是函数作用域,其次是全局作用域

示例代码

let x = 1;
let foo = (y=x) => {
   let x  = 3;
   console.log(y);
}
foo();//1
// 参数默认值是函数
let strfoo = 'bar';
let sfunc = (func = () => strfoo) => {
  let strfoo = 'ccc';
  console.log(func());
}
// 传值的变量是全局变量是
let sx = 1;
let sxfoo = (x, y= () => { sx=x }) => {
  console.log('inner',x);
  x  = 4;
  y();
  console.log(sx);
}
sfunc(); // 'bar'
sxfoo(5); // 'inner' 5 \n 4
console.log(sx); // 4

原理

从上面可以看出,sxfoo执行时,分以下几步:

一、执行参数部分

  1. 先通过函数传参,显示指明第一个参数x等于5;
  2. 由于没有传值给参数y,此时其值为undefined,自动赋值y =() => {sx = x} ,然后初始化此匿名箭头函数,顺序也是从参数到函数体。由于该匿名函数此时只是进行一系列函数初始化并未执行,因此sx只是绑定了window'x'一个引用

二、执行函数体部分

  1. 执行函数体代码,x仍等于5,此时程序向控制台输出 inner 5
  2. x= 4 将 全局变量x即window['x']设置为4,
  3. 执行y() 此时sx= x实际执行,赋值sx等于window['x']即4
  4. 程序执行到最后一句console.log(sx),向控制台输出 4

    rest

    规则

  5. rest是一个真正的数组,可以使用数组对象的一切方法
  6. rest参数后不能再有参数,否则会报错
  7. 函数的length属性不包括rest参数

    示例代码

  // rest可以使用一个真正的数组方法
  let arrPush = (array,..items) => {
     items.forEach( item => array.push(item));
  }
  let a = [];
  arrPush(a,1,2,3,4);
  // 函数长度
  let restFunc = (y,...rest) => {};
  let restFunc1 = (...rest) => {}
  console.log(reestFunc.length,restFunc1.length); // 1 0;

扩展运算符

1.扩展运算符可以将数组变成以逗号为分隔的字符串 2.如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。

代码示例CODEPEN

// 将数组转化为逗号分隔的字符串
let a = [1,2,3]
console.log(...a) // 1 2 3 
console.log(1,2,3);
// 合并数组
let b = [4,5]
a.push(...b)
console.log(a) // 1 2 3 4 5
// 替代apply
console.log(Math.max(...a)); // 3
console.log(Math.max.apply(null,a)); // 3
// 与解构赋值一起使用
const [first,...second] = [1,2,3,4];
console.log(first,second);// 1,[2,3,4]
const [ifirst,...isecond] = [];
console.log(ifirst,isecond); // undefined [];
const [...iisecond,iifirst] // 报错
// 处理函数的返回值,函数的返回值只有一个,如果想多个值,可以返回数组
let foo = () => [1,2,3];
console.log(...foo());
// 将字符串转化为真正的数组
console.log(...'helloworld');
/* 任何Iterator接口的对象,都可以用扩展运算符转为真正的数组。
...document.getElementsByTagName document.childNodes
上面代码中,arrayLike是一个类似数组的对象,但是没有部署Iterator接口,扩展运算符就会报错。这时,可以改为使用Array.from方法将arrayLike转为真正的数组。
*/
let arrayLike = {
  '0': 'a',
  '1': 'b',
  '2': 'c',
  length: 3
};

// TypeError: Cannot spread non-iterable object.
let arr = [...arrayLike];
// Map和Set结构,Generator函数都可以使用扩展运算符
let map = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three'],
]);

let arr = [...map.keys()]; // [1, 2, 3]
var go = function*(){
  yield 1;
  yield 2;
  yield 3;
};

[...go()] // [1, 2, 3]

函数name

规则

将函数名称标准化了,纠正了es5,匿名函数赋值,函数名称为空的现象

代码示例

 let foo = () => {} // function name is 'foo'
 var foo = function() {} // function name is ''
 function foo() {} // function name is foo

严格模式

规则

  1. 参数使用了默认参数,解构赋值,扩展符的不能在函数体内部使用‘use strict’
  2. 在全局下使用严格模式,即将‘use strict’显示声明在所有函数体外
  3. 把函数包在一个无参数的立即执行函数里面,可以规避第一条规则
const doSomething = (() => {
  'use strict'
 return function(value=43) {
    return value; 
 }
})

原理

函数执行的顺序是先执行参数部分,再执行函数体,如果在函数内部常规声明严格模式,由于参数部分先执行,此时严格模式还没生效,而到了函数体部分,严格模式又生效了,这样参数部分和函数体部分就出现不一致的验证的规则,增加了程序的复杂度,es6标准索性禁止设置严格模式。

箭头函数

规则

  1. 箭头函数的this指的是它定义时的上下文环境,而不是运行时的上下文环境;
  2. 箭头函数不能当作构造函数,因为它没有new命令,否则会报错;
  3. 箭头函数不能使用arguments,因为箭头函数没有arguments这个值,可以用rest替代;
  4. 不可以用作yield函数,因为箭头函数不能用作Generator函数;
  5. 除了this,以下三个变量在箭头函数之中也是不存在的,指向外层函数的对应变量:arguments、super、new target;
  6. 由于箭头函数没有自己的this,所以当然也就不能用call()、apply()、bind()这些方法去改变this的指向。

    代码示例

// 简单的箭头函数
let foo = () => {}
// 作为回调函数被执行
[].map(v => v+1);
[].map(v => { return v+1;})
//等同于
[].map(function(v) { return v+1})
// 结合扩展符
let a = [1,2,3];
let oitem = (...numbers) => {
    let sum = 0;
    numbers.forEach(n => sum+=n);
    return sum;
}
console.log(oitem(...a)); //6
//和变量解构一起使用
let oput = ({first,second}) => `${first}${second}`;
console.log(oput({
  first: 'cuc',
  second: 'baidu'
})); // 'cucbaidu'
// 箭头函数的this指向
console.clear();
window['s2'] = 0;
class Timer {
  constructor() {
    this.s1 = 0;
    this.s2 = 0;
    this.count = 0;
    setInterval( () => {
      this.s1++;
    },1000);
    setInterval(function() {
      this.s2++;
      this.count++;
    },1000);
  }
}
let timer = new Timer();
console.log(timer.s2); // 0
setTimeout(() => console.log('s1',timer.s1,window['s1']),2100); // 2 undefined
setTimeout(() => console.log('s2',timer.s2,window['s2']),2100); // 0  2
console.log(window['s2']); // 0 
/* 
声明了一个类,类里面一个使用箭头函数作为回调函数,一个使用普通函数作为回调函数。
箭头函数绑定定义时的上下文环境也就是Timer,而普通函数绑定的是运行时的上下文环境,即window
*/

尾调用

什么是尾调用

  1. 尾调用(Tail Call)是函数式编程的一个重要概念,本身非常简单,一句话就能说清楚,就是指某个函数的最后一步是调用另一个函数;
  2. 尾调用只发生在严格模式下。
  3. 最有最后一步调用的是函数,才可以称为尾调用
function f(x) {
    return g(x);
}
function f(x) {
    if (x<0) {
     return m(x);
    } else {
     return n(x);
    }
}
只有最后一步调用的是函数才是尾调用

原理

尾调用之所以与其他调用不同,就在于它的特殊的调用位置。我们知道,函数调用会在内存形成一个“调用记录”,又称“调用帧”(call frame),保存调用位置和内部变量等信息。如果在函数A的内部调用函数B,那么在A的调用帧上方,还会形成一个B的调用帧。等到B运行结束,将结果返回到A,B的调用帧才会消失。如果函数B内部还调用函数C,那就还有一个C的调用帧,以此类推。所有的调用帧,就形成一个“调用栈”(call stack)。尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用帧就可以了。

尾递归

尾递归的实现,往往需要改写递归函数,确保最后一步只调用自身。做到这一点的方法,就是把所有用到的内部变量改写成函数的参数。 CODEPEN

// hack尾递归方式一
 function currying(func,n) {
     return function(m) {
        return func.apply(this,m,n);
     }
  }
function tailFactorial(n, total) {
  if (n === 1) return total;
  return tailFactorial(n - 1, n * total);
}

const factorial = currying(tailFactorial, 1);
console.log(factorial(5)); // 120
// hack 尾递归方式二
function factorial(n,total = 1) {
    if (n===1) {
        return total;
    }
    return factorial(n-1,total);
}
console.log(factorial(5)) // 120
// hack 尾递归方式三
let tco = (f) => {
  let value = null;
  let active = false;
  let accumArr = [];
  return function() {
    accumArr.push(arguments);
    while(accumArr.length) {
      active = true;
      value = f.apply(this,accumArr.shift());
    }
    active = false;
    return value;
  }
}

let sum = tco((n,m) => {
  if (m>0) {
    return sum(n+1,m-1);
  } else {
    return n;
  }
});

console.log(sum(1,1000));

相关链接

  1. ECMAScript入门
  2. exploring es6
  3. 深入浅出Es6