likes
comments
collection
share

干翻this这座大山(一)

作者站长头像
站长
· 阅读数 25

一,this的作用

  1. 在对象内部方法中使用对象内部的属性
  2. 更加便利,减少代码量
let obj = {
    myName: 'Tom',
    age: 18,
    bar: function () {//在对象内部方法中使用对象内部的属性
        console.log(this.myName);
    }
}
obj.bar();

在如上代码中,我们构造了一个对象字面量,在其属性集合中放了一个函数。现在我想在这个函数里面查找与他并列的属性名,所有我在最后调用了bar函数。现在问题来了,如果在bar函数中不使用this指明的话是无法访问到bar所在作用域的属性中的myName属性的,不用this指明代码执行会报错,这是为什么呢?

大家再来看这一串代码

function identify(context) {
    return context.name.toUpperCase()
}

function speak(context) {
    var greeting = 'Hello I am' + identify(context)
    console.log(greeting);
}

var me = {
    name: 'xioaxiao'
}

speak(me) 

这是一串肥肠普通的代码,我们在全局声明了name=xiaoxiao,并且构造了两个函数。第一个函数 identify(context) 的作用是接受一个参数 context,并尝试访问该参数对象的 name 属性,然后通过toUpperCase()将其转换为大写形式并返回。在第二个函数中我们声明了一个问候语greeting,这个问候语以 'Hello I am ' 开头后接我们调用的第一个函数。这串代码的结果显而易见----Hello I amXIOAXIAO。对于这种类型的代码我们可以用this加以优化:

function identify() {
    return this.name.toUpperCase()
}

function speak() {
    var greeting = 'Hello I am' + identify.call(this)
    console.log(greeting);
}

var me = {
    name: 'xioaxiao'
}

speak.call(me) 

在这种通过this优化过后的代码中我们省去了传来传去的麻烦参数context,提升了代码的维护,预防了其中一个参数出现失误导致整个代码的瘫痪。

经过上面两题我们可以总结:

  1. 为什么要有this,因为在JS中没有访问自己的方法,this让对象中的函数有能力访问自己对象的属性
  2. this可以显著提升代码质量,减少上下文参数传递

this的主要作用场景:全局作用域和函数作用域,

二,this的指向,绑定规则

  • 浏览器环境下的全局thiswindow对象)。

  • Node.js环境下的全局thisglobal对象)。

当this在全局中运行时,代指的就是全局window;当写在函数体内时,代指的就是某一块作用域(当然某一块作用域也包括全局作用域)。 要明白this的底层绑定规则,必须牢记下面两点:

1. 默认绑定

当一个函数独立调用不带任何修饰符的时候,此时触发默认绑定;函数在哪个词法作用域下生效,函数中的this就指向哪里。简单来说----只要是默认绑定(即函数被独立调用时),this一定指向window(全局作用域)。

独立调用:在 JavaScript 中,当函数不是作为对象的方法被调用,也不是通过 new 关键字作为构造函数被调用,而是直接通过函数名来调用时,我们称这种调用方式为“函数被独立调用”或“函数作为函数调用”。在独立调用的情况下,函数内部的 this 值通常指向全局对象

词法作用域:我们在前面几篇文章中的《浏览器中的JS执行机制3--面试难点:闭包》一文中有过详细的讲解,词法作用域由函数声明的位置来决定,跟函数在哪里调用没有关系如果函数声明在全局,那么该函数的词法作用域就在全局,如果声明在另外一个函数体内,那词法作用域就在该函数体内。在这里说通俗点就是起作用的主体所在的域。 让我们来看两道题运用一下这两个知识点:

function baz() {
    bar()
}

function bar(){
   foo()
}

baz()

问 foo里面的this指向哪?

我们首先分析这个代码结构,他由两个并列的函数和一个调用组成;我们调用了baz函数,baz函数被执行后调用了bar函数。我们不用管其他的,首先,一个问题,函数是不是被独立调用的?当然是。第二个问题,foo()的词法作用域在哪?可不就是在全局嘛,foo在函数bar内,函数bar就声明在全局。或者我们直接秒杀,foo()不就是独立第哦啊有吗,this不就指向全局嘛。那么答案显而易见了,foo()内的this指向全局。很简单对不对~

function foo() {
    console.log(this.a);
}
var a = 1
foo()

在全局中用var声明的变量相当于在全局window上添加了一个属性,用let和const声明的变量在块级作用域内,this无法指向。在上面代码块中的this.a能在全局拿到a的值。只要函数是独立调用,就触发默认绑定,和函数在哪里在全局还是其他函数体里调用无关,同理:

var boj = {
    a: 1,
    b: function () {
        foo();
    }
}

function foo() {
    console.log(this.a);
}
var a = 2

在上面代码中的foo依然是独立调用,词法作用域在全局,默认绑定,指向的依然是全局a=2

2. 隐式绑定:

当函数的引用有上下文对象时(当函数被某个对象所拥有时, 当函数作为对象的方法被调用时),函数的this指向引用他的对象;如果函数是作为对象的一个属性被调用,那么 this 将绑定到该对象。我们看这一题,

var obj = {
    a: 1,
    foo: foo
}
var obj2 = {
    a: 2,
    obj: obj
}

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

obj2.obj.foo()

我们首先分析一下这串代码,在函数obj中发生了foo的引用,在obj2中发生了obj的引用,最后调用了foo函数。我们很明显的知道,这里不是独立调用,foo的调用嵌套了多层对于两次单纯的函数引用来说,代码块应该变成这个样子:

var obj2 = {
    a: 2,
    obj:  {
        a: 1,
        foo: function foo() {
            console.log(this.a);
        }
    }
}

obj2.obj.foo()

分析这种形式的题目我们不得不科普一个新知识点----隐式丢失

隐式丢失:

当一个函数被多个函数链式调用时,函数的this指向最近的那个对象

这也基本就是隐式绑定的规则,当函数发生嵌套时首先应该想到的知识点。所以对于这道题来说this指向的仍然是a=1。

小知识点

对于代码串

var val = 123
var b = 'abc'

var o = {
a:1,
b:val
}

请问上题中b的值是多少?如何将o中的b值变为全局中的b值?

相信有基础的小伙伴们看这一题时能够迅速的分离全局中的b和o中的b.这是两个不同的b,o中的b只是一个属性名。那我们怎么样才能把全局中的b值赋给他呢?我们要记住一句话----对象中的key默认就是字符串,我们把b加上中括号变为[b]代表的是一个b变量,

var val = 123
var b = 'abc'

var o = {
a:1,
[b]:val
}

你看,如果加上中括号的话,再打印var中的值你会发现出来的是abc:123,这个操作把b的指向变成全局中的b。

结语

以上就是this的基本绑定规则,熟悉掌握以上知识点能帮我们有效的判断this的指向,对我们前端的学习非常重要。this的指向是由函数调用的方式和上下文决定的,我们有没有什么方式可以改变this的指向呢?欢迎大佬在评论区讨论~

转载自:https://juejin.cn/post/7369019487625297931
评论
请登录