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

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

箭头函数的this指向规则:

1、箭头函数没有prototype(原型),所以箭头函数本身没有this

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

2、箭头函数的this指向在定义的时候继承自外层第一个普通函数的this

下面例子中在一个函数中定义箭头函数,然后在另一个函数中执行箭头函数。

let a,
barObj = { msg: 'bar的this指向' },
fooObj = { msg: 'foo的this指向' };
bar.call(barObj); // 将bar的this指向barObj
foo.call(fooObj); // 将foo的this指向fooObj
function foo() {
  a(); // 结果:{ msg: 'bar的this指向' }
}
function bar() {
  a = () => {
    console.log(this, 'this指向定义的时候外层第一个普通函数'); // 
  }; // 在bar中定义 this继承于bar函数的this指向
}

从上面例子中可以得出两点:

  • 箭头函数的this指向定义时所在的外层第一个普通函数,跟使用位置没有关系;
  • 被继承的普通函数的this指向改变,箭头函数的this指向会跟着改变。

3、不能直接修改箭头函数的this指向

上个例子中的foo函数修改一下,尝试直接修改箭头函数的this指向。

let fnObj = { msg: '尝试直接修改箭头函数的this指向' };
function foo() {
  a.call(fnObj); // 结果:{ msg: 'bar的this指向' }
}

很明显,call显示绑定this指向失败了,包括apply、bind都一样。它们(call、aaply、bind)会默认忽略第一个参数,但是可以正常传参。

然后我又通过隐式绑定来尝试同样也失败了,new调用会报错,这个稍后再说。

SO,箭头函数不能直接修改它的this指向。

幸运的是,我们可以通过间接的形式来修改箭头函数的指向:

去修改被继承的普通函数的this指向,然后箭头函数的this指向也会跟着改变,这在上一个例子中有演示。

bar.call(barObj);// 将bar普通函数的this指向barObj 然后内部的箭头函数也会指向barObj

4、箭头函数外层没有普通函数,严格模式和非严格模式下它的this都会指向window(全局对象)

既然箭头函数的this指向在定义的时候继承自外层第一个普通函数的this,那么,当箭头函数外层没有普通函数时,它的this会指向哪里?

普通函数的默认绑定规则是:在非严格模式下,默认绑定的this指向全局对象,严格模式下this指向undefined。

如果箭头函数外层没有普通函数继承,它this指向的规则:经过测试,箭头函数在全局作用域下,严格模式和非严格模式下它的this都会指向window(全局对象)。

Tip:测试的时候发现严格模式在中途声明无效,必须在全局/函数的开头声明才会生效:

a = 1;
'use strict'; // 严格模式无效 必须在一开始就声明严格模式
b = 2; // 不报错

 

箭头函数的this指向全局时,使用arguments会报未声明的错误,指向普通函数时,它的argumens继承于该普通函数

如果箭头函数的this指向window(全局对象),使用arguments会报错,未声明arguments

let b = () => {
  console.log(arguments);
};
b(1, 2, 3, 4); // Uncaught ReferenceError: arguments is not defined

PS:如果你声明了一个全局变量为arguments,那就不会报错了,但是你为什么要这么做呢?

如果箭头函数的this指向普通函数,它的argumens继承于该普通函数。

function bar() {
  console.log(arguments); // ['外层第二个普通函数的参数']
  bb('外层第一个普通函数的参数');
  function bb() {
    console.log(arguments); // ["外层第一个普通函数的参数"]
    let a = () => {
      console.log(arguments, 'arguments继承this指向的那个普通函数'); // ["外层第一个普通函数的参数"]
    };
    a('箭头函数的参数'); // this指向bb
  }
}
bar('外层第二个普通函数的参数');

那么应该如何来获取箭头函数不定数量的参数呢?答案是:ES6rest参数(…扩展符)

rest参数获取函数的多余参数

这是ES6的API,用于获取函数不定数量的参数数组,这个API是用来替代arguments的,API用法如下:

let a = (first, ...abc) => {
  console.log(first, abc); // 1 [2, 3, 4]
};
a(1, 2, 3, 4); 

上面的例子展示了,获取函数除第一个确定的参数,以及用一个变量接收其他剩余参数的示例。

也可以直接接收函数的所有参数,rest参数的用法相对于arguments的优点:

(1)箭头函数和普通函数都可以使用。

(2)更加灵活,接收参数的数量完全自定义。

(3)可读性更好,参数都是在函数括号中定义的,不会突然出现一个arguments

(4)rest是一个真正的数组,可以使用数组的API。而arguments是一个类数组的对象,如果我们需要使用数组的API,需要使用扩展符/Array.from来将它转换成真正的数组。

