1、概念
this
提供了一种隐形的传递一个对象的引用,是执行上下文创建时确定的一个在执行过程中不可更改的变量
2、绑定规则
this
在函数调用阶段确定,也就是执行上下文创建的阶段进行赋值,保存在变量对象中,即当函数在不同的调用方式下都可能会导致this
的值不同,this
的最终指向是那个调用它的对象
2.1 默认绑定(函数直接调用)
独立函数运行默认指向全局对象window
(浏览器环境)
var name = 'hello'
function test() {
console.log(this.name) // 'hello',this 等于 window
}
test()
使用let/const
定义的变量是不会被绑定到window
对象上的,而var
声明的变量会挂载在window
对象上当作对象属性
var a = 'a'
let b = 'b'
const c = 'c'
function test() {
console.log(this.a) // 'a',相当于window.a
console.log(this.b) // undefined
console.log(this.c) // undefined
}
test()
2.2 隐式绑定(对象方法调用)
函数的调用是在某个对象上触发的,对象属性链调用则指向最后一层
function sayHi() {
console.log(this.name) // person2,对象属性链调用 this 指向最后一层的 person 对象
}
let person2 = {
name: 'person2',
sayHi: sayHi
}
let person1 = {
name: 'person1',
person: person2
}
person1.person.sayHi()
2.2.1 隐式绑定丢失
由于某些原因丢失了绑定对象,则会应用默认绑定
引用赋值
把一个隐式绑定的函数赋给了另一个变量或把函数当作参数传入到另一个函数
var name = 'windowName'
function callbackFn(fn) {
fn()
}
let obj = {
name: 'hello',
getName: function () {
console.info(this.name)
}
}
var newName = obj.getName
newName() // 'windowName',调用 newName 函数为直接调用,与 obj 对象无关了
callbackFn(obj.getName) // 'windowName',调用 callbackFn 中的 fn 函数为直接调用
内置函数
内置函数中如setTimeout
和setInterval
等定时器,它俩的第一个参数是回调函数,这个回调函数中this
指向window
var name = 'windowName'
setTimeout(function () {
console.log(this.name) // 'windowName'
}, 1000)
2.3 显式绑定(call/apply/bind 指定 this 对象)
call/apply/bind
可以改变函数执行时的this
指向,显式的指定this
所指向的对象
// 语法
fn.call(obj, param1, param2, ...)
fn.apply(obj, [param1,param2,...])
fn.bind(obj, param1, param2, ...)
-
fn
的this
指向obj
对象(obj
为null
或undefined
则出现绑定丢失,使用默认绑定) -
call/apply
返回fn
函数执行的结果,bind
返回fn
函数的拷贝,拥有指定的this
,需要手动触发执行该函数 -
call
和apply
传参不同,call
是使用逗号相隔依次传递,而apply
则是以数组形式传递
为了借助已实现的方法,改变方法中数据的this
指向,减少重复代码,节省内存
const arr = [1, 527, 102, 3, 15]
Math.max.apply(null, arr) // 527
call/apply/bind 模拟实现
// call/apply 只是所传参数不同,只需要对入参进行调整即可
Function.prototype.myCall = function (context, ...args) {
context = context || window // 传入的为 null 和 undefined,则应用默认绑定规则 this 指向 window 对象,原始值则自动包装对象
let fn = Symbol() // 创造唯一的 key 值
context[fn] = this // 设置属性值为所传函数,为后面调用,隐式绑定 this 做准备
let result = context[fn](...args) // 执行函数返回结果,隐式绑定,所以 this 指向 context
delete context[fn] // 删除新增属性
return result
}
function getName(a, b) {
console.info(this.name + a + b)
}
let obj = {
name: 'haha'
}
getName.myCall(obj, 1, 2) // 'haha12'
Function.prototype.myBind = function (objThis, ...params) {
const thisFn = this
let fToBind = function (...secondParams) {
const isNew = this instanceof fToBind // this 是否是 fToBind 的实例 也就是返回的 fToBind 是否通过 new 调用
const context = isNew ? this : objThis // new 调用就绑定到 this 上,否则就绑定到传入的 objThis 对象上
return thisFn.call(context, ...params, ...secondParams) // 用 call 调用函数绑定 this 的指向并传递参数,返回执行结果,即上面 myCall 实现
}
if (thisFn.prototype) {
// 复制源函数的 prototype 给 fToBind 一些情况下函数没有 prototype,比如箭头函数
fToBind.prototype = Object.create(thisFn.prototype)
}
return fToBind // 返回拷贝的函数
}
function getNames(a, b) {
console.info(this.name + ',' + a + ',' + b)
}
function GetName(a, b) {
console.info(this.name + '-' + a + '-' + b)
}
GetName.prototype.name = 'name'
GetName.prototype.say = function () {
console.log('123')
}
let obj = {
name: 'haha'
}
// 普通函数调用
let bindFun = getNames.myBind(obj, 'params')
bindFun('secondParams') // 'haha,params,secondParams'
// 构造函数调用
let bindFuns = GetName.myBind(obj, 'params')
let fun = new bindFuns('secondParams') // 'name-params-secondParams'
fun.say() // '123'
2.4 new 绑定(构造函数调用)
new
关键字用来创建一个类(模拟类)的实例对象,实例化对象之后,也就继承了类的属性和方法
function objectFactory() {
// shift 返回第一个参数构造函数,并改变 arguments 参数原数组
let constructor = [].shift.call(arguments)
// 判断 constructor 是否是一个函数
if (typeof constructor !== 'function') {
console.error('type error')
return
}
// 创建一个空的对象并链接到构造函数的原型,使它能访问原型中的属性
let newObject = Object.create(constructor.prototype)
// 将 this 指向新建对象,并执行函数
let result = constructor.apply(newObject, arguments)
// 判断返回对象类型,如果是引用类型,则返回该引用类型的对象,否则返回新建的对象
let flag = result && (typeof result === 'object' || typeof result === 'function')
// 判断返回结果
return flag ? result : newObject
}
function SayName(name) {
this.name = name
}
let obj = objectFactory(SayName, 'haha')
console.info(obj.name) // 'haha'
2.5 特殊绑定(箭头函数)
-
箭头函数没有
this
,绑定的是最近一层非箭头函数作用域的this
-
箭头函数的指向永远是最近一层非箭头函数作用域的
this
指向(显式绑定无法改变this
指向,但可以通过修改外层的this
来达成间接修改),并且没有arguments
、prototype
等对象,也不可以当作构造函数使用
var name = 'window'
var obj1 = {
name: 'obj1',
foo1: function () {
console.log(this.name)
return () => {
console.log(this.name)
}
},
foo2: () => {
console.log(this.name)
return function () {
console.log(this.name)
}
}
}
var obj2 = {
name: 'obj2'
}
obj1.foo1.call(obj2)() // 'obj2' 'obj2'
obj1.foo1().call(obj2) // 'obj1' 'obj1'
obj1.foo2.call(obj2)() // 'window' 'window'
obj1.foo2().call(obj2) // 'window' 'obj2'
-
obj1.foo1.call(obj2)()
,obj1.foo1
通过call
显示绑定obj2
,所以第一个name
返回obj2
,第二层为箭头函数,所以它的this
继承上一层的obj1.foo1
的this
同样返回obj2
-
obj1.foo1().call(obj2)
,obj1.foo1()
通过隐式绑定obj1
,所以第一个name
返回obj1
,第二层为箭头函数显式绑定无效,还是返回obj1
-
obj1.foo2.call(obj2)()
,第一层为箭头函数显式绑定无效,继承外层的作用域为window
,返回window
,第二层函数最后调用为window
,所以也返回window
-
obj1.foo2().call(obj2)
,第一层为箭头函数,继承外层的作用域为window
,返回window
,第二层函数显示绑定obj2
,所以返回obj2
3、绑定优先级
new
绑定 > 显式绑定(bind
) > 隐式绑定 > 默认绑定
4、综合经典题
4.1 题目一
function Foo() {
getName = function () {
console.log(1)
}
return this
}
Foo.getName = function () {
console.log(2)
}
Foo.prototype.getName = function () {
console.log(3)
}
var getName = function () {
console.log(4)
}
function getName() {
console.log(5)
}
Foo.getName() // '2'
getName() // '4'
Foo().getName() // '1'
getName() // '1'
new Foo.getName() // '2'
new Foo().getName() // '3'
new new Foo().getName() // '3'
-
Foo.getName()
,直接执行Foo
的getName
静态属性,返回 2 -
getName()
,变量提升之后,全局变量的getName
覆盖getName
函数,返回 4 -
Foo().getName()
,先执行Foo
函数,返回this
,这是this
指向window
,然后getName
方法,覆盖全局变量的getName
调用,返回 1 -
getName()
,调用全局函数,使用上次更新覆盖的getName
函数,返回 1 -
new Foo.getName()
,先计算Foo.getName()
,再计算new
,返回 2 -
new Foo().getName()
,先计算new Foo()
,this
指向实例,再计算.getName()
,因为实例中没有属性,所以接着原型找,返回 3 -
new new Foo().getName()
,先计算new Foo().getName()
,然后再new
一次,同样查找原型链,返回 3
4.2 题目二
var number = 5
var obj = {
number: 3,
fn: (function () {
var number
this.number *= 2
number = number * 2
number = 3
return function () {
var num = this.number
this.number *= 2
console.log(num)
number *= 3
console.log(number)
}
})()
}
var myFun = obj.fn
myFun.call(null) // '10' '9'
obj.fn() // '3' '27'
console.log(window.number) // '20'
-
首先
obj
定义的时候,fn
自执行函数触发,形成闭包并返回一个匿名函数。自执行函数的this
指向window
,所以此时window.number
为 10,fn
函数的局部变量number
为 3 -
myFun.call(null)
,隐式绑定丢失,this
指向window
,执行fn
函数,此时num
等于window.number
,打印 10,window.number
计算为 20,number
等于闭包局部变量乘以 3,打印 9 -
obj.fn()
,隐式绑定,this
指向obj
,执行fn
函数,此时num
等于3
,打印 3,obj.number
计算为 6,number
等于闭包局部变量乘以 3,打印 27,此时window.number
还是 20