前言

今天在写代码时,用了箭头函数,出现了如下问题:

function Drawing(shape) {
  this.shape = shape
}
Drawing.prototype.drawShape = (radius, x, y) => { // bug 问题所在
  return this.shape.draw(radius, x, y) // this指向了全局
}

报错信息:

Error in created hook: "TypeError: Cannot read properties of undefined (reading 'shape')"

然后,改成function后,错误消除。

function Drawing(shape) {
  this.shape = shape
}
Drawing.prototype.drawShape = function(radius, x, y) {
  return this.shape.draw(radius, x, y)
}

原因是this指向问题,这是因为不了解箭头函数属性导致的。

  • 箭头函数没有单独的thisargumentssuper,并且不能作为构造函数。

箭头函数

箭头函数

  • 箭头函数没有单独的thisargumentssuper,并且不能作为构造函数。
  • 虽然箭头函数没有创建自己的this,但是它会在自己的作用域链上继承this,不能修改this的指向,所以在callapply调用时,只能传参数,不能绑定this
  • 箭头函数没有prototype属性
  • 箭头函数不能作为函数生成器

普通函数

  • 普通函数可以作为构造函数,this指针指向一个新的对象。
  • 如果普通函数是一个对象的方法,那么this指向的是该对象。

普通函数和箭头函数的区别:

  1. 箭头函数没有prototype(原型),所以箭头函数本身没有this
  2. 箭头函数的this在定义的时候继承自外层第一个普通函数的this。
  3. 如果箭头函数外层没有普通函数,严格模式和非严格模式下它的this都会指向window(全局对象)
  4. 箭头函数本身的this指向不能改变,但可以修改它要继承的对象的this。
  5. 箭头函数的this指向全局,使用arguments会报未声明的错误。
  6. 箭头函数的this指向普通函数时,它的argumens继承于该普通函数
  7. 使用new调用箭头函数会报错,因为箭头函数没有constructor
  8. 箭头函数不支持new.target
  9. 箭头函数不支持重命名函数参数,普通函数的函数参数支持重命名
  10. 箭头函数相对于普通函数语法更简洁优雅

箭头函数的注意事项及不适用场景

箭头函数的注意事项

  1. 箭头函数一条语句返回对象字面量,需要加括号

    var func2 = () => ({foo: 1}) // ok

  2. 箭头函数在参数和箭头之间不能换行

  3. 箭头函数的解析顺序相对||靠前

    let c = false || (() => {}); // ok

箭头函数的特点

1. 相比普通函数,箭头函数有更加简洁的语法。

普通函数

function add(num) {
  return num + 10
}

箭头函数

const add = num => num + 10;

2. 箭头函数不绑定this,会捕获其所在上下文的this,作为自己的this。

这句话需要注意的是,箭头函数的外层如果有普通函数,那么箭头函数的this就是这个外层的普通函数的this,箭头函数的外层如果没有普通函数,那么箭头函数的this就是全局变量。

下面这个例子是箭头函数的外层有普通函数。

let obj = {
  fn:function(){
      console.log('我是普通函数',this === obj)   // true
      return ()=>{
          console.log('我是箭头函数',this === obj) // true
      }
  }
}
console.log(obj.fn()())

下面这个例子是箭头函数的外层没有普通函数。

let obj = {
    fn:()=>{
        console.log(this === window);
    }
}
console.log(obj.fn())
// true

3. 箭头函数是匿名函数,不能作为构造函数,不可以使用new命令,否则后抛出错误。

let f = () => {};
let t = new f(); // f is not a constructor

4. 箭头函数不绑定arguments,取而代之用rest参数解决,同时没有supernew.target

箭头函数没有argumentssupernew.target的绑定,这些值由外围最近一层非箭头函数决定。

下面的这个函数会报错,在浏览器环境下。

let f = ()=>console.log(arguments);

//报错
f(); // arguments is not defined

下面的箭头函数不会报错,因为arguments是外围函数的。

function fn(){
  let f = ()=> {
    console.log(arguments)
  }
  f();
}
fn(1,2,3) // [1,2,3]

箭头函数可以通过拓展运算符获取传入的参数。

const testFunc = (...args) => {
    console.log(args); // [ [1, 2, 3] ], undefined
}

testFunc([1, 2, 3])

5. 使用call,apply,bind并不会改变箭头函数中的this指向。

  • 当对箭头函数使用call或apply方法时,只会传入参数并调用函数,并不会改变箭头函数中this的指向。
  • 当对箭头函数使用bind方法时,只会返回一个预设参数的新函数,并不会改变这个新函数的this指向。

请看下面的代码

window.name = "window_name";

let f1 = function () {
return this.name;
};
let f2 = () => this.name;

let obj = { name: "obj_name" };

console.log(f1.call(obj));  //obj_name
console.log(f2.call(obj));  // window_name
console.log(f1.apply(obj)); // obj_name
console.log(f2.apply(obj)); // window_name
console.log(f1.bind(obj)());  // obj_name
console.log(f2.bind(obj)());  // window_name

6. 箭头函数没有原型对象prototype这个属性

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

let F = ()=>{};
console.log(F.prototype) // undefined

7. 不能使用yield关键字,不能用作Generator函数

arguments辨析

既然上文我们提到了arguments,那么下面我们就仔细讲讲这个arguments

arguments有什么用?

arguments对象是所有非箭头函数中都可用的局部变量,可以使用arguments对象在函数中引用函数的参数,此对象包含传递给函数的每一个参数,第一个参数在索引0的位置。

如何将arguments对象转换为数组

  1. 通过slice
  2. 通过拓展运算符
  3. 通过Array.from
var args = Array.prototype.slice.call(arguments);
var args = [].slice.call(arguments);

const args = Array.from(arguments);
const args = [...arguments];

arguments函数如何调用自身函数?

我们先看看下面这个函数,这个是可以正常运行的。

function factorial (n) {
    return !(n > 1) ? 1 : factorial(n - 1) * n;
}

[1,2,3,4,5].map(factorial);

但是作为匿名函数则不行。

[1,2,3,4,5].map(function (n) {
    return !(n > 1) ? 1 : /* what goes here? */ (n - 1) * n;
});

因此arguments.callee诞生了。

arguments要想调用自身的匿名函数,可以通过arguments.callee来调用。

[1,2,3,4,5].map(function (n) {
    return !(n > 1) ? 1 : arguments.callee(n - 1) * n;
});

然而,由于一些安全和性能问题,arguments.callee已经被弃用,并且在严格模式下会导致错误。

用如下方法代替。

[1,2,3,4,5].map(function myArg(n) {
    return !(n > 1) ? 1 : myArg(n - 1) * n;
});