4.Function扩展

110 阅读4分钟

ES6对Function做一些扩展,引入很多新特征,下面来逐个介绍

1.函数默认参数

函数定义时,可以指定默认参数,在未传入参数或参数为undefined时,会使用该默认参数

function Obj(id = 107, name = 'jack'){
  this.id = id
  this.name = 'jack'
}
const obj = new Obj() // {id: 107, name: 'jack'}

function add(x, y = 1){
  return x + y
}
add(1) // 2
add(1, 3) // 4

但是要小心,当指定默认函数参数,在函数调用时传参会发生隐式解构,即let [arg1,...,arg2] = [...arguments],不能再此使用letconst重定义

function fn(x = 5){
  // 隐式解构:let [x=5] = [...arguments]
  let x = 1 // error
}

// 下面这个可以
function fn(x=7) {
  if (x) {
    let x = 1 // 不在一个语句块里
    console.log(x) // 1
  }
  console.log(x) // 7
}

惰性求值

let x = 99
function foo(p = x + 1){
  alert(p)
}
foo() // 100

x = 100
foo() // 101

指定了默认值后,函数的length属性会失真,因为length属性含义为函数预期传入的参数个数

(function (a, b, c = 5) {}).length // 2
(function (a, b = 1, c) {}).length // 1 只记录默认值前面的

2.rest参数

rest参数形式为...args,会将获取函数的多余参数并将其放进数组中

function(a,...b){}

// 等价于
function(){
  let args = [...arguments]
  let a = args[0]
  let b = args.slice(1)
}
// 注意rest只能是传参的末尾,放在其他位置会报错
function(a,...b,c){} // 报错

应用

function add(n, ...values) {
  let sum = 0
  for (let val of values) {
    sum += val
  }
  return n * sum
}
add(10, 1, 2, 3) // 60

3.name属性

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

function fn(){ ... }
fn.name // 'fn'

var bar = function foo(){ ... }
bar.name // 'foo'

// 特殊的
var fn = function(){ ... }
fn.name // 'fn'

// bind()返回的函数会加上‘bound’前缀
function fn(){ ... }
fn.bind(null).name // 'bound fn'

☆4.箭头函数

4.1 箭头函数语法

箭头函数是ES6对函数进行的最大扩展,以下是箭头函数的语法

var fn = n => n+1
// 等价于
var fn = function(n){
  return n+1
}

var fn = (a,b)=>a+b

var fn = (a,b)=>{
  return a+b
}

若直接返回一个对象有必要时需要加个括号

var getId = id =>({id: id,name:'jack'})

// 等价于
var getId = (id)=>{
  return {id: id,name:'jack'}
}

若箭头函数只有一行语句,且不需要返回值,可以采用void(慎用,会被同事特殊关怀滴~)

var fn = arr => void arr.push(1)

var arr = [3,2]
fn(arr) // [3,2,1]

4.2 箭头函数和参数解构结合

var obj ={
  id: 107,
  name: 'jack',
  age: 18
}
var getIdAndName = ({id, name}) =>name + ':'+ id

getIdAndName(obj) // jack:107

4.3 箭头函数与rest参数结合

let numbersToArr = ...num => num
numbersToArr(1,2,3) // [1,2,3]

let fn = (head,...end) =>[head, end]
fn(1,2,3) //[1,[2,3]]

☆4.4 箭头函数注意点

简单记忆就是:4个 "达咩"

  • 不存在this,标准的来说箭头函数执行上下文中无this,实际来自于沿作用域链访问到的this
  • 不存在arguments
  • 不可以new,即不可以作为构造器函数使用
  • 不可以yield

详细说明一下箭头函数的this

//  用Babel编译ES6代码来解释this
// ES6
function fn() {
  let f = () => {
    console.log('id:', this.id)
  }
}

// ES5
function fn() {
  var _this = this
  var f = function () {
    console.log('id:', _this.id)
  }
}

由于箭头函数作用域中没有自己的this,故call、apply、bind对其无效

function fn() {
  return () => {
    return () => {
      return () => {
        console.log('id:', this.id)
      };
    };
  };
}

var f = fn.call({id: 1}) 
// 指定fn上下文的this为{id:1},f为return出来的箭头函数,这时候this一直为fn上下文中的this,
// 故箭头函数没有真正意义上的this,也意味着bind、call、apply对它无效!

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,前提你得了解VO,AO,执行上下文等概念《一文搞懂执行上下文、VO、AO、Scope、[[scope]]、作用域链、闭包》

var obj = {
  name: 'jack',
  sayName: () => alert(this.name),
  sayName2() { alert(this.name) }
}

obj.sayName2() // 'jack'
obj.sayName() // undefined, WTF???

// 冷静详细分析一下
// 在全局执行obj.sayName2()和obj.sayName()

// 1.创建全局上下文gContext
gContext = {
  VO:{
    obj:{ ... },
    this: window
  }
  Scope:[ gContext.VO ],
}

// 2.创建sayName2Context
sayName2Context = {
  AO:{
    arguments:{ length:0 },
    this:obj  // 普通函数执行上下文有this
  },
  Scope:[ sayName2Context.AO, gContext.VO ]
}
// 执行函数,从Scope作用域链查询this,
// 在sayName2Context.AO中找到this为obj,故打印obj.name:'jack'

// 3.创建sayNameContext
sayNameContext = {
  AO:{
    arguments:{ length:0 },
    // 箭头函数执行上下文无this(区别在这里!!!)
  },
  Scope:[ sayNameContext.AO, gContext.VO]
}
// 执行函数,从Scope作用域链查询this,
// 在gContext.VO中找到this为window,故打印window.name:undefined