你真的了解function吗?

411 阅读3分钟

开发过程中,我们会经常定义一堆函数,函数内部运算处理结果。但是你真的了解function的用法吗?如下4种方法的定义有区别吗?现在可以仔细思考一下。

// 函数关键字
function f1(name) {
  console.log(`hello ${name}!`)
}

// 函数字面量
var f2 = function(name) {
  console.log(`hello ${name}!`)
}

// Function()构造函数
var f3 = new Function('name', 'console.log(`hello ${name}`)')

// 箭头函数
var f4 = (name) => {
  console.log(`hello ${name}!`)
}

函数关键字和函数字面量是我们经常用到的定义方法的方式,使用的方式也很接近,但是他们之前有区别吗?我们看如下的实例:

f1('张三') // hello 张三!

function f1(name) {
  console.log(`hello ${name}!`)
}
f2('张三') // Uncaught TypeError: f2 is not a function

var f2 = function(name) {
  console.log(`hello ${name}!`)
}

从上面的例子能发现,函数字面量来定义方法,在函数定义前使用,js报错了,但是函数关键字定义方法可以正常运行。这是因为函数关键字定义方法是可以函数提升的,就算你在定义函数前使用了这个函数,也可以正常运行。而函数字面量方式定义了一个匿名函数,将其赋值给变量f2,在调用f2('张三')时,js判断使用了f2变量,将变量提升到使用前,但是此时并没有赋值,所以f2是undefined。 现在我们从运行效率方面看下,这2种的运行速度:

console.time('f1')

function f1(name) {
  console.log(`hello ${name}!`)
}

f1('张三')

console.timeEnd('f1') // f1: 0.098876953125 ms
console.time('f2')

var f2 = function(name) {
  console.log(`hello ${name}!`)
}

f2()

console.timeEnd('f2') // f2: 0.101806640625 ms

从运行效率角度看,函数关键字语句运行效率稍微高点,但是js引擎对函数优化的很好,所以基本上不需要考虑这2中写法的效率问题,根据自己的习惯来就好。 接下来我们看下Function构造函数来创建函数的方式:

function wrapFunction() {
  var temp = '我是临时变量'
  return new Function('return temp')()
}

wrapFunction() // Uncaught ReferenceError: temp is not defined

从如上的案例可以看出,构造函数的方式生成function是无法访问局部变量的,他会自动升级成顶级函数,不遵循典型的作用域。而且Function()每次执行都会解析函数主体,并创建一个新的函数对象,所以调用Funciton()的效率相对较低。但是对于某些极端场景,function内容是接口返回的,此时我们就需要使用此种方式。

console.time('f3')

var f3 = new Function('name', 'console.log(name)')

f3()

console.timeEnd('f3') // f3: 0.14404296875 ms

终于到了最后一种,箭头函数了。他是es6提案中提出来的,可以简化了写法,和箭头函数的写法一致,但是你知道使用箭头函数过程中会遇到那些坑吗?

var person = {
  name: '张三',
  
  sayName: function() {
    console.log(`我是${this.name}`)
  }
}

person.sayName() // 我是张三
var person = {
  name: '张三',
  
  sayName: () => {
    console.log(`我是${this.name}`)
  },
  
  consoleArguments: () => {
    console.log(arguments)
  },
  
  sayName1: function() {
    return () => console.log(`我是${this.name}`)
  }
}

person.sayName() // 我是
person.sayName1()() // 我是张三
person.consoleArguments() // Uncaught ReferenceError: arguments is not defined

我们期望在箭头函数中获取person的this,但是他却拿到了person对象所处作用域的this,也就是指向了window对象。由此可见,箭头函数没有自己的this和arguments对象,所以bind, call和apply是无法改变箭头函数的this指向。但是由于箭头函数也是函数,所以这3个方法并不会报错。

happy hacking!