一.this指向问题
函数调用方式的不同决定了this 的指向不同, 一般指向函数调用者.
1.全局下
全局下, this始终指向window对象
// 全局下, this始终指向window对象
console.log(this); // window 相当于window.console.log(this); window
2.函数中
(1)作为普通函数被调用时
在非严格模式下, this指向window
严格模式下, this指向undefined
// 函数中 this 关键字的指向问题
// 1. 作为函数被调用
// (1) 非严格模式下, 直接被调用 this指向Window
function fun(){
console.log('非严格模式下, 直接被调用==>',this); //非严格模式下, 直接被调用==> Window {window: Window, self: Window, document: document, name: '', location: Location, …}
}
fun() // Window
// (2)在函数中,严格模式下,直接调用函数, this指向undefined
function strictFun(){
'use strict'
console.log('严格模式下, 直接被调用==>',this); //严格模式下, 直接被调用==> undefined
}
strictFun() // undefined
(2)作为方法被调用时(对象方法)
this指向方法拥有者
对象内部方法的this 谁调用就指向谁
// 2. 作为方法被调用, this指向方法拥有者(当多层对象嵌套时, this指向被调用函数最近的对象) 当函数作为对象的某个属性时, 可称这个函数为方法
// 对象内部方法的this 谁调用就指向谁
let obj = {
uname: 'mm',
eat1(){
console.log('eat1===>', this);
},
eat2: function(){
console.log('eat2==>', this);
},
eat3: ()=>{
console.log('eat3==>',this);
},
objjj: {
name: 'aaa',
eat4(){
name: 'bbb'
console.log(this.name); // aaa
console.log('内层对象this',this);
}
}
}
obj.eat1() // obj
obj.eat2() // obj
obj.eat3() // window 箭头函数没有this, 他的this指向来源于父级this指向
obj.objjj.eat4() // objjj 被调用函数最近的对象
(3)作为构造函数被调用时
通常指向实例化对象
// 3. 作为构造函数来使用, 通常指向实例化对象
function User(){
this.uname = 'mmm';
this.age = 21;
console.log('构造函数User中的this===>',this);
}
let mm = new User()
console.log('mm===>',mm); // {uname: 'mmm', age: 21}
补充:
构造函数在new的过程中, 发生的事情:
创建一个空对象
该空对象作为this参数传递给构造函数, 从而成为构造函数的上下文(e.g: 把构造函数中的this,替换成 mm )
新构造的对象作为new运算符的返回值返回(在构造函数显示返回对象时,会有例外,看构造函数内部是否有对象类型的返回值)
// 构造函数内部有返回值,为非对象类型, 使用new关键字后,会走正常实例化的流程,将tt传递给this
function User1(){
this.uname = 'ttt';
this.age = 22;
console.log('构造函数User1中的this===>',this);
return 1;
}
let tt = new User1()
console.log('tt===>', tt); // {uname: 'ttt', age: 22}
// 构造函数内部有返回值,且为对象类型, new关键字创建对象后, 会直接返回构造函数内部的对象
function User2(){
this.uname = 'ttt2';
this.age = 22;
console.log('构造函数User2中的this===>',this);
return {
n: 'haha'
}
}
let tt2 = new User2()
console.log('tt2===>', tt2); //{n: 'haha'}
(4)箭头函数中
this指向来源于父级this指向(非箭头函数的父级)
箭头函数中没有this和arguments, 他的this指向来源于父级this指向,且被定义的时候就确定了,之后永远都不会改变,即使使用call()、apply()、bind()等方法改变this指向也不可以
// 箭头函数 箭头函数中没有this和arguments, 他的this指向来源于父级this指向,且被定义的时候就确定了,之后永远都不会改变,即使使用call()、apply()、bind()等方法改变this指向也不可以
let fn = ()=>{
console.log(this);
}
fn() // window
(5)立即执行函数中
this指向window对象
// 立即执行函数 this指向window对象
;(function(){
console.log('立即执行函数==>',this); //window
}())
(6)定时器函数中
this指向window对象
// 定时器函数中 this指向window对象
setTimeout(function(){
console.log('定时器函数===>',this);
}, 1000)
3.事件绑定方法中
事件绑定方法中, this指向绑定事件的对象
<!-- 事件绑定方法中, this指向绑定事件的对象 -->
<button>事件绑定方法</button>
<script>
document.querySelector('button').addEventListener('click',function(){
console.log('事件绑定方法===>',this); // <button>事件绑定方法</button>
})
</script>
4.闭包中的
// 闭包中的this
const obj = {
name: '内---mm',
getName: function () {
return this.name
},
}
console.log('getName===>', obj.getName()) // 内---mm
const getNameFun = obj.getName
// getNameFun 是一个函数 调用时,没有其他调用者,那么调用者即为Window对象, const声明的变量在window上没有,故为undefined, 若为var声明则为var声明的变量值
console.log('getNameFun==>', getNameFun()) //undefined
const obj1 = {
name: '内1111---mm',
getName: function () {
return function () {
return this.name
}
},
}
// obj1.getName获得一个函数function(){ return function(){ return this.name }, ()执行一次,又得到一个函数function(){return this.name},再()执行返回this.name, 此时没有其他调用者调用
console.log('getName1===>', obj1.getName()()) //undefined
const getNameFun1 = obj1.getName()
console.log('getNameFun1==>', getNameFun1()) //undefined
const obj2 = {
name: '内1111---mm',
getName: function () {
//保存环境,保存this, 保存当前调用者 that常驻内存
const that = this
// 闭包 函数套函数, 内部函数可访问函数体外的变量
return function () {
return that.name
}
},
}
console.log('getName2===>', obj2.getName()()) //内1111---mm
const getNameFun2 = obj2.getName()
console.log('getNameFun2==>', getNameFun2()) //内1111---mm
二.改变this指向
JavaScript专门提供了一些函数方法来处理函数内部this的指向问题,常有bind(),call(),apply()
1.call() 方法
fun.call(thisArg, arg1, arg2, ...)
立即执行
thisArg可选参数, 函数运行时使用的this值, 非严格模式下,则指定为null或undefined时会自动替换为全局对象
arg1,arg2指定的参数列表
调用一个对象(可调用函数) , 可改变函数的 this 指向
返回值: 该方法的返回值, 若无返回值,则返回undefined
应用: 实现继承
// call(thisArg, arg1, arg2...) thisArg可选参数, 函数运行时使用的this值, arg1,arg2指定的参数列表
function callThis(sex){
console.log(`name: ${this.name},sex: ${sex}`); // name: mm,sex: girl
return this.name
}
let person1 = {name:'mm'}
let res1 = callThis.call(person1,'girl')
console.log('res1===>', res1); // mm (函数内部自身的有返回值,返回了this.name, 若没有会返回undefined)
// 非严格模式下,则指定为null或undefined时会自动替换为全局对象
var num = 1
function showNum(){
console.log('num===>', this.num);
}
showNum.call(undefined) // 1
function showNum1(){
console.log('num===>', this.num);
}
showNum1.call(null) // 1
// 严格模式
"use strict";
var number = 2
function showNum2(){
console.log('number===>', this.number);
}
showNum2.call(undefined) // 报错 Uncaught TypeError: Cannot read properties of undefined (reading 'number')
2.apply()方法
fun.apply(thisArg, [argsArray])
立即执行
thisArg可选参数, 函数运行时使用的this值, 非严格模式下,则指定为null或undefined时会自动替换为全局对象
参数必须为数组形式 [argsArray]
返回值: 该方法的返回值, 若无返回值,则返回undefined
可调用函数, 可改变函数的 this 指向
应用: 常跟数组有关系. 比如借助于数学对象实现数组最大值最小值
// apply() 与 call()相同, 只是参数需要传递一个数组
function applyThis(sex){
console.log(`name: ${this.name},sex: ${sex}`); // name: tt,sex: boy
return this.name
}
let person2 = {name:'tt'}
let res2 = callThis.call(person2,['boy'])
console.log('res2===>', res2); // tt
3.bind()方法
fun.bind(thisArg, arg1, arg2, ...)
会创建一个新的函数, 调用新的函数时才会执行
thisArg可选参数, 函数运行时使用的this值, 非严格模式下,则指定为null或undefined时会自动替换为全局对象,如果使用new运算符构造绑定函数, 则忽略该值
arg1, arg2 目标函数被调用时参数列表中预置的参数,当目标参数调用时传入参数,会增加参数数量而不是替换
返回值: 原函数的拷贝,并拥有指定的this值和初始参数(返回由指定的 this 值和初始化参数改造的原函数拷贝(返回原函数改变this之后产生的新函数))
不会调用函数, 可以改变函数内部this指向
应用: 比如改变定时器内部的this指向.
// bind() 原函数的拷贝,并拥有指定的this值和初始参数
function bindThis(sex){
console.log(`name: ${this.name},sex: ${sex}`);
return this.name
}
let person3 = {name:'kkong'}
let res3 = bindThis.bind(person3,'boy')
console.log('res3===>', res3);
/* res3===> ƒ bindThis(sex){
console.log(`name: ${this.name},sex: ${sex}`);
return this.name
} */
// res3是个函数 原函数的拷贝,并拥有指定的this值和初始参数
res3() //name: kkong,sex: boy
// 使用new运算符构造绑定函数, 则忽略thisArg这个可选参数
new res3() // name: undefined,sex: boy
// 当目标参数调用时传入参数,会增加参数数量而不是替换
function Arg(){
console.log(Array.from(arguments).join());
}
var result1 = Arg.bind()
result1() // 空白
var result2 = Arg.bind(undefined,1,2)
result2() //1,2
var result3 = Arg.bind(undefined,3,4)
result3(5,6) //3,4,5,6
const a = new result3(7,8,9) //3,4,7,8,9
const b = new Arg() // 空白
// 改变函数内this指向
// 1. call() 可调用函数,可改变函数内this指向. 主要作用: 实现继承
function Father(uname){
this.uname = uname;
}
function Son(uname, uage){
Father.call(this,uname,uage); //调用函数Father,将其this修改为Son中的this,并且添加uage属性 应用: 实现继承
}
// 2. apply() 可调用函数,可改变函数内this指向 参数必须是数组(伪数组) 应用: 利用apply借助数学内置对象求最大/小值
var o = { };
function fn(arr){
console.log(arr);
}
fn.apply(o,['pink']);
var arr1 = [1,3,5,2,4]; //求数组最大/小值
console.log(Math.max.apply(null, arr1));//不需改变this指向写null
// 3. bind() 绑定
var fn1 = fn.bind(o); //不调用原函数, 可改变原函数内部this指向
fn1; //返回原函数改变this之后产生的新函数
// 点击按钮后禁用,三秒后开启使用 定时器函数不需要立即执行,改变this指向用bind
var btn = document.querySelector('button');
btn.onclick = function(){
this.disabled = true; //this指向btn 按钮禁用
setInterval(function(){
this.disabled = false; //开启按钮 定时器中this指向window
}.bind(this),3000); //此处this指向btn对象
}