在 JavaScript 中,函数是一等公民,是代码执行、作用域创建、上下文切换的核心载体,而this 指向 是函数运行时上下文的核心标识。不同于面向对象语言中固定的 this 指向,JS 的 this 是动态绑定,由函数调用方式而非定义方式决定。
this 绑定规则、call/apply/bind 手动绑定、箭头函数特性、函数声明与表达式差异、new 运算符底层机制,是 JS 执行上下文体系的核心骨架,也是前端面试最高频、最易混淆、必考的底层知识模块。本文将系统性拆解全套原理,逐层剖析规则差异,搭配对应实战案例与嵌入式面试真题,实现理论与刷题一体化掌握。
一、函数核心基础:声明式函数 vs 表达式函数
函数是 JS 最基础的执行单元,分为函数声明与函数表达式两大类,二者在提升机制、执行方式、this 优先级上存在本质差异,是理解函数执行与 this 绑定的前置基础。
1.1 函数声明(Function Declaration)
以 function关键字开头、携带函数名的独立语句,称为函数声明。
// 标准函数声明
function fn() {
console.log("函数声明执行");
}
核心特性:
1、具备整体函数提升,预解析阶段直接提升至作用域顶部,支持先调用、后定义;
2、拥有独立函数作用域,this 遵循普通函数绑定规则;
3、无法直接参与运算、立即执行,必须通过转换为表达式才可 IIFE 执行。
1.2 函数表达式(Function Expression)
函数作为值参与赋值、运算、包裹的写法,统称为函数表达式,包含匿名、具名函数表达式。
// 匿名函数表达式
const fn = function() {
console.log("函数表达式执行");
}
// 具名函数表达式
const test = function inner() {}
核心特性:
1、无整体提升,仅遵循变量提升规则,存在暂时性死区,必须先定义后调用;
2、具名函数表达式的内部函数名仅内部可访问,不会泄露到外层作用域;
3、可直接参与运算、立即执行,灵活性更高,是现代开发主流写法。
1.3 二者核心差异总结
1、提升机制:声明整体提升,表达式仅变量提升;
2、执行时机:声明可先调用后定义,表达式必须先定义后调用;
3、使用场景:声明适合全局通用方法,表达式适合回调、赋值、模块化封装。
二、this 核心底层认知
this 是函数运行时的上下文对象,用于标识当前函数的调用主体。
JS 中 this 最大的特点:this 指向与函数定义位置无关,只与函数调用方式有关。函数定义时 this 无确定值,只有在调用瞬间才会绑定指向。
this 的所有绑定规则存在优先级层级,优先级从低到高依次递增,高优先级规则会覆盖低优先级规则。
三、this 五大绑定规则(核心必考)
this 绑定共分为五种标准规则,覆盖 JS 所有函数调用场景,是面试刷题、代码纠错的核心依据。
3.1 默认绑定(优先级最低)
适用场景:纯函数独立调用,无任何修饰、无调用主体、无绑定。
绑定规则:
1、非严格模式:this 指向 window(浏览器)/ global(Node);
2、严格模式:this 指向 undefined。
// 非严格模式
function fn() {
console.log(this); // window
}
fn();
// 严格模式
"use strict";
function fn2() {
console.log(this); // undefined
}
fn2();
3.2 隐式绑定(最常用)
适用场景:函数被对象调用,存在调用主体。
绑定规则:谁调用,this 指向谁,指向调用函数的前置对象。
const obj = {
name: "前端",
fn: function() {
console.log(this.name);
}
}
obj.fn(); // this 指向 obj,输出 前端
经典陷阱:隐式丢失
当对象方法被单独赋值、解构、回调传递时,会丢失宿主对象,退化为默认绑定。
const newFn = obj.fn;
newFn(); // 纯独立调用,this 指向 window/undefined,隐式丢失
3.3 显式绑定(call / apply / bind)
适用场景:手动强制修改函数 this 指向,主动指定绑定对象。
JS 提供三个核心方法实现手动绑定:call、apply、bind,优先级高于默认绑定、隐式绑定。
3.3.1 call 与 apply(立即执行绑定)
二者功能完全一致,唯一区别为传参形式不同。
call:参数逐个传递;apply:参数以数组形式传递。
function fn(a, b) {
console.log(this.name, a, b);
}
const obj = {name: "测试"};
fn.call(obj, 10, 20);
fn.apply(obj, [10, 20]);
3.3.2 bind(永久绑定)
bind 不会立即执行函数,而是返回一个永久绑定 this 的新函数,绑定一次、永久生效,无法被再次修改(多次 bind 无效)。
const newFn = fn.bind(obj);
newFn();
3.3.3 三者核心区别总结
1、执行特性:call/apply 立即执行,bind 返回新函数、延迟执行;
2、传参差异:call 散参、apply 数组参;
3、绑定特性:bind 永久绑定,优先级高于 call/apply。
3.4 new 绑定(优先级极高)
适用场景:通过 new 关键字调用构造函数,生成实例对象。
绑定规则:this 指向全新创建的实例对象。
function Person(name) {
this.name = name; // this 指向新实例
}
const p = new Person("张三");
console.log(p.name); // 张三
3.5 箭头函数绑定(优先级最高、特殊规则)
箭头函数是 ES6 全新语法,无自身 this、无 arguments、无 prototype、不能 new,是完全区别于普通函数的特殊函数。
绑定规则:箭头函数没有独立 this,其 this 指向定义位置的上层作用域 this,遵循词法作用域,绑定永久固定,不受任何绑定规则修改。
const obj = {
name: "测试",
fn: () => {
console.log(this.name);
}
}
obj.fn(); // this 指向外层全局 this,而非 obj
核心特性:
1、不参与 this 优先级排序,自身无绑定机制,继承上层 this;
2、无法通过 call/apply/bind 修改 this;
3、不能作为构造函数,不支持 new 调用;
4、完美解决定时器、回调函数 this 丢失问题。
四、this 绑定完整优先级(必背)
从低优先级到高优先级排序,高优先级覆盖低优先级:
默认绑定 < 隐式绑定 < call/apply 显式绑定 < bind 永久绑定 < new 绑定 < 箭头函数词法绑定
核心结论:箭头函数 this 优先级最高,一旦确定无法修改;new 绑定优先级高于所有手动绑定。
五、new 运算符底层执行原理(重难点)
new 是前端核心底层考点,使用 new 调用构造函数时,内部会自动执行四步底层逻辑,完成实例创建与 this 绑定。
new 四步底层原理:
1、创建空对象:在内存中开辟空间,创建一个全新的空实例对象;
2、绑定原型:将空对象的__proto__ 指向构造函数的 prototype,挂载原型链;
3、绑定 this 并执行:将构造函数内部 this 绑定到当前新实例,执行构造函数代码,挂载实例属性;
4、返回实例:若构造函数无手动 return 对象,自动返回新创建的实例;若手动返回引用类型,则覆盖默认实例。
function Person(name) {
this.name = name;
// return {age:18}; // 手动返回对象,覆盖实例
}
const p = new Person("李四");
六、高频易错点与实战场景总结
6.1 定时器 this 丢失
定时器回调为独立默认绑定,普通函数 this 指向 window,箭头函数继承外层 this,完美解决丢失问题。
6.2 数组方法回调 this
forEach、map 等方法支持 thisArg 绑定,可手动指定回调 this 指向。
6.3 箭头函数不能用于构造函数
箭头函数无 this、无 prototype,无法通过 new 实例化,报错提示不是构造器。
七、配套高频面试真题解析
真题一:普通函数 this 默认绑定辨析
题目:说出以下代码严格/非严格模式输出结果
function fn(){
console.log(this);
}
fn();
答案:非严格模式 window,严格模式 undefined
解析:独立纯调用走默认绑定,严格模式禁用全局指向,防止全局污染。
真题二:隐式绑定丢失经典题
题目:分析代码输出
const obj = {
name: "测试",
fn: function(){
console.log(this.name);
}
}
const f = obj.fn;
f();
答案:undefined(严格模式报错)
解析:方法赋值后脱离宿主对象,变为独立调用,隐式绑定失效,退化默认绑定。
真题三:call/apply/bind 优先级
题目:判断最终 this 指向
function fn(){
console.log(this);
}
const obj1 = {name:1};
const obj2 = {name:2};
fn.bind(obj1).call(obj2);
答案:指向 obj1
解析:bind 永久绑定优先级高于 call,多次绑定以第一次 bind 为准,后续绑定失效。
真题四:箭头函数 this 经典坑
题目:分析输出结果
const name = "全局";
const obj = {
name: "局部",
fn: () => {
console.log(this.name);
}
}
obj.fn();
答案:全局(浏览器环境)
解析:箭头函数在对象字面量中定义,对象不产生作用域,this 继承全局作用域,无法指向宿主对象。
真题五:new 绑定优先级面试题
题目:bind 绑定后 new 调用,this 指向?
function fn() {
console.log(this);
}
const newFn = fn.bind({name:"绑定"});
const ins = new newFn();
答案:this 指向 new 创建的新实例
解析:new 绑定优先级高于 bind 显式绑定,new 会覆盖手动绑定,重新指向实例。
真题六:函数声明与表达式提升差异
题目:判断代码执行结果
fn1();
fn2();
function fn1(){ console.log("声明"); }
var fn2 = function(){ console.log("表达式"); }
答案:fn1 正常执行,fn2 报错
解析:函数声明整体提升可先调用,函数表达式仅变量提升,变量为 undefined,调用触发类型错误。
真题七:手写简述 new 底层原理(口述必考)
标准答案:
1、创建一个全新的空 JS 对象;
2、将空对象的隐式原型 __proto__ 指向构造函数的显式原型 prototype;
3、将构造函数内部 this 绑定到新创建的空对象,执行构造函数代码;
4、若构造函数无手动返回引用类型数据,自动返回该实例对象。
八、全文终极背诵总结
1、函数差异:声明可提升、可先调用;表达式无提升、灵活性更高;
2、this 核心:this 看调用不看定义,六大场景逐级覆盖;
3、绑定优先级:默认 < 隐式 < 显式 < bind < new < 箭头函数;
4、三大方法:call/apply 立即执行、传参不同;bind 永久绑定、延迟执行;
5、箭头函数:无自身 this,继承上层词法 this,不可修改、不可 new;
6、new 原理:创建对象、绑定原型、绑定 this 执行、返回实例。