VueBloghyhero6

一言难尽的 this(笔记)

2020-10-18 / 2020-11-01 / 368次浏览

这几天 也是比较的,呃,时间啊都去哪了。 距离上次更新 已有一周。

那么不话不多说,植入主题:
一网打尽 this, 对执行上下文说 Yes

我又盗图了嘻嘻嘻。 这篇也就当成笔记吧。

咳咳,敲黑板。

调用函数会创建新的属于函数自身的执行上下文。执行上下文的调用创建阶段会决定 this 的指向。 到此我们得出的一个结论就是: this 的指向,是在调用函数时根据执行上下文所动态确定的。

1.在函数体当中,简单调用该函数时 (非显示 / 隐式绑定下),严格模式下 this 绑定到 undefined, 否则 绑定到全局对象 window / global;

2.一般构造函数new调用,绑定到新创建的对象上;

3.一般由 call / apply / bind 方法显示调用,绑定到指定参数的对象上;

4.一般由上下文对象调用,绑定在该对象上;

5.箭头函数中,根据外层上下文绑定的this决定this指向。

真实环境的多样,我们逐个梳理。

function f1( ) {
    console.log( this )
}
function f2( ) {
    'use strict'
    console.log( this )
}
f1( ) // window
f2( ) // undefined

查看下面的变种题目

const foo = {
    bar: 10,
    fn: f它们相当于unction( ) {
         console.log( this )
         console.log( this.bar )
    }
}
var fn1 = foo.fn
fn1( )

这里 this 仍然指向是 window 。 虽然 fn 函数 在 foo 对象中作为方法被引用,但是赋值给fn1 之后, fn1 的执行仍然是在 window 的全局环境中。因此 输出 window 和 undefined, 它们相当于:

console.log(window)
console.log(window.bar)

还是 上面 这道 题目,如果调用改变为:

const foo = {
    bar: 10,
    fn: function ( ) {
        console.log( this )
        console.log( this.bar )
    }
}
foo.fn( )

输出:

{ bar:10, fn: f }
10

因为这个时候 this 指向的是最后调用它的对象,在foo.fn( ) 语句中 this 指向 foo 对象。请记住:

在执行函数时,如果函数中的 this 是被上一级对象所调用,那么 this 指向的就是上一级的对象;否则指向全局环境。

const student = {
    name: 'Lucas',
    fn: function(){
        return this
     }
}
console.log(student.fn() === student)

最终结果将返回 true。

当存在更复杂的调用关系时,请看例题:

const person = {
    name: 'Lucas',
    brother: {
        name: 'Mike',
        fn: function () {
            return this.name
        }
    }
}
console.log(person.brother.fn())

在这种嵌套的关系中, this 指向 最后调用它的对象,因此输出将会是: Mike。

到此, this 的上下文对象调用已经理解得比较清楚了。我们看更高阶的题目:

const o1 = {
    text: 'o1',
    fn: function() {
        return this.text
    }
}

const o2 = {
    text: 'o2',
    fn: function() {
        return o1.fn
    }
}

const o3 = {
    text: 'o3',
    fn: function() {
        var fn = o1.fn
        return fn()
    }
}

console.log(o1.fn())  
console.log(o2.fn())  
console.log(o3.fn())

不知道是是否答对, o1 , o1, undefined

让我们来一一分析。

第一个console.log 最简单,和上面的一样直接找最近一个级别的输出,难点在第二个和第三个上面,关键还是看调用 this 的那个函数。

第二个 console.log 的 o2.fn(),最终还是调用o1.fn(),因此答案仍然是o1.

后一个,在进行 var fn = o1.fn 赋值之后,是"裸奔"调用,因此这里的this 指向 window,所以答案居然是undefined
有兴趣的小伙伴可用动手实验一波 最后一段代码。

发散思维我们再看,
我们想 console.log(o2.fn( )) 输出 o2, 我们需要怎么做。

常规思路

直接就是一波
o1.fn.call(o2)

一般来说 会想到 使用 bind/call/apply 来对 this 的指向进行干预,这确实是一种思路,但是来说不使用 bind/call/apply,还有别方法嘛。

还是那个重要结论:this 指向最后调用它的对象,在 fn 执行 时,挂到 o2 对象上即可,我们提前进行了赋值操作

const o1 = {
   text: 'o1',
   fn: function() {
       console.log(this.o1)
       return this.text
   } 
}

const o2 = {
    text: 'o2',
    fn: o1.fn
}

console.log(o2.fn())
console.log(o1.fn())  

汝如何穿品如之衣,

这种写法好风骚呀, 大写的优秀。

有兴趣的小伙伴可以试试

上文 引申出了 bind/call/apply, 在这个概念上,比较常见的基础考点是:bind/call/apply 三个方法的区别。

这种问题相对基础,我们直接上答案:一句话总结,他们都是用来改变相关函数 this 指向的,但是 call / apply 是直接进行
相关函数调用; bind 不会执行相关函数,而是返回一个新的函数,这个新的函数已经自动绑定了新的 this 指向,开发者需要 手动调用即可。再具体的 call / apply 之间的区别主要区别在参数设定上

代码总结:

const target = {}
fn.call(target, 'arg1','arg2')

相当于:

const target = {}
fn.apply(target, ['arg1','arg2'])

相当于:

const target = {}
fn.bind(target, 'arg1','arg2')()

具体基础用法这里不详解了。

