Skip to content

this、call/apply/bind

this

在JavaScript中,this总是指向一个对象。但独特的是,this具体指向哪个对象是在运行时基于函数的执行环境动态绑定的,而不是基于函数被声明时的环境。

在实际应用中,this的指向一般有以下几种

  • 作为对象的方法调用
js
const obj = {
  name: 'long',
  run() {
    console.log(this === obj) // true
    console.log(this.name)  // long
  }
}
obj.run()
  • 作为普通函数调用
js
window.text = 'data'
function fn() {
  console.log(this)
  /**
   * 在浏览器环境中指向 window
   * 在Node环境中指向 global
   */
  console.log(this.text) // data 浏览器环境中
}
fn()
  • 构造函数调用

当用new运算符调用函数时,该函数总会返回一个对象,通常情况下,构造器里的this就指向返回的这个对象。

js
function Animal() {
  this.type = 'small'
}
const cat =  new Animal()
console.log(cat.type) // small

注意:在使用new运算符调用构造函数时,构造函数如果显示的返回了一个对象,此时的this就会指向这个对象。

js
function Person() {
  this.name = 'Jack'
  return {
    name: 'Tom'
  }
}
const p = new Person()
console.log(p.name) // Tom
  • 箭头函数调用

事实上,箭头函数本身没有this,如果需要访问this,就从外部获取。

js
/* 全局环境 */
const fn = () => {
  /* 浏览器环境中 */
  console.log(this === window) // true 
}
fn()
/* 注意:在Node环境中,全局环境下的箭头函数中的this指向为{},而不是global。 */

/* 局部环境 */
const test = {
  title: 'test',
  run() {
    (() => {
      console.log(this.title) // test
    })()
  }
}
test.run()
/* 此时this为外部函数run的this指向 */

call/apply

  • call 语法:func.call(context, arg1, arg2, ...)

运行func,context指定函数的this指向,第二个参数往后都会作为参数传给函数。

  • apply 语法:func.call(context, args)

运行func,context指定函数的this指向,第二个参数为一个参数数组(或者类数组对象)。

call 和 apply 之间唯一的语法区别是 call 接受一个参数列表,而 apply 则接受一个数组或者类数组对象。

js
/**
 * call
 */
function say(word) {
  console.log(`${this.name}:${word}`)
}
const user = { name: 'Tom' }
const admin = { name: 'Admin' }
say.call(user, 'hello') // Tom:hello  此时this指向user
say.call(admin, 'Hi')   // Admin:Hi   此时this指向admin

/**
 * apply
 */
function person(age, gender) {
  console.log(`${this.name} : ${age} & ${gender}`)
  // Tom : 18 & 女
}
const user = { name: 'Tom' }
person.apply(user, [18, '女'])

bind

语法:func.bind(context, arg1, arg2, ...)

bind语法与call一致,需要注意的是:bind与call/apply不同的是它不会调用这个函数,而是返回一个原函数的拷贝,并且拥有指定的this和参数。

js
function fn(word, greet) {
  console.log(`${this.name}:${word} & ${greet}`)
}
const obj = { name: 'Lucy' }
const newFn = fn.bind(obj, 'Hi', 'Hello')
newFn() // Lucy:Hi & Hello

MDN详解

模拟实现 call/apply/bind

call

js
Function.prototype._call = function(context = window) {
  if (typeof this !== 'function') {
    throw new TypeError('error')
  }
  let fn = Symbol()
  context[fn] = this
  const args = [...arguments].slice(1)
  const result = context[fn](...args)
  delete context[fn]
  return result
}

apply

js
Function.prototype._apply = function(context = window) {
  if (typeof this !== 'function') {
    throw new TypeError('Error')
  }
  let fn = Symbol() 
  context[fn] = this
  let result
  if (arguments[1]) {
    result = context[fn](...arguments[1])
  } else {
    result = context[fn]()
  }
  delete context[fn]
  return result
}

bind

js
Function.prototype._bind = function (context) {
  if (typeof this !== 'function') {
    throw new TypeError('Error')
  }
  const _this = this
  const args = [...arguments].slice(1)
  return function F() {
    // 判断是否对函数使用new操作符
    if (this instanceof F) {
      return new _this(...args, ...arguments)
    }
    return _this.apply(context, args.concat(...arguments))
  }
}

Updated at: