JavaScript this绑定规则完全指南—四大规则与硬绑定实现|新宇宙博客
代码示例 1:同一函数不同 this function identify ( ) {
return this .name ;
}
const alice = { name : 'Alice' , identify };
const bob = { name : 'Bob' , identify };
console .log (alice.identify ());
console .log (bob.identify ());
console .log (identify ());
2. 默认绑定规则 当函数以独立形式调用时,this 绑定到全局对象(非严格模式)或 undefined(严格模式):
function showThis ( ) {
console .log (this );
}
showThis ();
'use strict' ;
function showThisStrict ( ) {
console .log (this );
}
showThisStrict ();
模块顶层的 this 是 module.exports(CommonJS)或 undefined(ESM)
全局的 this 在 REPL 中是 global
文件中顶层 this === module.exports(CJS),this === undefined(ESM)
console .log (this === module .exports );
console .log (this );
3. 隐式绑定与隐式丢失
3.1 隐式绑定 当函数作为对象方法调用时,this 绑定到该对象:
const timer = {
seconds : 0 ,
start ( ) {
console .log (`Timer this: ${this === timer} ` );
this .seconds ++;
}
};
timer.start ();
3.2 隐式丢失 — 最常见的 this 陷阱
const obj = {
value : 42 ,
getValue ( ) {
return this .value ;
}
};
const fn = obj.getValue ;
console .log (fn ());
setTimeout (obj.getValue , 0 );
function callFn (callback ) {
callback ();
}
callFn (obj.getValue );
const { getValue } = obj;
getValue ();
4. 显式绑定:call/apply/bind
4.1 call 和 apply
function greet (greeting, punctuation ) {
return `${greeting} , ${this .name} ${punctuation} ` ;
}
const person = { name : 'Alice' };
console .log (greet.call (person, 'Hello' , '!' ));
console .log (greet.apply (person, ['Hi' , '?' ]));
4.2 bind — 创建硬绑定函数 const boundGreet = greet.bind (person, 'Hey' );
console .log (boundGreet ('.' ));
const rebind = boundGreet.bind ({ name : 'Bob' });
console .log (rebind ('!' ));
5. new 绑定与构造过程 new 操作符的内部步骤(规范 §13.2.2):
function Person (name ) {
this .name = name;
}
const p = new Person ('Alice' );
console .log (p.name );
console .log (p instanceof Person );
构造函数返回值的影响
function Factory (name ) {
this .name = name;
return { custom : true };
}
const f = new Factory ('test' );
console .log (f.name );
console .log (f.custom );
function Factory2 (name ) {
this .name = name;
return 42 ;
}
const f2 = new Factory2 ('test' );
console .log (f2.name );
6. 箭头函数的词法 this 箭头函数没有自己的 this,继承自定义时 的词法环境:
const obj = {
value : 42 ,
regular ( ) {
setTimeout (function ( ) {
console .log (this .value );
}, 0 );
},
arrow ( ) {
setTimeout (() => {
console .log (this .value );
}, 0 );
}
};
obj.regular ();
obj.arrow ();
注意 :箭头函数不能被 new 调用,没有 prototype 属性,也不能通过 call/apply/bind 改变 this:
const arrowFn = ( ) => this ;
console .log (arrowFn.call ({ x : 1 }));
7. 绑定优先级 new 绑定 > 显式绑定 (call/apply/bind) > 隐式绑定 > 默认绑定
function foo ( ) {
return this .a ;
}
const obj1 = { a : 1 , foo };
const obj2 = { a : 2 , foo };
console .log (obj1.foo .call (obj2));
function Bar (val ) { this .a = val; }
const obj3 = { a : 3 , Bar };
const b = new obj3.Bar (4 );
console .log (b.a );
const BoundBar = Bar .bind ({ a : 5 });
const bb = new BoundBar (6 );
console .log (bb.a );
8. 硬绑定实现原理
手写 bind 实现
Function .prototype .myBind = function (thisArg, ...boundArgs ) {
if (typeof this !== 'function' ) {
throw new TypeError ('Bind must be called on a function' );
}
const targetFn = this ;
const fNOP = function ( ) {};
const boundFn = function (...callArgs ) {
const isNew = this instanceof fNOP;
return targetFn.apply (
isNew ? this : thisArg,
[...boundArgs, ...callArgs]
);
};
if (targetFn.prototype ) {
fNOP.prototype = targetFn.prototype ;
}
boundFn.prototype = new fNOP ();
return boundFn;
};
function Foo (x, y ) {
this .val = x + y;
}
const BoundFoo = Foo .myBind (null , 10 );
const instance = new BoundFoo (20 );
console .log (instance.val );
console .log (instance instanceof Foo );
手写 call/apply
Function .prototype .myCall = function (context, ...args ) {
context = context == null ? globalThis : Object (context);
const key = Symbol ('temp' );
context[key] = this ;
const result = context[key](...args);
delete context[key];
return result;
};
Function .prototype .myApply = function (context, args = [] ) {
context = context == null ? globalThis : Object (context);
const key = Symbol ('temp' );
context[key] = this ;
const result = context[key](...args);
delete context[key];
return result;
};
9. 实战案例
实战案例 1:React 类组件中的 this 问题 class Button extends React.Component {
constructor (props ) {
super (props);
this .state = { count : 0 };
this .handleClick = this .handleClick .bind (this );
}
handleClick ( ) {
this .setState (prev => ({ count : prev.count + 1 }));
}
handleClickArrow = () => {
this .setState (prev => ({ count : prev.count + 1 }));
};
render ( ) {
return (
<button onClick ={this.handleClick} >
{this.state.count}
</button >
);
}
}
bind 方案:每个实例共享原型上的方法,bind 后的函数在堆上分配
箭头函数方案:每个实例都有独立的函数副本,内存占用更大
在大列表渲染场景中,bind 方案更优
实战案例 2:事件委托中的 this 管理 class EventBus {
#handlers = new Map ();
on (event, handler, context ) {
if (!this .#handlers.has (event)) {
this .#handlers.set (event, []);
}
this .#handlers.get (event).push ({
fn : handler,
ctx : context || null
});
}
emit (event, ...args ) {
const handlers = this .#handlers.get (event) || [];
for (const { fn, ctx } of handlers) {
fn.apply (ctx, args);
}
}
off (event, handler ) {
const handlers = this .#handlers.get (event);
if (handlers) {
const idx = handlers.findIndex (h => h.fn === handler);
if (idx !== -1 ) handlers.splice (idx, 1 );
}
}
}
实战案例 3:链式调用模式 class QueryBuilder {
#conditions = [];
#table = '' ;
from (table ) {
this .#table = table;
return this ;
}
where (condition ) {
this .#conditions.push (condition);
return this ;
}
build ( ) {
const where = this .#conditions.length
? ` WHERE ${this .#conditions.join(' AND ' )} `
: '' ;
return `SELECT * FROM ${this .#table} ${where} ` ;
}
}
const sql = new QueryBuilder ()
.from ('users' )
.where ('age > 18' )
.where ('active = true' )
.build ();
console .log (sql);
10. 深度追问
Q1:为什么 bind 后的函数再次 bind 无效? 因为 bind 创建的是一个 exotic object(§10.4.1.1),其 [[Call]] 内部方法固定了 [[BoundThis]]。再次 bind 只是在外层又包了一层,但最内层的 [[BoundThis]] 不变。
Q2:Reflect.apply vs Function.prototype.apply 的区别? Reflect.apply 不会在 thisArg 为 null/undefined 时替换为全局对象(即使在非严格模式)。此外,如果目标不是函数,Reflect.apply 会抛出 TypeError,而不是在调用时才发现。
Q3:Class 中的方法默认为严格模式吗? 是的。ECMAScript 规范规定 class body 始终处于严格模式(§15.7.1),因此 class 中方法的默认绑定 this 为 undefined 而非 globalThis。
11. 总结表格 绑定规则 条件 this 值 优先级 new 绑定 new Foo()新创建的对象 最高 显式绑定 call/apply/bind指定的对象 高 隐式绑定 obj.method()obj 中 默认绑定 独立调用 foo() globalThis / undefined 低 箭头函数 定义时确定 继承外层 this N/A(词法)
场景 推荐方案 原因 事件处理器 箭头函数 / bind 避免隐式丢失 回调函数 箭头函数 简洁且安全 方法定义 普通函数 支持动态 this 工具函数 箭头函数 无 this 语义 构造函数 class / function 必须支持 new