简单谈谈
对于箭头函数的出现,我是这样理解的:
function
在 JavaScript
中是一种类似与流程的一种存在,一个 function
代表一种处理数据的逻辑。在 ES6
中, function
的这一特性被进一步的认可和强调,因此产生了箭头函数。
箭头函数
let f = v => v;
上面的箭头函数的约等于以下函数
var f = function(v) {
return v;
};
当然函数参数存在没有或者多个情况
var f = () => 5;
// 约等于
var f = function () {
return 5;
};
var sum = (num1, num2) => num1 + num2;
// 约等于
var sum = function(num1, num2) {
return num1 + num2;
};
当箭头函数的代码块部分多于一条语句,就需要使用大括号括起来,并使用 return
语句返回。
var sum = (num1, num2) => {
return num1 + num2
}
由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,就必须在对象外面加上大括号。
// 报错
let getTempItem = id => { id: id, name: "Temp" }
// 不报错
let getTempItem = id => ({ id: id, name: "Temp" })
当然箭头函数也是函数,拥有函数的所有功能。
- 参数默认值
- 参数的变量解构
rest
参数
const full = ({ first, last } = { first:'xxx', last:'xxxx' }) => first + ' ' + last
箭头函数的使用技巧
// 正常函数写法
[1, 2, 3].map(function (x) {
return x * x;
})
// 箭头函数写法
[1, 2, 3].map(x => x * x);
// 正常函数写法
var result = values.sort(function (a, b) {
return a - b
});
// 箭头函数写法
var result = values.sort((a, b) => a - b)
const numbers = (...nums) => nums
numbers(1, 2, 3, 4, 5)
// [1, 2, 3, 4, 5]
使用注意点
- 函数体内的
this
对象,是定义时所在的对象,不会随着调用环境的变化而变化。 - 不可以当作构造函数,也就是不能使用
new
。 - 不可以使用
arguments
对象,该对象在函数体内不存在,可以用rest
参数替代。 - 不可以使用
yield
命令,因此不能用作Generator
函数。
箭头函数中的 this 对象
在箭头函数中 this
对象是固定的,并不会因为调用环境的不同而不同
注意这句话:这里说明的是调用环境,而不是生成环境,箭头函数的 this
指向为生成环境,所以对函数的生成环境进行修改,this
下对应的值也是会发生变化的。
function foo() {
setTimeout(() => {
console.log('id:', this.id)
}, 100)
}
let id = 21
foo.call({ id: 42 })
// id: 42
这个例子中,setTimeout
中是一个箭头函数,设置在 100ms
后执行,一般的函数在执行时,才会去找 this
的指向,而箭头函数直接输出了 42
,而这个 42
是箭头函数生成是所在的对象(这里是 { id: 42 }
)所以输出的是 42
。
作为对比,可以用以下代码测试效果
function foo() {
setTimeout(function() {
console.log('id:', this.id)
}, 100)
}
var id = 21
foo.call({ id: 42 })
// id: 21
一个更完美解释了该现象的例子
function Timer() {
this.s1 = 0
this.s2 = 0
// 箭头函数
setInterval(() => this.s1++, 1000)
// 普通函数
setInterval(function () {
this.s2++
}, 1000)
}
var timer = new Timer()
setTimeout(() => console.log('s1: ', timer.s1), 3100)
setTimeout(() => console.log('s2: ', timer.s2), 3100)
// s1: 3
// s2: 0
箭头函数可以让 this
的指向固化,因此可以用来封装回调
var handler = {
id: '123456',
init: function() {
document.addEventListener('click', event => this.doSomething(event.type), false)
},
doSomething: function(type) {
console.log('Handling ' + type + ' for ' + this.id)
}
};
例子中调用 handler.init
时,由于内部是箭头函数,那么箭头函数内部的 this
即为 init
函数的 this
,也就是 handler
对象,而如果不是使用箭头函数,那么在执行回调时,this
将会指向 document
。
没有 this !
this
指向的固定化,并不是因为箭头函数内部有绑定 this
的机制,实际原因是箭头函数根本没有自己的 this
,导致内部的 this
就是外层代码块的 this
。正是因为它没有 this
,所以也就不能用作构造函数。
如果把 this
仅仅当一个单纯的变量看待的话,这一切就好理解了。
因此箭头函数转成 ES5
的代码时,需要特别处理 this
的指向。
// ES6
function foo() {
setTimeout(() => {
console.log('id:', this.id)
}, 100)
}
// 转换为 ES5 时需要处理 this 的指向问题
function foo() {
var _this = this
setTimeout(function () {
console.log('id:', _this.id)
}, 100)
}
箭头函数的注意点
this
,arguments
、super
、new.target
这4
个变量在箭头函数内部都是不存在的,指向外层函数的相应变量。- 由于箭头函数没有自己的
this
,所以不能使用call
、apply
、bind
这些方法去改变this
的指向。
双冒号运算符
函数绑定运算符是并排的两个冒号(::
),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即 this
对象),绑定到右边的函数上面。
foo::bar
// 等同于
bar.bind(foo)
foo::bar(...arguments)
// 等同于
bar.apply(foo, arguments)
const hasOwnProperty = Object.prototype.hasOwnProperty
function hasOwn(obj, key) {
return obj::hasOwnProperty(key);
}
因此双冒号运算符能固定函数执行的上下文环境的能力。
当需要固定的上下文环境和函数所属对象是同一个时,可以简写省略前面的对象
var method = obj::obj.foo
// 等同于
var method = ::obj.foo
let log = ::console.log
// 等同于
var log = console.log.bind(console)