这是我参与8月更文挑战的第5天,活动详情查看:8月更文挑战
hello, 大家好,我是前端学长Joshua (公众号) 。
热心于做开源,写文章。目的为帮助在校大学生,刚入职场的小伙伴可以尽快搭建自己的前端学习体系。
如果你有学习上的困惑,欢迎关注我,找我交流,我实时回复大家。
初识普通函数 和 箭头函数
通常,我们要定义一个函数,有很多种方式。
目前大体可以分为两大类,
一类是通过function关键字来定义一个函数, 叫做 普通函数
另外一类就是使用ES6的箭头函数的语法,叫做 箭头函数
- 普通函数
// 声明式定义一个函数
function read() {
console.log('前端学长Joshua')
}
// 表达式定义一个函数
const read = function () {
console.log('前端学长Joshua')
}
- 箭头函数
const read = () => {
console.log('前端学长Joshua')
}
那么,我们就应该会有这样的一个思考,普通函数 和 箭头函数之间有什么区别呢?
普通函数 和 箭头函数之间的区别
this 指向
普通函数
在普通函数里边,this(也称为执行期上下文)的指向是动态的。
动态的执行期上下文,意味this指向取决于普通函数被调用的方式,一共有四种调用普通函数的方式
下面,我们一起来瞧瞧
- 直接对普通函数进行简单的调用, this指向全局对象window,在严格模式下是undefined
function read () {
console.log(this)
}
read() // window
- 普通函数作为对象上的方法属性,this指向这个对象
const myObject = {
method() {
console.log(this);
}
};
// 函数指向
myObject.method(); // myObject
- 通过 bind / apply 来改变this指向的,this指向call / apply的第一个参数
function myFunction() {
console.log(this);
}
const myContext = { value: 'A' };
myFunction.call(myContext); // logs { value: 'A' }
myFunction.apply(myContext); // logs { value: 'A' }
- 通过 new 关键字调用普通函数(作为构造函数),this指向构造出来的对象实例
function MyFunction() {
console.log(this);
}
new MyFunction(); // MyFunction函数构造出来的对象实例
箭头函数
在箭头函数中,是没有自己的执行期上下文的。
箭头函数的this是确定的,在定义函数时就被确定下来的,于外层的函数绑定好的
无论箭头函数在哪里执行,怎么执行,this永远指向外层包裹的函数的this
更加准确的说,箭头函数的this是指向外层包裹,最接近自己的普通函数的this
const myObject = {
myMethod(items) {
console.log(this); // logs myObject
const callback = () => {
console.log(this); // logs myObject
};
items.forEach(callback);
}
};
myObject.myMethod([1, 2, 3]);
上边例子: 箭头函数 callback() 中的 this 值等于外部函数 myMethod() 的 this 值。
关于箭头函数this,这个词法解析,是箭头函数的一大特点。
我们在使用箭头函数时,要记住箭头函数是没有自己的this的,它的this就是外边最接近的普通函数的this
所以,我们以后就可以避免写:
const self = this
或
callback.bind(this)
关于call / apply这点
与普通函数相反,使用 myArrowFunc.call(thisVal) 或 myArrowFunc.apply(thisVal) 间接调用箭头函数不会改变 this 的值:上下文值始终按词法解析。
但是,如果是在全局下定义的箭头函数, this指向window
var read = () => {
console.log(this)
}
var obj = {
read: read
}
obj.read() // window
构造函数
普通函数
通过普通函数,可以很轻易地构造出一个对象实例出来
function Car(color) {
this.color = color;
}
const redCar = new Car('red');
redCar instanceof Car; // => true
Car 是一个普通函数,使用new关键字调用这个函数时,就会有一个Car的对象实例被创建出来
箭头函数
由于this的词法解析的结果,箭头函数不能用作构造函数。
如果您尝试调用以 new 关键字为前缀的箭头函数,JavaScript 会抛出错误:
const Car = (color) => {
this.color = color;
};
const redCar = new Car('red'); // TypeError: Car is not a constructor
调用 new Car('red'),其中 Car 是一个箭头函数,会抛出 TypeError: Car is not a constructor。
arguments 对象
普通函数
在普通函数中,关键字arguments是类数组对象,它一系列函数执行时传递的参数
function myFunction() {
console.log(arguments);}
myFunction('a', 'b'); // logs { 0: 'a', 1: 'b', length: 2 }
在 myFunction() 内部,参数是一个类似数组的对象,其中包含调用参数:'a' 和 'b'。
箭头函数
箭头函数, 是没有arguments关键字。
与this相同,arguments 关键字在词法上的解析:箭头函数从外部函数访问arguments。
也就是, this / arguments永远指向外层包裹的函数的 this / arguments
function outer() {
const inner = () => {
console.log(arguments);
}
inner('c', 'd');
}
outer('a', 'b'); // logs { 0: 'a', 1: 'b', length: 2 }
我们可以看见,箭头函数inner, 执行时被传递了参数 - 'c', 'd'。但是箭头函数inner内部打印时,是打印外部的函数outer的arguments
疑问,那么如果箭头函数 希望像 普通函数获取arguemts关键字那样方便的获取参数地话,要怎么办呢?
好想法,我们可以使用...
操作符
function myRegularFunction() {
const myArrowFunction = (...args) => {
console.log(args);
}
myArrowFunction('c', 'd');
}
myRegularFunction('a', 'b'); // logs ['c', 'd']
执行箭头函数时,...args
就是会收集箭头函数的参数 --- ['c', 'd']
隐形的 return
普通函数
在普通函数中,有return XXX
就返回 XXX
如果是 return
或者 是没有 return, 就是返回undefined
function myFunction() {
return 42;
}
function myEmptyFunction() {
42;
}
function myEmptyFunction2() {
42;
return;
}
myFunction(); // => 42
myEmptyFunction(); // => undefined
myEmptyFunction2(); // => undefined
箭头函数
在箭头函数中,可以和普通函数进行一样的返回操作,但是有一个简洁的使用方式,不需使用return关键字也可以返回我们需要的值
如果箭头函数只是包含一个表达式,并且您省略了该函数的花括号,则该表达式将被隐式返回。 这些是内联箭头函数。
const increment = (num) => num + 1;
increment(41); // => 42
increment() 箭头只包含一个表达式:num + 1。这个表达式由箭头函数隐式返回,没有使用 return 关键字。
在class中的方法属性
在class中this执行问题
- 普通函数
使用普通函数作为方法属性:
class Hero {
constructor(heroName) {
this.heroName = heroName;
}
logName() { console.log(this.heroName); }}
const batman = new Hero('Batman');
有时候呀,我需要使用batman.logName
回调函数,比如是 事件的回调 或者是 setTimeout() 的回调,那么这个时候,会有点小问题
setTimeout(batman.logName, 1000);
// after 1 second logs "undefined"
有解决办法嘛?有, 可以通过call / apply来改变this 指向
setTimeout(batman.logName.bind(batman), 1000);
// after 1 second logs "Batman"
但是,如果代码一多,这就会出现很多样板式的代码,阅读性不高
更好的法子是,在class中使用箭头函数
2,箭头函数
class Hero {
constructor(heroName) {
this.heroName = heroName;
}
logName = () => { console.log(this.heroName); }}
const batman = new Hero('Batman');
setTimeout(batman.logName, 1000);
// after 1 second logs "Batman"
现在,与普通函数相比,使用箭头定义的方法在词法上将 this 指向了类实例。
当然,你可能和我一样会想,箭头函数的this是怎么和类实例绑定到一起的呢?
我们通过babel编译下:
"use strict";
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
class Hero {
constructor(heroName) {
_defineProperty(this, "logName", () => {
console.log(this.heroName);
});
this.heroName = heroName;
}
}
const batman = new Hero("Batman");
本质上,是通过Object.defineProperty将class的this和logName函数进行绑定。而对于普通函数,是不将this 和 普通对象进行绑定的。
总结
- this指向:在普通函数中,是动态的,依赖于函数的调用;在箭头函数中,因为语法解析,this指向是确定的,this是与外层函数绑定的,如果是最外层没有函数就是window
- arguments: 在普通函数中,可以获取到所有的参数;在箭头函数中,arguments是指向外层的函数的arguments的。如果想要获取到统一获取到箭头函数的参数,可以使用
…
操作符 - return: 箭头函数如果只有一个表达式,这个表达式就会被隐式返回,而且不需要使用return关键字
- 我们可以在class中使用箭头函数,this会和类实例进行绑定
上述就是普通函数 与 箭头函数之间的区别,希望有用❤❤❤
动下小手
- 欢迎关注我的GitHub:@huangyangquang ⭐⭐
- 欢迎关注我的公众号:前端学长Joshua