this 的概念
this 在构造函数中表示实例对象,还可以用在其他场合。但不管使用在哪,this 总是返回一个对象。
纯 js 脚本中的 this
在非严格模式下,总是指向一个对象,在严格模式下可以是任意值。
- 全局执行环境中,指向全局 window 对象(非严格模式、严格模式)
- 函数内部,取决于函数被调用的方式
- 直接调用的this值:① 非严格模式:全局对象(window) ② 严格模式:undefined
- 对象方法调用的this值:① 调用者
- 使用 new 方法调用构造函数,构造函数内的 this 会绑定到新创建的对象上。
- 箭头函数,this 指向由外层(函数或者全局)作用域决定,箭头函数不能作为构造函数。
- apply / bind / call 方法调用,函数体内的 this 绑定到指定参数的对象上。
开启严格模式: 脚本开启: 'use strict' 函数内部开启:'use strict' 注意:'use strict'写在代码顶端
// 如何确认this的值
// 1.全局执行环境
// 严格模式,非严格模式:全局对象(window)
// 2.函数内部
// 2.1 直接调用
// 严格模式下:undefined
// 非严格模式:全局对象(window)
// 2.2 对象方法调用
// 严格模式,非严格模式:调用者
// 3. 使用 new 方法调用构造函数,构造函数内的 this 会绑定到新创建的对象上。
// 4. 箭头函数,this 指向由外层(函数或者全局)作用域决定。
// 5. apply / bind / call 方法调用,函数体内的 this 绑定到指定参数的对象上。
// ------------- 1.全局执行环境 -------------
// 严格模式,非严格模式 全局对象(window)
// 'use strict'
// console.log(this)
// ------------- 2.函数内部 -------------
// 2.1 直接调用-非严格模式
// function func() {
// console.log(this) // window
// }
// func()
// 2.1 直接调用-严格模式
// function func() {
// 'use strict'
// console.log(this) // undefined
// }
// func()
// 2.2 对象方法调用
const food = {
name: '猪脚饭',
eat() {
'use strict'
console.log(this)
}
}
// 非严格模式,严格模式
food.eat() // 调用者
this 指向绑定事件的元素
dom 元素绑定事件时,事件处理函数里面的 this 指向绑定了事件的元素。
此时和 target 不同,target 指向触发事件的元素。
<ul id="color-list">
<li>red</li>
<li>yellow</li>
<li>blue</li>
<li>black</li>
<li>white</li>
</ul>
<script>
let colorList = document.getElementById('color-list')
colorList.addEventListener('click', function (e) {
console.log('this:', this)
console.log('target', e.target)
console.log('srcElement:', e.srcElement)
})
</script>
有些时候我们会遇到一些困扰,比如在 div 节点的事件函数内部,有一个局部的 callback 方法,该方法被作为普通函数调用时, callback 内部的 this 是指向全局对象 window 的。
<div id="div1">我是一个 div</div>
<script>
window.id = 'window'
document.getElementById('div1').onclick = function () {
console.log(this.id) // div1
const callback = function () {
console.log(this.id)
}
callback() // window
}
</script>
可以用一个变量保存 div 节点。
<div id="div1">我是一个 div</div>
<script>
window.id = 'window'
document.getElementById('div1').onclick = function () {
console.log(this.id) // div1
const that = this
const callback = function () {
console.log(that.id)
}
callback() // div1
}
</script>
指定 this
call
通常是为函数(方法)指定 this 指向(其他对象)。
call 方法可以改变 this 的指向,指定 this 指向对象 obj,然后在对象 obj 的作用域中运行函数。
call 方法的参数,应该是对象 obj,如果参数为空或 null,undefind,则默认传参全局对象。
如果 call 传参不是以上类型,则转换为对应的包装对象,然后传入方法。
let obj = {}
console.log(obj.hasOwnProperty('toString')) // false 查看本身是否有某方法
console.log(obj.toString()) // [object Object] 调用toString方法 是继承来的方法
// 重写hasOwnProperty方法
// obj.hasOwnProperty = function () {
// return 'aaa'
// }
// console.log(obj.hasOwnProperty('toString')) // aaa
// 可以使用 call 调用原生的方法
console.log(Object.prototype.hasOwnProperty.call(obj, 'toString')) // false
apply
可以通过 apply 方法,利用 Array 构造函数将数组的空元素变成 undefined。
let a = ['1', , '2']
Array.apply(null, a).forEach((e, i) => {
console.log(e, i) // 1 0 undefined 1 2 2
})
配合数组对象的 slice 方法,可以将一个类数组的对象(比如 arguments 对象)转成真正的数组。
Array.prototype.slice.apply({0: 1, length: 1}) // [1]
Array.prototype.slice.apply({0: 1}) // []
console.log(Array.prototype.slice.apply({0: 1, length: 2})) // [1, undefined]
console.log(Array.prototype.slice.apply({length: 2})) // [undefined, undefined]
这个方法起作用的前提是,被处理的对象必须有length属性,以及相对应的数字键。
bind
let d = new Date()
d.getTime()
// let print = d.getTime
// this 的指向改变 导致报错 可以使用 bind 改变 this 的指向
// print() // this is not a Date object.
let print = d.getTime.bind(d)
console.log(print()) // 1715074121540
bind 的一些注意点:
- 每一次返回一个新函数
- 结合回调函数使用
- 某些数组方法可以可以接收一个函数当做参数
- 结合 call
let slice = Function.prototype.call.bind(Array.prototype.slice)
// call 方法来源于 Function.prototype
// 这里是改写了 slice 方法
console.log(slice([1, 2, 3], 0, 1)) // 1
function fn(){
console.log(this.v)
}
let obj = {
v:123
}
let func = Function.prototype.call.bind(Function.prototype.bind)
func(fn, obj)() // 123
可以通过两种方法指定this:
- 调用时指定:
- call方法
- apply方法
- 创建时指定:
- bind方法
- 箭头函数
/**
* 如何指定this的值:
* 1. 调用时指定this:
* 2. 创建时指定this:
* */
// ------------- 1. 调用时指定this: -------------
function func(numA, numB) {
console.log(this)
console.log(numA, numB)
}
const person = {
name: 'username'
}
// 1.1 call:挨个传入参数
// func.call(person, 1, 2) // this 为 {name: 'username'}
// 1.2 apply:以数组的方式传入参数
// func.apply(person, [3, 4]) // this 为 {name: 'username'}
// ------------- 2. 创建时指定this: -------------
// 2.1 bind方法
// const bindFunc = func.bind(person, 666) // this 为 {name: 'username'} ,Func函数的参数可以依次传入
// bindFunc(888) // numA 为 666,numB 为 888
// 2.2 箭头函数
const food = {
name: '西兰花炒蛋',
eat() {
console.log(this) // food
// 箭头会从自己作用域链的上一层继承this
setTimeout(() => {
console.log(this) // food
}, 1000);
// setTimeout(function () {
// console.log(this) // window
// }, 1000)
}
}
food.eat()
总结
如何确认this指向:
- 全局执行环境中,指向全局 window 对象(非严格模式、严格模式)
- 函数内部,取决于函数被调用的方式
- 直接调用的this值:① 非严格模式:全局对象(window) ② 严格模式:undefined
- 对象方法调用的this值:① 调用者
- 使用 new 方法调用构造函数,构造函数内的 this 会绑定到新创建的对象上。
- 箭头函数,this 指向由外层(函数或者全局)作用域决定。
- apply / bind / call 方法调用,函数体内的 this 绑定到指定参数的对象上。
如何开启严格模式:
// 为整个脚本开启严格模式
'use strict'
function func() {
// 为函数开启严格模式
'use strict'
}
如何改变this指向,有2类改变this指向的方法,分别是:
- 调用函数时并传入具体的
thiscall:从第二个参数开始挨个传递参数apply:在第二个参数以数组的形式传递参数
- 创建函数时绑定
this?bind:返回一个绑定了this以及参数(可选)的新函数- 箭头函数:创建时会绑定上一级作用域中的
this
例题实战:
const foo = {
bar: 10,
fn: function () {
console.log(this)
console.log(this.bar)
}
}
let fn1 = foo.fn
fn1() // window or global 和 undefined
foo.fn() // { bar: 10, fn: [Function: fn] } 和 10