JSthis详解

JSthis详解

Posted by limantang on November 12, 2019

this详解

如何声明一个函数?

  1. const f = new Function(‘x’, ’y’, ‘return x + y’)
  2. function f(x, y) { return x + y }
  3. const f = function (x, y) { return x + y }
  4. const f = () => {return x + y}

其中1, 2, 3是ES6之前的语法, 支持this/arguments/new,4是ES6的语法不支持this/arguments/new 明确一下不支持的含义 举个例子

    const fn = () => {console.log(this)}
    //		window
    fn.call(null);
    //		window

不支持this不代表不可以访问this, 而是不接受指定this

这也和之前函数全解中所说的对应上, 箭头函数的this是函数所处的环境决定的,而不是像非箭头函数的普通函数,this是函数第一个参数决定的

this到底是参数还是环境呢?

箭头函数

对于箭头函数来说, this就是箭头函数所处的环境, 也就是平时所说的箭头函数是由外层的this决定, 之所以这样说就是因为this对于箭头函数来说是环境,不是参数

普通函数

对于普通函数来说, this是普通函数的一个隐式的参数, 请先记住这句话, 下面解释一下可能会遇到的几个疑惑的点 在解释之前,我先说一下我之前关于this的看法==>死记硬背 死记方法如下:

  • this是上下文(至今对于上下文这个抽象的概念还不是很理解, 当时我也是人云亦云)
  • 全局环境执行时, this是全局对象
  • 调用对象方法的时候, this是该对象
  • 函数里面调用函数时, this是全局对象
  • 箭头函数里的this, 不看调用, 看定义(静态作用域)
  • 箭头函数里的this指向函数外面的this
  • 用new关键字时候, this是新增的那个对象
  • 可以用call/apply/bind指定this

上述的方法就是我绝大部分关于this的知识点, 上述几点有正确之处, 但是也有很片面的点 接下来我说一下我自己关于this的新理解, 也顺便解释一下上面的那句话和大家可能遇到的疑惑点 首先举几个🌰

  • fn.call(undefined, 1, 2)
  • fn.bind(undefined, 1, 2)
  • obj.method.call(obj, 1, 2)

这三个例子很明确指定了this是什么, 函数中的this就是call的第一个参数, 或者是bind的第一个参数 再举几个隐式的🌰

  • fn(1,2) //fn.call(undefined, 1, 2)
  • obj.method('hi') //obj.method.call(obj, ‘hi’)
  • array[0]('hi') //array[0].call(array, ‘hi’)

其中第三个例子, 假设数组的第一项就是函数 上面三种函数的调用就相当于注释中的写法, 上面三种写法也是平时我们常用的函数调用方法

函数中this的确定方法

就是把函数中的this作为call, apply或者bind的第一个参数来确定, 虽然我们平时写代码不能所有代码全部都写成函数使用call去调用, 但是为了明确函数中令人迷惑的this, 我们可以在心中假想使用call去调用, 然后将this作为call的第一个参数就可以了

上面的方法有几个注意点

  • 函数普通调用, call的第一个参数就传入undefined即可
  • 作为对象的方法调用, call的第一个参数就传入该对象
  • 比较特殊的就是函数作为数组的元素调用时, call的第一个参数就是该数组

下面用一段代码来具体说明一下

    button.onclick = function (e) {
        console.log(this)
    }

请问this是什么? 其实这段代码就很好的验证了上面的说法, 大家的第一印象会说, this就是button这个按钮元素, 其实不然 首先这个this是什么是不确定的, 要看函数式如何被调用的, 如果是普通情况, 用户点击按钮触发函数被调用, 那么this确实是按钮本身 但是如果是这种情况被调用的呢

    const fn = button.onclick;
    fn();

这种情况this就是undefined 还有这种情况

  button.onclick();

代码层面去调用onclick, 虽然这种情况this确实是button元素本身 但是也验证了上面的说法, onclick作为对象的方法调用, call的第一个参数就是该对象

为了确定上面这个题目所给出答案的正确性,看一段很简单的代码

    function fn(x) {
        console.log(x);
    }

请问打印出的x是什么? 这个问题大家的答案肯定是: 这得看fn这个函数调用时传入的参数是什么才行啊, 同样的对于按钮元素的那个问题, this其实就是一个隐式的参数, 所以也必须等到被调用才能确定, 在没有被调用之前就是不确定的, 之所以说这个this是隐式的是因为如果函数被调用时不是显式的使用call去指定this, 那么这个this就是JS引擎帮忙偷偷的传进来的😭, 所以this的第一感觉就是个黑盒, 永远猜不到会传进来个什么东西

在看最后一段代码

    let length = 10;
    function fn() {
        console.log(this.length);
    }
    let obj = {
        length: 5,
        method(fn) {
            fn();
            arguments[0]();
        }
    }
    obj.method(fn, 1);

最终会输出啥呢(非严格模式), 建议大家别看答案, 自己好好想一想, 考察了好几个知识点, 其中只有两个知识点前面没有提到

无论你答案正确与否,看看我的观点探讨一下吧😁 先说一下这段代码在我浏览器里面执行的结果是: 0 2

大家的答案可能是 10 10 或者undefined 10或者其他的🤡 正确答案其实是: 不确定 2

  • 为什么打印的第一个确定不了呢, 因为和执行的环境有关系, 第一个打印也就是fn被调用的那个打印最终是window.length, window的length属性返回的是当前窗口中iframe的个数, 所以具体的值要看iframe个数确定, 这是第一个考察的知识点, 虽然不常用
  • 为什么是window.length呢, fn()这行代码其实是fn.call(undefined), 其实就是普通函数被调用, 由于是非严格模式, 所以this就是window了, 否则就是undefined
  • 为什么第一个打印的不是10呢, let声明的变量不会挂载到window上, 这一点不同于var
  • 为什么第二个打印的是2呢, arguments是个类数组, 也是个对象, arguments[0]也就是函数fn, 但是最大的区别就是被调用的方式,
    arguments[0]()相当于arguments[0].call(arguments), 所以最终的this就是arguments这个类数组(对象), 这个就是上面提到的知识点, 最终打印的就是arguments.length, 如果你不清楚arguments代表的是实参列表还是形参列表, 也会答错,arguments代表的是实参列表, length属性就是实参的个数

到此这个题解答完毕😀

记住this就是call的第一个参数, 额外记住两条规则, new关键字重新设计了this, 箭头函数不接受this 本次讨论的函数中的this,不包括new关键字的范畴, new以后文章在单独详细讨论一下