做前端开发时,不少同学会纠结「箭头函数和传统函数该咋选」,明明代码里两种写法都能跑,可有时候换个写法就报错,或者this指向乱套……其实箭头函数和传统函数在语法、this绑定、使用场景等方面差别不小,把这些区别掰碎了理解,写代码时才不会踩坑,今天就从多个角度聊聊它们到底不一样在哪~
传统函数靠`function`关键字声明,写法分**函数声明**和**函数表达式**,“冗余”的语法让逻辑边界更清晰;箭头函数用`=>`简化表达,在简单场景下像“语法糖”一样轻快。
传统函数的两种形态
函数声明:以
function
开头,名字紧跟其后,能被JS引擎“提升”(代码执行前就加载,所以可以在声明前调用)。// 函数声明,可在声明前调用 sayHello(); // 输出「你好」 function sayHello() { console.log('你好'); }
函数表达式:把函数赋值给变量/属性,不存在提升,必须先定义后调用,常见写法是匿名函数(也能写具名函数,但具名在外部无法访问):
// 函数表达式,先调用会报错 const add = function(a, b) { return a + b; }; console.log(add(1,2)); // 输出3
箭头函数的极简风格
箭头函数只有表达式形式(不能当函数声明用,因为没有函数名,也不存在提升),但胜在“能省则省”:
无参数:必须用占位,比如定时打印:
const tick = () => console.log('一秒过去啦~');
单个参数:可省略,写起来像“参数→返回值”的映射:
const square = num => num * num;
// 传入num,返回num²多个参数:必须保留,比如计算两数之和:
const sum = (a, b) => a + b;
多行逻辑:需用包裹,且
return
要手动写(否则默认返回undefined
):const complexCalc = (a, b) => { const temp = a * b; const result = temp + a - b; return result; // 必须写return,否则返回undefined };
这种“极简”语法让箭头函数在处理简单逻辑时更高效,但遇到复杂逻辑(比如多变量声明、循环、条件判断),传统函数的大括号和显式return
反而让代码结构更易读。
this指向:「动态绑定」vs「静态绑定」
this是JS里的“老大难”,箭头函数和传统函数的this绑定时机、规则完全不同——传统函数“谁调用指向谁”(动态绑定),箭头函数“定义时继承外层this”(静态绑定)。
传统函数:运行时“看调用者”
传统函数的this是运行时绑定,谁调用它,this就指向谁,不同场景下表现不同,甚至严格模式('use strict'
)会改变规则:
普通调用 + 非严格模式:this指向全局对象(浏览器
window
,Node.jsglobal
)。function logThis() { console.log(this); } logThis(); // 输出window
普通调用 + 严格模式:this是
undefined
(防止误操作全局对象)。'use strict'; function logThis() { console.log(this); } logThis(); // 输出undefined
作为对象方法:this指向“调用时的对象”,哪怕方法被赋值给其他变量,this也会跟着调用者变:
const person = { name: '小李', sayName: function() { console.log(this.name); } }; person.sayName(); // 输出「小李」(this指向person) const anotherSay = person.sayName; anotherSay(); // 非严格模式下输出window.name(空字符串),严格模式下报错
事件回调/定时器:this指向“触发者”(事件元素、全局对象),比如给按钮绑定点击事件:
const btn = document.querySelector('button'); btn.addEventListener('click', function() { console.log(this); // 点击时输出button元素 });
被call/apply/bind修改:这三个方法能强制改变传统函数的this指向。
function showThis() { console.log(this.name); } const obj = { name: '被修改的this' }; showThis.call(obj); // 输出「被修改的this」
箭头函数:定义时“锁死外层this”
箭头函数的this是定义时绑定,一旦定义,this就固定为“外层作用域的this”,之后永远不变,也不受call/apply/bind影响。
举几个场景感受下:
对象方法里用箭头函数:this会指向外层作用域(比如全局window),而非对象本身,看反例:
const person = { name: '小红', sayName: () => { console.log(this.name); // this指向window,window.name默认空 } }; person.sayName(); // 输出空字符串
想让对象方法的this指向对象,必须用传统函数。
事件回调里用箭头函数:this不是触发事件的元素,而是定义时的外层this。
const btn = document.querySelector('button'); btn.addEventListener('click', () => { console.log(this); // 输出window,而非button元素 });
所以如果事件回调需要操作当前元素(比如改样式),传统函数更稳妥。
定时器/延时器里的this:传统函数的this是window(因为定时器由全局对象调用),但箭头函数能继承外层作用域的this,比如在Vue组件里:
export default { data() { return { msg: '定时更新' } }, mounted() { // 传统函数写法,this会变成window,拿不到msg setTimeout(function() { console.log(this.msg); // undefined }, 1000); // 箭头函数写法,this继承mounted里的this(组件实例) setTimeout(() => { console.log(this.msg); // 输出「定时更新」 }, 1000); } };
这种“保留外层this”的特性,让箭头函数在异步回调里特别省心。
能不能当构造函数?箭头函数说「不行」
传统函数能当构造函数,用new
创建实例;但箭头函数完全不能当构造函数,用new
会直接报错。
传统函数:构造实例的“标配”
构造函数的核心逻辑是:new
时JS引擎会做这几步:
创建空对象;
让空对象的
__proto__
指向构造函数的prototype
;调用构造函数,把this绑定到空对象上;
若构造函数返回非原始值(对象、函数等),则返回这个值;否则返回空对象。
传统函数有prototype
属性,所以能走通这套流程,比如实现“动物”构造函数:
function Animal(type) { this.type = type; // new时,this指向新创建的空对象 } Animal.prototype.speak = function() { console.log(`我是${this.type}`); }; const dog = new Animal('狗'); dog.speak(); // 输出「我是狗」 console.log(dog.__proto__ === Animal.prototype); // true(原型链关联成功)
箭头函数:构造函数的“绝缘体”
箭头函数没有prototype
属性,new
操作时JS引擎会先检查函数是否有prototype
——没有的话直接抛错TypeError: XXX is not a constructor
。
就算强行在箭头函数里写this.xxx
,也会因为this不是实例对象而报错(严格模式下还会因this是undefined
报错):
const FakeAnimal = () => { this.type = '假动物'; // 非严格模式下this是window,严格模式下this是undefined,都会报错 }; new FakeAnimal(); // 抛错:FakeAnimal is not a constructor
arguments对象:箭头函数「不支持」,传统函数「有专属」
传统函数里有个特殊的类数组对象arguments
,能拿到调用时传入的所有参数(不管形参有没有写全);但箭头函数里没有arguments
,得用剩余参数(rest parameters)...args
代替。
传统函数:arguments的“灵活”
传统函数的arguments
是类数组(有length
属性,能通过索引访问,但不是真正的数组),适合灵活获取所有入参。
function logArgs() { console.log(arguments); // 输出{0: 'a', 1: 'b', length: 2}这样的结构 console.log(arguments[0]); // 取第一个参数'a' } logArgs('a', 'b');
箭头函数:剩余参数的“替代”
箭头函数没有arguments
,但剩余参数...args
是真正的数组,能调用数组方法(如map
forEach
),且必须作为函数的最后一个参数。
const logArgs = (...args) => { console.log(args); // 输出数组['a', 'b'] console.log(args[0]); // 取第一个参数'a' args.forEach(item => console.log(item)); // 数组方法直接用 }; logArgs('a', 'b');
原型与new操作符:箭头函数「没原型」
传统函数天生有prototype
属性,是JS原型链继承的基础;但箭头函数没有prototype
,也没法通过new
创建实例。
传统函数:prototype是继承的“桥梁”
传统函数的prototype
是个对象,里面的constructor
又指回函数本身,当用new
创建实例时,实例的__proto__
会指向构造函数的prototype
,实现原型链继承。
比如前面的Animal
函数:
console.log(Animal.prototype); // 输出{constructor: ƒ, speak: ƒ}(constructor指向Animal函数) const dog = new Animal('狗'); console.log(dog.__proto__ === Animal.prototype); // true(原型链关联成功)
箭头函数:没有prototype
箭头函数的prototype
属性是undefined
,打印验证:
const func = () => {}; console.log(func.prototype); // 输出undefined
该用箭头函数还是传统函数?看场景选
知道了区别,得学会“对症下药”,总结几个典型场景:
场景1:需要动态this → 选传统函数
写对象的方法(比如
person.sayName
),希望this指向对象本身;写构造函数,用
new
创建实例;事件回调需要this是触发事件的DOM元素(比如
btn.addEventListener
里的回调要操作btn本身);需要用
call/apply/bind
强制改this指向。
场景2:需要固定this(继承外层this) → 选箭头函数
异步回调(定时器
setTimeout
、Promise的then/catch
),避免传统函数this变成window/global
;数组迭代方法(
map
filter
reduce
等),简化写法同时保证this指向外层作用域;简洁的单行逻辑(比如返回一个计算结果、处理参数),用箭头函数省代码。
场景3:需要arguments对象 → 传统函数更直接
如果逻辑里要灵活获取所有入参(不管有没有定义形参),传统函数的arguments
更顺手;要是用箭头函数,得换成剩余参数...args
,不过剩余参数是数组,处理起来更灵活(比如直接args.forEach
)。
综合例子:场景选择实战
// 对象方法:需要this指向对象 → 传统函数 const cart = { goods: ['苹果', '香蕉'], showGoods: function() { console.log('购物车商品:', this.goods); } }; cart.showGoods(); // 输出购物车商品:['苹果','香蕉'] // 数组map回调:需要this继承外层(假设外层有discount变量)→ 箭头函数 const discount = 0.8; const prices = [10, 20, 30]; const finalPrices = prices.map(price => price * discount); console.log(finalPrices); // 输出[8, 16, 24] // 构造函数:创建实例 → 传统函数 function Product(name, price) { this.name = name; this.price = price; } const apple = new Product('苹果', 10); console.log(apple.name); // 输出「苹果」 // 定时器回调:需要this是组件实例(Vue场景)→ 箭头函数 export default { data() { return { time: '00:00' } }, mounted() { setInterval(() => { this.time = new Date().toLocaleTimeString(); // this继承mounted的this(组件实例) }, 1000); } };
箭头函数和传统函数不是「谁代替谁」,而是「各有擅长场景」,记住核心区别:语法简洁度、this绑定规则、构造函数能力、arguments支持、原型存在性,写代码前想清楚「我需要this怎么变?需不需要当构造函数?参数咋处理?」,选对了写法,代码少踩坑,逻辑更清晰~要是刚开始分不清,多写例子对比,用着用着就熟啦~
网友回答文明上网理性发言 已有0人参与
发表评论: