×

前端开发:语法结构,写代码的「简洁度」差异

提问者:Terry2025.07.03浏览:56

前端开发

做前端开发时,不少同学会纠结「箭头函数和传统函数该咋选」,明明代码里两种写法都能跑,可有时候换个写法就报错,或者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引擎会做这几步:

  1. 创建空对象;

  2. 让空对象的__proto__指向构造函数的prototype

  3. 调用构造函数,把this绑定到空对象上;

  4. 若构造函数返回非原始值(对象、函数等),则返回这个值;否则返回空对象。

传统函数有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人参与

发表评论: