你是否曾在 JS 的世界里被 this 搞得头晕脑胀?是否在面试时被问到 call/apply/bind/new 的底层原理时,脑袋一片空白?别慌!本文将带你用轻松幽默的方式,层层递进地揭开这些谜团,助你成为 JS 面试王者!😎
一、this 是什么?
this 是 JS 里一个神秘的关键字,指向当前函数的调用者。它能让代码更灵活,但也常常让人抓狂。this 的指向有四大规则:
- 默认绑定:独立调用,this 指向 window(浏览器)或 global(Node)。
- 隐式绑定:被对象调用,this 指向该对象。
- 显示绑定:call、apply、bind,this 指向绑定对象。
- new 绑定:构造函数调用,this 指向新创建的对象。
记住口诀:独立 window,对象对象,call/apply/bind 显示绑,new 新对象!
二、代码实战:this 的各种姿势
1. 默认绑定
javascript
var a = 1
function foo() {
var a = 2
console.log(this.a)
}
function bar() {
var a = 3
foo()
}
bar() // 输出 1
分析:foo 独立调用,this 指向 window,window.a = 1,所以输出 1。
2. 隐式绑定
javascript
var a = 2
function foo() {
console.log(this.a)
}
let obj = {
a: 1,
foo: foo
}
obj.foo() // 输出 1
分析:obj.foo(),this 指向 obj,obj.a = 1。
3. 显示绑定(call/apply/bind)
call 实现
javascript
Function.prototype.myCall = function(context, ...args) {
context = context || window
const fn = Symbol('fn')
context[fn] = this
const res = context[fn](...args)
delete context[fn]
return res
}
function foo(x, y) {
console.log(this.a, x + y);
}
let obj = { a: 1 }
foo.myCall(obj, 1, 2) // 输出 1 3
apply 实现
javascript
Function.prototype.myApply = function(context, ...args) {
context = context || window
const arg = args[0]
if (!Array.isArray(arg)) {
throw new TypeError('CreateListFromArrayLike called on non-object')
}
const fn = Symbol('fn')
context[fn] = this
const res = context[fn](...arg)
delete context[fn]
return res
}
bind 实现
javascript
Function.prototype.myBind = function(context, ...args1) {
let _this = this
return function F(...args2) {
if (this instanceof F) {
return new _this(...args1, ...args2)
} else {
return _this.apply(context, [...args1, ...args2])
}
}
}
function foo(x, y, z) {
console.log(this.a, x + y + z);
}
let obj = { a: 1 }
let bar = foo.myBind(obj, 1, 2)
bar(3) // 输出 1 6
let p = new bar(4, 5)
console.log(p)
小贴士:bind 返回的新函数可以被 new 调用,this 指向新对象,否则指向绑定对象。
4. new 绑定
javascript
function Person() {
return function F() {
this.name = 'sss'
return foo()
}
}
function foo() {
console.log('123');
}
let p = new Person()
let n = new p()
console.log(n)
分析:new Person() 返回 F,new p() 执行 F,this 指向新对象。
三、this 的高级用法与陷阱
1. call/apply/bind 的区别
- call:参数列表分开传递。
- apply:参数以数组形式传递。
- bind:返回新函数,可延迟执行,可 new。
2. this 与箭头函数
箭头函数没有自己的 this,取外层作用域的 this。
3. this 与作用域链
this 不是作用域链的一部分,作用域链决定变量查找,this 决定函数执行时的上下文。
四、面试高频:手写 call/apply/bind/new
1. 手写 call
核心思路:把函数挂到 context 上执行,利用隐式绑定。
2. 手写 apply
和 call 类似,参数以数组形式传递。
3. 手写 bind
返回新函数,判断是否被 new 调用,原型链处理。
4. 手写 new
虽然本次代码未直接实现 new 的 polyfill,但理解 new 的底层机制很重要:
- 创建新对象。
- 将构造函数的 prototype 赋给新对象的 proto。
- 执行构造函数,this 指向新对象。
- 返回新对象(如果构造函数返回对象则用返回值)。
五、综合案例:this 的灵活运用
1. 传参与 call 的妙用
javascript
function identify() {
return this.name.toUpperCase()
}
function speak() {
var greeting = 'hello, I am ' + identify.call(this)
console.log(greeting);
}
var me = { name: 'Ricardo' }
speak.call(me) // 输出 hello, I am RICARDO
2. bind 的构造函数特性
javascript
function foo(x, y, z) {
this.name = 'foo'
console.log(this.a, x + y + z);
}
let obj = { a: 1 }
const baz = foo.bind(obj, 1, 2)
const p = new baz(4, 5)
console.log(p)
console.log(p.name)
分析:bind 返回的新函数被 new 调用,this 指向新对象,obj.a 未被继承。
六、面试真题与易错点
1. this 指向 window 还是 global?
浏览器环境下 var a = 1 会挂到 window 上,Node 环境下不会。
2. bind 实现的坑
- 判断 new 调用时要用 instanceof。
- 原型链要处理,否则 new 出来的对象原型不对。
- 函数类型检查不能少。
3. apply 参数必须是数组
手写 apply 时要校验参数类型,否则会报错。
七、总结与面试技巧
- this 的指向由调用方式决定,牢记四大规则。
- call/apply/bind 是改变 this 的利器,手写实现要注意细节。
- bind 返回的新函数可被 new 调用,原型链处理是难点。
- 面试时多用代码举例,讲清原理,展示底层实现。
- 遇到 this 不要慌,先看调用方式,再看代码环境。
祝你面试顺利,成为 JS this 机制的掌控者!🚀🎉
面试官最爱问的 this 题目
-
箭头函数的 this?
-
setTimeout 里的 this?
-
构造函数和普通函数的 this 区别?
-
如何手写 bind 并支持 new?
-
call/apply/bind 的底层原理?