rest参数有两点需要注意:

  • rest必须是函数的最后一位参数:
let a = (first, ...rest, three) => {
  console.log(first, rest,three); // 报错:Rest parameter must be last formal parameter
};
a(1, 2, 3, 4);
  • 函数的length属性,不包括rest参数
(function(...a) {}).length  // 0
(function(a, ...b) {}).length  // 1 

箭头函数不能作为构造函数,不能使用new

无论箭头函数的this指向哪里,使用new调用箭头函数都会报错,因为箭头函数没有constructor

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

箭头函数不支持new.target

new.target是ES6新引入的属性,普通函数如果通过new调用,new.target会返回该函数的引用。此属性主要用于确定构造函数是否为new调用的。

  • 箭头函数的this指向全局对象,在箭头函数中使用箭头函数会报错
let a = () => {
  console.log(new.target); // 报错:new.target 不允许在这里使用
};
a();
  • 箭头函数的this指向普通函数,它的new.target就是指向该普通函数的引用。
new bb();
function bb() {
  let a = () => {
    console.log(new.target); // 指向函数bb:function bb(){...}
  };
  a();
}

箭头函数不支持重命名函数参数,普通函数的函数参数支持重命名

如下示例,普通函数的函数参数支持重命名,后面出现的会覆盖前面的,箭头函数会抛出错误:

function func1(a, a) {
  console.log(a, arguments); // 2 [1,2]
}

var func2 = (a,a) => {
  console.log(a); // 报错:在此上下文中不允许重复参数名称
};
func1(1, 2); 
func2(1, 2);

箭头函数相对于普通函数语法更简洁优雅

  • 箭头函数都是匿名函数,并且都不用写function
  • 只有一个参数的时候可以省略括号:
var f = a => a; // 传入a 返回a
  • 函数只有一条语句时可以省略{}return
var f = (a,b,c) => a; // 传入a,b,c 返回a
  • 简化代码,让你的函数更优雅:
[1,2,3].map(function (x) {
  return x * x;
}); // 普通函数写法 
[1,2,3].map(x => x * x); // 箭头函数只需要一行

箭头函数不能当做Generator函数,不能使用yield关键字 

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

箭头函数的注意事项

  • 一条语句返回对象字面量,需要加括号,或者直接写成多条语句的return形式,否则像下面演示的一样,花括号会被解析为多条语句的花括号,不能正确解析
var func1 = () => { foo: 1 }; // 想返回一个对象,花括号被当成多条语句来解析,执行后返回undefined
var func2 = () => ({foo: 1}); // 用圆括号是正确的写法
var func2 = () => {
  return {
    foo: 1 // 更推荐直接当成多条语句的形式来写,可读性高
  };
};
  • 箭头函数在参数和箭头之间不能换行!
var func = ()
           => 1;  // 报错: Unexpected token =>
  • 箭头函数的解析顺序相对靠前   MDN: 虽然箭头函数中的箭头不是运算符,但箭头函数具有与常规函数不同的特殊运算符优先级解析规则
let a = false || function() {}; // ok
let b = false || () => {}; // Malformed arrow function parameter list
let c = false || (() => {}); // ok

箭头函数不适用场景

1、this的意外指向

  • 定义字面量方法
const obj = {
  array: [1, 2, 3],
  sum: () => {
    // 根据上文学到的:外层没有普通函数this会指向全局对象
    return this.array.push('全局对象下没有array,这里会报错'); // 找不到push方法
  }
};
obj.sum();

上述例子使用普通函数或者ES6中的方法简写来定义方法,就没有问题了:

// 这两种写法是等价的
sum() {
  return this.array.push('this指向obj');
}
sum: function() {
  return this.array.push('this指向obj');
}
  • 回调函数的动态this

下文是一个修改dom文本的操作,因为this指向错误,导致修改失败:

const button = document.getElementById('myButton');
button.addEventListener('click', () => {
    this.innerHTML = 'Clicked button'; // this又指向了全局
});

相信你也知道了,改成普通函数就成了。

2、考虑代码的可读性,使用普通函数

  • 函数体复杂:具体表现就是箭头函数中使用多个三元运算符号,就是不换行,非要在一行内写完,非常恶心!

  • 行数较多

  • 函数内部有大量操作


转自:https://blog.csdn.net/m0_37686205/article/details/88776259

总结

  • 箭头函数的this永远指向其上下文的this,任何方法都改变不了其指向
  • 普通函数的this指向调用它的那个对象

 

上一篇:2020-11-22


下一篇:Flutter 路由管理