JavaScript - this、call、apply、bind

this

this 主要分为以下几种.

1、函数调用

  • 直接调用一个函数,就是默认绑定,this 在非严格模式下指向的为全局对象 window
javascript
1234
function fn() {
console.log(this) // window
}
fn()
javascript
123456789101112131415161718
const obj = {
fn: () => {
console.log(this) // window
},
fn1() {
return function () {
console.log(this) // window
}
},
fn2() {
console.log(this) // window
},
}
obj.fn()
obj.fn1()()
const Fn = obj.fn2
Fn()

2、对象函数调用

  • 函数 fn 作为一个对象的方法来调用时,this 指向这个对象
javascript
123456
const obj = {
fn() {
console.log(this) // obj
},
}
obj.fn()

3、构造器调用

  • 使用 new 调用时,函数执行前会新创建一个对象,this 指向这个新创建的对象。
javascript
12345
function fn() {
console.log(this) // fn
}
let obj = new fn()
javascript
123456789101112131415161718
function fn() {}
fn.prototype = {
func: function () {
console.log(this) // fn
},
}
let FN = new fn()
FN.func()
const obj = {
name: 'j',
}
FN.func.call(obj) // obj
FN.func.apply(obj) // obj
// 在使用 call,apply,bind 改变 this,这个优先级仅次于 new

call

Function.prototype.call(thisArg, arg1, arg2, ...)
call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

使用

javascript
12345
function method(a, b) {
console.log(this, a, b)
}
method.myCall(null, 1, 2)

具体实现

  • 不传入第一个参数,那么默认为 window
  • 改变了 this 指向,让新的对象可以执行该函数。那么思路可以变成给新的对象添加一个函数,然后在执行完以后删除
javascript
123456789101112131415
Function.prototype.myCall = function (context, ...args) {
context = context === null || context === undefined ? globalThis : Object(context)
// 使用Symbol确保不会出现命名冲突
let key = Symbol('_key')
// 使用defineProperty创建一个属性存放 this
Object.defineProperty(context, key, { value: this })
// 运行方法
let result = context[key](...args)
// 删除方法
delete context[key]
return result
}

apply

Function.prototype.apply(thisArg, [argsArray])
apply() 方法调用一个具有给定 this 值的函数,以及以一个数组(或一个类数组对象)的形式提供的参数。

使用

javascript
1234
function method(a, b) {
console.log(this, a, b)
}
method.apply({}, [2, 3])

具体实现

  • apply 的实现和 call 类似
javascript
12345678910111213141516
Function.prototype.myApply = function (context) {
context = context === null || context === undefined ? globalThis : Object(context)
// 使用Symbol确保不会出现命名冲突
let key = Symbol('_key')
// 使用defineProperty创建一个属性存放 this
Object.defineProperty(context, key, { value: this })
// 需要判断是否存储第二个参数
// 如果存在,就将第二个参数展开
let result = arguments[1] ? context[key](...arguments[1]) : context[key]()
// 删除方法
delete context[key]
return result
}

bind

Function.prototype.bind(thisArg, arg1, arg2, ...)
bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

使用

javascript
123456
function method(a, b) {
console.log(this, a, b)
}
let newMethod = method.bind({}) //this指向改变为obj2
newMethod(3, 4)

具体实现

  • bind 和其他两个方法作用也是一致的,只是该方法会返回一个函数。并且我们可以通过 bind 实现柯里化。
javascript
123456789101112131415
Function.prototype.myBind = function (context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
let _this = this
let args = [...arguments].slice(1)
// 返回一个函数
return function F() {
// 因为返回了一个函数,我们可以 new F(),所以需要判断
if (this instanceof F) {
return new _this(...args, ...arguments)
}
return _this.apply(context, args.concat(...arguments))
}
}