简述
理解并讲述 ES6 新的定义函数的方式:Arrow Function,内容有误请指正。
语法
箭头函数是 ES6 中提出的新的定义函数的方式,语法较之传统函数声明与函数表达式更简洁,箭头函数只能以表达式的形式定义。
// () 参数列表// => 胖箭头// { ... } 函数体const add = (a, b) => { return a + b;};
console.log(add(1, 2)); // 3
复制代码
箭头函数在有且仅有一个参数的情况下,能够省略 ()
// 省略 ()const print = (charts) => { console.log(charts);};
print('Hello JavaScript!'); // Hello JavaScript!
复制代码
此外,如果函数体内只有一个表达式,可以省略包裹函数体的 {},且表达式的结果会被当作函数的返回值
// 函数体内只有一个表达式,省略 {}const isString = (_unknown) => typeof _unknown === 'string';
console.log(isString('Learn JavaScript')); // trueconsole.log(isString({})); // false
复制代码
当省略花括号 {} 且函数返回一个对象时,应使用 () 包裹
// 返回对象const getPersonObj = (name, age) => ({ // 使用 () 包裹 name: name, age: age, sayHi() { console.log('hello, my name is ' + this.name + '.'); }});
const me = getPersonObj('yuanyxh', 22);me.sayHi(); // hello, my name is yuanyxh.
复制代码
这是因为 JS 引擎在大部分场景下遇到 { 会将它看作是代码块 { ... } 的左半部分,而非对象。
特性
箭头函数除了语法更简洁之外,还存在一些程序猿应该知晓的特性,只有了解这些特性才能更好的发挥出箭头函数的作用。
箭头函数没有原型对象,即 prototype 属性,也意味着它不能被当作构造函数,被 new 调用
// 声明箭头函数const ArrowFun = () => {};
// 没有原型对象console.log(ArrowFun.prototype); // undefined
// 无法以 new 调用new ArrowFun(); // TypeError
复制代码
箭头函数的 this 永远指向最近的上下文的 this,且无法被 call、bind、apply 改变
// 全局作用域下const arrow_global = () => console.log(this);arrow_global(); // window
// 函数作用域下function getThisArgs() { const arrow_func = () => console.log(this); arrow_func(); // { name: 'yuanyxh', age: 22 }}// getThisArgs 绑定 thisgetThisArgs.call({ name: 'yuanyxh', age: 22});
复制代码
const owner = { count: 6, add(num) { const inner = { count: 0 }; const fn = (v) => v + this.count; return fn.call(inner, num); // 修改 this 指向 }};console.log(owner.add(2)); // 8 -> 修改 this 指向无效!!!
复制代码
箭头函数没有自己的 arguments、super 与 new.target,若最近的上下文拥有这些 标识 则引用上下文的,否则抛出错误
// 全局作用域下,箭头函数没有自己的 argumentsconst arrow_global = () => console.log(arguments);arrow(); // ReferenceError
// 函数作用域下,箭头函数引用了 owner 的 argumentsfunction owner() { const args = arguments; const inner = () => { console.log(args === arguments); // true (() => console.log(args === arguments))(); // true }; inner();}owner();
复制代码
super 是 ES6 的另一个新特性,配合 ES6 class 使用,用于在子类引用父类构造器或父类方法
// 父类class Person { sayHi() { console.log('hi'); }}// 子类class Son extends Person { constructor() { super(); } sayHi() { // 箭头函数引用最近上下文的 super const arrow = () => super.sayHi(); arrow(); }}// 构造实例new Son().sayHi(); // hi
复制代码
new.target 指向 new 调用的目标构造函数或类,当以普通方式调用时,new.target 值为 undefined
// 全局作用域下,箭头函数无法使用 new.targetconst arrow = () => console.log(new.target);arrow(); // SyntaxError
// 函数作用域下function CheckNewTarget() { // 箭头函数引用 CheckNewTarget 的 new.target const arrow_func = () => console.log(new.target === CheckNewTarget); arrow_func();}new CheckNewTarget(); // true
复制代码
实践
JS 中的 this 是动态绑定的,在不同场景下会出现 this 指向与预料不同的情况,如
假设有一个按钮,点击出现倒计时并禁用
// 获取按钮const btn = document.querySelector('.btn');// 添加事件btn.addEventListener('click', function () { let i = 10, timer = null; this.disabled = true; // 每隔一秒执行回调 timer = setInterval(function () { i--; this.innerHTML = '还有' + i + '秒';
if (i <= 0) { clearInterval(timer); this.disabled = false; this.innerHTML = '点击倒计时'; } }, 1000);});
复制代码
运行以上代码,会发现点击后除了按钮被禁用外,按钮文本并没有变化,甚至在严格模式下还会报错,这是因为传递给 setInterval 的函数内部 this 指向 window,在严格模式下为 undefined
const obj = { sayHi() { console.log(this); } asyncSayHi() { setTimeout(this.sayHi, 1000); }};
obj.sayHi(); // objobj.asyncSayHi(); // window
复制代码
可以看到 sayHi 方法经由 obj 调用,this 指向 obj,而传递给 setTimeout 的 sayHi 方法在定时结束后是直接调用的,并没有绑定到谁身上。
在箭头函数出现之前,开发人员经常使用一个临时变量来存储 this 的值
// 获取按钮const btn = document.querySelector('.btn');// 添加事件btn.addEventListener('click', function () { let i = 10, timer = null;
// 存储 this const _self = this;
this.disabled = true; // 每隔一秒执行回调 timer = setInterval(function () { i--;
// 使用临时变量修改状态 _self.innerHTML = '还有' + i + '秒';
if (i <= 0) { // 清除定时器并重置状态 clearInterval(timer); _self.innerHTML = '点击倒计时'; _self.disabled = false; } }, 1000);});
复制代码
而使用箭头函数则不必再分配一个变量,因为箭头函数的 this 永远指向最近的上下文的 this
// 获取按钮const btn = document.querySelector('.btn');// 添加事件btn.addEventListener('click', function () { let i = 10, timer = null;
// const _self = this;
this.disabled = true; // 每隔一秒执行回调 timer = setInterval(() => { i--;
// _self.innerHTML = '还有' + i + '秒';
// this 指向正常 this.innerHTML = '还有' + i + '秒';
if (i <= 0) { // 清除定时器并重置状态 clearInterval(timer); // _self.innerHTML = '点击倒计时'; // _self.disabled = false;
this.innerHTML = '点击倒计时'; this.disabled = false; } }, 1000);});
复制代码
结语
本文介绍了箭头函数的语法与相关特性。虽然箭头函数语法更简洁,但不应该滥用,不应该为了节省几句代码而忽视了箭头函数的特性。
参考资料
评论