this 的指向与 call, apply, bind 的模拟实现
1. this 的指向问题
关于函数的 this 的指向并不是一个很复杂的问题
我们首先要明确一个定义:
fn = function () {...} 指的是 fn 这个属性存储着一个函数的地址 fn_addr, 而我们通过 fn_addr() 来执行相应地址的函数
接下来我们仍将 fn 称为 fn 函数, 只需知道它只是一个存储函数地址的变量即可
1.1 普通函数的 this 指向
对于普通函数 fn, 若
xx.fn(), 其中fn为对象xx上的一个属性, 则执行fn函数时, 其this指向的是xx(这个xx可以是单个的对象, 也可以是由成员访问符.连接起来的嵌套对象:xx === obj.obj1.obj2.obj3)function fn() { console.log(this) } let obj = { val: 1, fn } obj.fn() //=> {val: 1, fn: ƒ}fn(), 则- 在严格模式下, 其
this指向为undefined - 在非严格模式下, 其
this指向为window
"use strict" function fn() { console.log(this) } fn() //=> undefinedfunction fn() { console.log(this) } fn() //=> window- 在严格模式下, 其
- 出现
call,apply,bind, 对于fn.call/apply(xx)和fn.bind(xx)()- 若
xx为null或undefined- 则严格模式下,
this为window - 非严格模式下,
this分别为null和undefined
"use strict" function fn() { console.log(this) } fn.call(null) //=> null fn.call(undefined) //=> undefinedfunction fn() { console.log(this) } fn.call(null) //=> window fn.call(undefined) //=> window - 则严格模式下,
- 若
xx为引用类型, 则this指向为xx - 若
xx为基础值类型- 严格模式下,
this为xx - 非严格模式下,
this为xx所对应的引用值类型
"use strict" function fn() { console.log(this) } fn.call(1) //=> 1function fn() { console.log(this) } fn.call(1) //=> Number {1} - 严格模式下,
- 若
1.2 箭头函数的 this 指向
箭头函数没有 this
当在箭头函数中访问 this 时, 其实还是获取的是其所在上下文中的 this
const obj = {val: 1}
const fn = () => console.log(this) // 定义时所在的上下文 this 为 window
let f
function f0() {
console.log(this) // 输出 f0 上下文的 this
fn()
fn.call(obj)
}
function f1() {
console.log(this) // 输出 f1 上下文的 this
const fn = () => {console.log(this)} // 所在上下文 this 等到执行 f1 时才知道
fn()
fn.call(window)
f = fn
}
f0.call(obj)
//=> obj
//=> window
//=> window (该输出证明了箭头函数中的 this 不会被 call 改变)
f1.call(obj)
//=> obj
//=> obj
//=> obj (该输出证明了箭头函数中的 this 不会被 call 改变)
f()
//=> obj (即使将 fn 从 f1 里面提出到全局中, 其 this 仍然不变, 表示箭头函数的 this 只与其定义时所在的上下文有关, 而不是与运行时所在的上下文有关)
1.3 立即执行函数的 this 指向
- 在严格模式下, 其
this指向undefined - 在非严格模式下, 其
this指向window
(function () {
console.log(this) //=> window
})()
"use strict"
;(function () {
console.log(this) //=> undefined
})()
1.4 回调函数的 this 指向
- 若是绑定在
dom元素上, 用来监听事件的非箭头函数, 其this为对应的dom元素 - 若是当作普通的回调函数使用, 则若该回调函数非箭头函数, 其
this与立即执行函数的this指向相同
function fn() {
console.log(this)
}
[1, 2, 3].reduce(fn, 0)
//=> window
//=> window
//=> window
"use strict"
function fn() {
console.log(this)
}
[1, 2, 3].reduce(fn, 0)
//=> undefined
//=> undefined
//=> undefined
2. call/apply 的模拟实现
2.1 call 的模拟实现
Function.prototype.call = function(obj, ...args) {
obj = obj == null ? window : obj // 如果被改变的 this 指向 null 或者 undefined, 则 this 指向 window
const objType = typeof obj
obj = (objType !== "object" && objType !== "function") ? Object(obj) : obj // 如果被改变的 this 指向基础类型, 则将 this 改为指向引用类型
obj.fn = this
const res = obj.fn(...args)
delete obj.fn
return res
}
2.2 apply 的模拟实现
Function.prototype.apply = function(obj, arr) {
obj = obj == null ? window : obj
const objType = typeof obj
const arrType = typeof arr
obj = objType !== "function" && objType !== "object" ? Object(obj) : obj
obj.fn = this
if (arr != null && arrType !== "function" && arrType !== "object") // 若 arr 是非 null 和 undefined 的基础类型, 则报错
new TypeError("CreateListFromArrayLike called on non-object")
arr = Array.isArray(arr) ? arr : (arr == null ? [] : Array.from(arr)) // 若 arr 是 null 或 undefined, 则令 arr 为空数组, 若 arr 为非数组的引用类型, 则通过 Array.from 将 arr 转换为数组
const res = obj.fn(...arr)
delete obj.fn
return res
}
2.3 bind 的模拟实现
Function.prototype.bind = function(obj, ...args1) {
obj = obj == null ? window : obj
const self = this
const objType = typeof obj
obj = objType !== "object" && objType !== "function" ? Object(obj) : obj
const retFn = function(...args2) {
if (this instanceof self)
return self.apply(this, args1.concat(args2))
return self.apply(obj, args1.concat(args2))
}
// 为 retFn.prototype 赋值, 以保证在对 bind 所返回的方法进行 new 操作时, 相应的实例对象原型仍然是调用 bind 的那个函数.prototype
// 这里为了防止 retFn.prototype 被更改导致 new 出来的实例对象的原型被更改, 所以使用了 Object.create 方法
retFn.prototype = Object.create(self.prototype)
retFn.constructor = retFn
return retFn
}

浙公网安备 33010602011771号