# 前言

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

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;
});