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))
}
}