总结:js中的this指向

389 阅读5分钟

js中的this指向

1. this是什么?

  1. 在浏览器端运行JS代码,非函数中的this一般都是window;研究this都是研究函数中的this。有一个特殊的,就是ES6+中“块级上下文”中的this,是其所在上下文中的this,这也可以理解为:块级上下文是没有自己this。
  2. this就是一个指针,指向调用函数的对象。常说的this,指的是函数的执行主体。也就是谁把这个函数执行了,函数里的this就指向谁。当然会存在特殊情况,这个后面会说到

2. this的几种绑定规则

想要分清楚函数执行的执行主体(this),可以按照如下几种情况来分析:

  1. 事件绑定
  2. 普通函数执行
  3. 构造函数执行(new)
  4. 箭头函数执行
  5. 基于call/apply/bind强制改变this

2.1 事件绑定

不论是DOM0还是DOM2级事件绑定,给元素的某个事件行为绑定方法,当事件触发方法执行,方法中的this是当前元素本身。

特殊情况:

  1. IE6~8中基于attachEvent实现DOM2事件绑定,事件触发方法执行,方法中的this不在是元素本身,大部分情况都是window
  2. 如果事件回调是基于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 普通函数执行

  1. 函数执行,看函数前面是否有“点”。如果有“点”,“点”的前面是谁,那么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__
  1. 自执行函数执行:其函数体内的this一般都是window,严格模式下则是undefined
  2. 回调函数中的this一般也是window/undefined。除非做过特殊的处理,亦或是正常的DOM事件绑定

2.3 构造函数

当使用new关键字调用函数时,函数中的this一定是在构造函数创建的实例对象。

箭头函数不能当做构造函数,所以不能与new一起执行,不然会报错

2.4 箭头函数

箭头函数是ES6中新增的,它和普通函数有一些区别。箭头函数没有自己的this,它的this继承于外层代码库中的this。

箭头函数在使用时,需要注意以下几点:

  1. 创建箭头函数时,就已经确定了它的 this 指向
  2. 箭头函数内的 this 指向外层的 thi
  3. 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误
  4. 箭头函数没有arguments对象
  5. 箭头函数没有自己的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,也是以强制改变的为主

  1. call/apply都是立即执行函数,并且改变函数中的THIS,再并且给函数传递参数信息。唯一的区别就是传参的方式不同,call如果是有多个参数传给函数,需要用逗号分开依次传入。而apply则是直接放到一个数组中
  2. 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)