js中的this指向
1. this是什么?
- 在浏览器端运行JS代码,非函数中的this一般都是window;研究this都是研究函数中的this。有一个特殊的,就是ES6+中“块级上下文”中的this,是其所在上下文中的this,这也可以理解为:块级上下文是没有自己this。
- this就是一个指针,指向调用函数的对象。常说的this,指的是函数的执行主体。也就是谁把这个函数执行了,函数里的this就指向谁。当然会存在特殊情况,这个后面会说到
2. this的几种绑定规则
想要分清楚函数执行的执行主体(this),可以按照如下几种情况来分析:
- 事件绑定
- 普通函数执行
- 构造函数执行(new)
- 箭头函数执行
- 基于call/apply/bind强制改变this
2.1 事件绑定
不论是DOM0还是DOM2级事件绑定,给元素的某个事件行为绑定方法,当事件触发方法执行,方法中的this是当前元素本身。
特殊情况:
- IE6~8中基于attachEvent实现DOM2事件绑定,事件触发方法执行,方法中的this不在是元素本身,大部分情况都是window
- 如果事件回调是基于call/apply/bind强制改变了函数中的this,也是以强制改变的为主
// 1
document.body.onclick = function () {
console.log(this) // body
}
// 2
document.body.addEventListener('click', function () {
console.log(this) // body
})
// 3: 基于call/apply/bind强制改变了函数中的this,也是以强制改变的为主
const callback = function() {
console.log(this) // { name: '123' }
}
document.body.addEventListener('click', callback.bind({ name: '123' }))
2.2 普通函数执行
- 函数执行,看函数前面是否有“点”。如果有“点”,“点”的前面是谁,那么this就是谁,没有“点”this是window「JS严格模式下是undefined」
// 1:默认绑定this
// 默认绑定,在不能应用其它绑定规则时使用的默认规则,通常是独立函数调用
function fn() {
console.log(this)
}
fn() // window
// 2:隐式绑定this
// 函数的调用是在某个对象上触发的,即调用位置上存在上下文对象。典型的形式为 XXX.fn()
const obj = {
fn: function() {
console.log(this)
}
}
obj.fn() // obj
// 3
// xxx.__proto__.fn() -> 此时的this是:xxx.__proto__
- 自执行函数执行:其函数体内的this一般都是window,严格模式下则是undefined
- 回调函数中的this一般也是window/undefined。除非做过特殊的处理,亦或是正常的DOM事件绑定
2.3 构造函数
当使用new关键字调用函数时,函数中的this一定是在构造函数创建的实例对象。
箭头函数不能当做构造函数,所以不能与new一起执行,不然会报错
2.4 箭头函数
箭头函数是ES6中新增的,它和普通函数有一些区别。箭头函数没有自己的this,它的this继承于外层代码库中的this。
箭头函数在使用时,需要注意以下几点:
- 创建箭头函数时,就已经确定了它的 this 指向
- 箭头函数内的 this 指向外层的 thi
- 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误
- 箭头函数没有arguments对象
- 箭头函数没有自己的this,所以不能用call()、apply()、bind()这些方法去改变箭头函数内部的this指向。
// 1
let obj = {
n: 0,
fn() {
setTimeout(() => {
// this -> 上级上下文中的this -> obj
this.n++
}, 1000)
}
}
obj.fn()
// 2
const fn = () => {
// 这里 this 指向取决于外层 this,此时this指向了window
console.log(this)
}
fn.bind(1)() // Window
// 箭头函数没有自己的this
// 所以不能用call()、apply()、bind()这些方法去改变箭头函数内部的this指向
2.5 call/apply/bind强制改变this
call/apply/bind 都是用来改变this指向的。如果是基于call/apply/bind强制改变了函数中的this,也是以强制改变的为主
- call/apply都是立即执行函数,并且改变函数中的THIS,再并且给函数传递参数信息。唯一的区别就是传参的方式不同,call如果是有多个参数传给函数,需要用逗号分开依次传入。而apply则是直接放到一个数组中
- bind不会把函数立即执行,它是预先处理函数中的this和参数,bind返回的是一个函数。多次 bind 时只认第一次 bind 的值
function fn(x, y) {
console.log(this, x, y)
}
let obj = {
name: 'zhangsan'
}
fn(10, 20) // window, 10, 20
fn.call(obj, 10, 20) // {name: 'zhangsan'}, 10, 20
fn.apply(obj, [10, 20]) // {name: 'zhangsan'}, 10, 20
document.body.onclick = function (e) {
// 创建一个匿名函数,绑定给点击事件,触发BODY点击行为的时候,执行的是匿名函数
// 此时的this指向的是body元素
console.log(this)
}
// 可以通过bind方法改变this,点击body触发事件,此时的this会指向obj
document.body.onclick = fn.bind(obj, 10, 20)
3. 实现call/apply/bind方法
/*
* 重写内置的call/apply:把需要执行的函数和需要改变的this关联在一起
* 原理:
* 1.普通函数被调用时,谁把这个函数执行了,函数里的this就指向谁
* 2.在call内部,给传入的对象设置属性,并把当前执行的函数赋值给这个属性
*/
Function.prototype.call = function (thisArg, ...params) {
thisArg = (thisArg !== null && thisArg !== undefined) ? Object(thisArg) : window
let fn = this,
result = null,
key = Symbol('KEY') //新增的属性名保证唯一性,防止污染了原始对象中的成员
thisArg[key] = fn // 相当于 obj[key] = fn
result = thisArg[key](...params) // 相当于 obj.fn()
delete thisArg[key] //用完后移除自己新增的属性
return result
}
// apply和call类型,唯一的区别就是传参方式不同而已
Function.prototype.apply = function (thisArg, argArray = []) {
thisArg = (thisArg !== null && thisArg !== undefined) !== undefined ? Object(thisArg) : window
let fn = this,
result = null,
key = Symbol('KEY')
thisArg[key] = fn
result = thisArg[key](...argArray)
delete thisArg[key]
return result
}
function fn(x, y) {
console.log(this, x, y)
}
let obj = {
name: 'zhangsan'
}
fn.call(obj, 10, 20)
fn.apply(0, [10, 20])
/*
* 重写内置bind:柯理化思想「预处理思想」
*/
Function.prototype.bind = function bind(thisArg, ...argArray) {
thisArg = (thisArg !== null && thisArg !== undefined) !== undefined ? Object(thisArg) : window
let fn = this
function proxyFn(...args) {
let key = Symbol('key')
thisArg[key] = fn
const finalArgs = [...argArray, ...args]
let result = thisArg[key](...finalArgs)
delete thisArg[key]
return result
}
return proxyFn
}
function fn(x, y, e) {
console.log(this, x, y, e)
}
let obj = {
name: 'zhangsan'
}
document.body.onclick = fn.bind(obj, 10, 20)