this 指向
this 指向基本原则
⭐ this 的取值是在
函数执行
的时候确定的,不是在函数定义的时候确定的,与闭包相反。this 指向调用它所在方法的那个对象
- 普通函数
普通函数的 this,指向
windows
(非严格模式)
function fn1() {
console.log(this)
}
fn1() // window
- 一般对象和类
对象中的普通方法,this 指向
该对象本身
const zhangsan = {
name:'张三',
sayHi(){
console.log(this) // this是当前对象 => obj
},
}
zhangsan.sayHi()
打印如下👇
类方法,this 指向
类实例
class Person{
constructor(name){
this.name = name
}
sayName(){
console.log(this) // this指向执行时对应的实例
console.log(this.name)
}
}
const A = new Person('jack')
A.sayName()
打印如下👇
- 复杂对象的 this
有的时候(大多数是在刷题中,实际开发并不常见),this 的各种挂载会让人摸不到头脑,这种时候要记住 this 指向的原则:
this 的指向是在执行时决定的,而非定义时
。
示例一
const zhangsan = {
name:'张三',
printName1(){
console.log(this.name)
},
}
const lisi = {
name:'李四',
printName2(){
zhangsan.printName1() // 代码执行处
},
}
zhangsan.printName1() // 张三
lisi.printName2() // 张三
- 打印一:结果是 ‘张三’,它是一个普通的对象方法
- 打印二:虽然看起来执行的是lisi的方法,但是函数实际执行的是
zhangsan.printName1
,因此输出的是 ‘张三’
示例二
var zhangsan = {
name:'张三',
printName: function(){
console.log(this.name)
},
}
var lisi = {
name:'李四',
printName: function(){
var fn1 = zhangsan.printName
fn1() // 函数执行
},
}
var name = '王五'
var fn2 = zhangsan.printName
zhangsan.printName()
lisi.printName()
fn2()
执行后打印如下👇
- 打印一:这是一个普通的对象方法,打印结果是 ‘张三’
- 打印二:这是一个容易混淆的地方:fn1虽然是在 lisi 这个对象里定义的,但是在
执行
的时候,fn1本身其实没有挂载
任何对象,它已经是一个挂载在window
上的方法,因此会打印出全局对象中的name- 打印三:与打印二类似—— fn2 指向的实际是个 function() { console.log(this.name) }, fn2此时已经挂载在
全局对象
下
严格模式与非严格模式
- 非严格模式下全局作用域函数中的this指向
window对象
。- 严格模式下全局作用域中函数中的this是
undefined
。html中加入 use strict 会开启严格模式,否则默认非严格模式,当使用ES6模块化(let const),会自动开启严格模式。
明确的this指向——定时器、立即执行函数
在两种特殊情境下,this 会直接指向 window:
- 定时器
setTimeout
、setInterval
中的普通函数(非箭头函数)- 立即执行函数
let zhangsan2 = {
name:'张三2',
printName: function(){
console.log(this.name)
}(),
printName2: function(){
setTimeout(function() {
console.log(this.name)
},100)
},
}
zhangsan2.printName2()
输出如下👇
- 打印一:创建zhangsan2对象时,会自动执行立即执行函数,它的this指向window
- 打印二:定时器中的普通函数this指向window
改变this指向的操作
箭头函数
箭头函数的this会向它执行的
上一级作用域
寻找
var name = '张三1'
const zhangsan = {
name:'张三2',
fn1: function(){
console.log(this.name) ;
setTimeout(()=>{
console.log(this.name)
}, 100)
},
fn2: ()=>{
console.log(this.name)
}
}
zhangsan.fn1() // 张三2 张三2
zhangsan.fn2() // 张三1
- 打印一:定时器中的箭头函数使它内部执行时跳出当前作用域,效果就与第一个打印相同了
- 打印二:箭头函数跳出zhangsan这个对象,找到了window对象
call、apply、bind、
call、apply 、 bind,都可用来改变函数的
this 指向
。
- call 和 apply 之间的区别不大,call 需要将目标函数的入参
逐个传入
,fn.call(target, arg1,arg2…) ,apply需要参数以数组形式传入
,fn.apply(target, [ arg1,arg2… ])。它们在改变 this 指向的同时,也会同时执行目标函数
。- bind 需要将目标函数的入参
逐个传入
,fn.bind(target, arg1,arg2…) ,它只改变 this 指向,不执行函数
。
- call、apply、bind使用示例
const name = 'global'
function fnC(a, b, c) {
console.log(this.name)
console.log(a, b, c)
}
function fnA(a, b, c) {
console.log(this.name)
console.log(a, b, c)
}
function fnB(a, b, c) {
console.log(this.name)
console.log(a, b, c)
}
const objC = {
name: 'Call'
}
const objA = {
name: 'Apply'
}
const objB = {
name: 'Bind'
}
// call使用
fnC.call(objC, 'c','a','ll')
// apply使用
fnC.apply(objA, ['a','pp','ly'])
// bind使用
const fn = fnB.bind(objB, 'b','i','nd')
fn()
进阶——手动实现call、apply、bind(简易版)
// call与apply
Function.prototype.myCall = function (target, ...args) {
// step1: 把函数挂到目标对象上, this 就是要改造的目标函数)
target.func = this
// step2: 执行函数,利用扩展运算符将数组展开
target.func(...args)
// step3: 删除 step1 中挂到目标对象上的函数
delete target.func
}
Function.prototype.myBind = function (target, ...args) {
// step1: 记录myBind函数的this
const self = this
// step2: 改变函数指向,但不执行
return function () {
return self.call(target, ...args)
}
}
fnC.myCall(objC, 1, 2, 3)
const fn = fnB.myBind(objB, 4, 5, 6)
fn()
在上面我们利用ES6的剩余参数,实现了手动实现call、apply、bind函数,对于bind函数的实现,要注意它返回的是一个
改变了this指向的待执行函数
总结
this 指向
- this 指向基本原则
- 严格模式与非严格模式
- 明确的this指向——定时器、立即执行函数
改变this指向的操作
- 箭头函数
- call、apply、bind、