上代码我们看一道例题解析:

const foo = {
    name: 'lucas',
    logName: function() {
        console.log( this.name )
    }
}
const bar = {
    name: 'mike'
}
console.log(foo.logName.call(bar))

将输出 'mike',这段代码我们应该不难理解

下面看看构造函数 和 this

还是直接上代码

function Foo() {
    this.bar = "Lucas"
}
const instance = new Foo()
console.log(instance.bar)

答案输出 'Lucas'。 但是这样的场景往往伴随着下一个问题:new 操作符调用构造函数,具体是做了什么,以下提供参考
1.创建一个新的对象;
2.将构造函数的this指向这个新对象;
3.为这个对象添加属性,方法等;
4.最终返回新对象。

以上过程,我的博客笔记也有提到过。有更加详细的解释和说明

代码表述

var obj = {}

obj.__proto__ = Foo.prototype
Foo.call(obj)

这里肯定是一个最基础简单版的,这里主要是说this 不在做进一步细致化的讨论了

需要指出一个点,如果在构造函数中出现了显示 return 的情况,那么需要注意分为俩种场景:

function Foo (){
    this.user = "Lucas"
    const o = {}
    return o
}
cosnt instance = new Foo()
console.log(instance.user)

将会输出 undefined, 此时 instance 是返回的空对象 o。

function Foo() {
    this.user = "Lucas"
    return 1
}
const instance = new Foo()
console.log(instance.user)

将会输出 Lucas,也就是说此时 instance 是返回的目标对象实例 this。

我们得到一个结论:如果构造函数中显示返回一个值,且返回的是一个对象,那么 this 就指向这个返回对象;如果返回的不是一个对象,那么 this 仍然指向实例。

箭头函数中 的 this 指向

首先我们再来温习一下相关结论。

结论:箭头函数使用 this 不适用以上标准,而是根据外层(函数或者全局)上下文来决定。

来看题目:

const foo = {
    fn: function ( ) {
        setTimeout( function ( ){
            console.log(this)
        })
    }
}
console.log(foo.fn())

这道题目中, this 出现在 setTimeout() 中的匿名函数里,因此 this 指向 window 对象。如果需要 this 指向 foo 这个 object
对象,可以巧用箭头函数解决:

const foo = {
    fn: function () {
        setTimeout(() => {
            console.log(this)
        })
    }
}
console.log(foo.fn())

单纯箭头函数中的 this 非常简单,但是综合所以情况,结合 this 的优先级考察, 这时候 this 指向并不确定

this 优先级相关
我们常常把通过 call、apply、bind、new 对 this 绑定的情况称为显示绑定;根据调用关系确定的 this 指向称为 隐式绑定。
那么显式绑定和隐式绑定谁的优先级更高呢?

function foo (a) {
    console.log(this.a)
}

cosnt obj1 = {
    a: 1,
    foo: foo
}

const obj2 = {
    a: 2,
    foo: foo
}
obj1.foo.call(obj2)
obj2.foo.call(obj1)

输出 分别为 2、1,也就是说 call、 apply 的显式绑定一般来说优先级别更高

看代码:

function foo (a){
    this.a = a
}
const obj1 = {}

var bar = foo.bind(obj1)
bar(2)
console.log(obj1.a)

上述代码通过bind,将bar 函数中 的this 绑定为 obj1 对象。 执行 bar(2) 后, obj1.a 值为 2。经过 bar(2).执行后 obj1 对象为 {a: 2 }

当再使用 bar 作为构造函数时:

var baz = new bar(3)
console.log(baz.a)

将会输出 3。 我们看 bar 函数本身是通过 bind 方法构造函数,其内部已经对将 this 绑定 为 obj1, 它再作为构造函数,通过 new 调用时, 返回的实例 已经 与 obj1 解绑。 也就是说:

new 绑定修改了 bind 绑定中的 this, 因此 new 绑定的优先级 比 显式 bind绑定更高:

我们的再看:

function foo () {
    return a => {
        console.log(this.a)
    }
}

const obj1 = {
    a: 2
}

const obj2 = {
    a: 3
}

const obj2 = foo.call(obj1)
console.log(bar.call(obj2))

将会输出 2。由于 foo() 的this 绑定到 obj1, bar (引用箭头函数)的 this 也会绑定到obj1,箭头函数的绑定 无法被修改。

如果将 foo 完全写成箭头函数的形式:

var a = 123
const foo = () => a=> {
    console.log(this.a)
}

const obj1 = {
    a: 2
}

const obj2 = {
    a: 3
}

var bar = foo.call(obj1)
console.log(bar.call(obj2))

将会输出123

在抖个机灵

const a = 123
const foo = () => a => {
    console.log(this.a)
}

const obj1 = {
    a: 2
}

const obj2 = {
    a: 3
}

var bar = foo.call(obj1)
console.log(bar.call(obj2))

答案将会输出 undefined, 原因 是因为 使用 const 声明的变量不会挂载到window 全局对象当中。(这个我也是第一次知道)。因此 this 指向 window 时,自然也找不到 a 变量了。关于 const 或者 let 等声明变量不在这里讨论,请移步到我相关博客😹。

this 这版就暂时写到这里

后记:原版是大佬的收费课程,我更新出来呢反正也没人看,私人博客。所以就不贴链接了,因为贴了你们也链接不进去。毕竟有人家大佬版权在。

js