美文网首页
JavaScript Tips: JavaScript 中的 t

JavaScript Tips: JavaScript 中的 t

作者: 云之外 | 来源:发表于2017-04-14 21:08 被阅读84次

    this 的引用问题一直是 JavaScript 新人比较头疼的问题。前段时间阅读了方应航老师关于 this 的文章,加深了对 this 的理解。同时,在实际项目中遇到了一些文中没有提到的关于 this 的用法,特此整理一下。

    勘误:之前发布的文章,将 call 误写成了 applycallapply 的效果是一样的,第一个参数都接收的是函数的 context 。只不过 apply 将函数的参数变成了数组进行传递而已。

    上下文环境( context )

    简单来讲,上下文环境指的是当前代码片段(函数)运行时所处的环境。
    在 JavaScript 中,每一个函数在执行的时候都会被赋予一个 context ,即函数运行的上下文环境(执行上下文),这个环境通常是一个对象。在函数中,我们使用 this 访问函数的执行上下文。这个上下文环境随着函数的调用方式、形式、位置等的不同会发生变化,因此我们无法直接依赖函数声明时的上下文环境来进行某些操作。这其中最典型的就是 setTimeout 这样的异步函数。
    举几个简单的例子,来观察一下函数的执行上下文:

    • 这是一个定义在全局环境的函数 foo,我们在全局环境中调用它:

      得到了全局对象 Window ,蛮合理的。(当然这么理解是不完全正确的,慢慢往下看)

    • 我们定义一个 obj 对象,其中的 foo 属性指向刚才定义的 foo 函数:

      此时虽然 obj 中的 foo 直接指向了全局 foo 函数,但是其执行结果却变成了 obj 对象。

    • 我们反过来再试一下:


      结果也反过来了。
      更难受的是 setTimeout 这样的方法:

    由此证明,函数的执行上下文与函数声明时的上下文不一定相同。所以我们有的时候会看到这样的写法,用来保存函数依赖的上下文环境:


    为什么会有这种差别呢?
    在 JavaScript 中,“万物皆对象”。每一个 function 其实是由 Function 类生成的一个对象。在执行函数调用时,其实是执行了一个语法糖,真正被调用的是函数的 call 内置方法。这个方法接收两种参数:call(context, [arg1, [arg2..)context 便是这个函数执行的上下文,即 this 。来做一个有点暴力的实验:

    我们尝试强制指定 foocontextobj2 ,结果显然 foothis 被绑定为了 obj2
    而 JavaScript 又是如何执行这个语法糖的呢?我们肯定会这么猜:JavaScript 会自动向前调用这个函数的的对象,并将这个对象作为 context 再执行 call 。这样说并没有错,但是不全面,来看下边几个实验:

    • 先创建一个 father 对象:

      显然这个是符合我们猜想的。

    • 然后我们再创建一个 child 对象:

      显然也符合我们的猜想。

    • 现在我们把两个对象结合起来:


      想必和一些人猜想的不一样吧。

    JavaScript 只会寻找最终调用该函数的对象,而不会向前追溯。
    不过还有一个问题,为什么直接执行函数的时候,会输出 window 这个对象。是因为在浏览器中所有的对象都是 window 的属性,所以 foo() 等价于 window.foo() 吗?答案是否定的。
    在 JavaScript 中,如果函数是直接调用的,而不是源自于某个对象,函数的 call 方法的 context 将会被定义成 undefined 。所以 foo()foo.call(undefined) 是完全等价的:

    在浏览器策略中,函数 context 如果为 undefined,将会自动绑定全局对象 window。这种绑定在 JavaScript 严格模式下会被禁止。

    按照规矩来也不行?

    有些写在函数里的函数(或者说,闭包),会丢失原函数的上下文。其实也不怪它,因为函数的执行上下文是不会继承的:


    如果你理解了刚才对 call 的解读,你也许就会认为:inner 并没有被任何对象调用,而是直接被执行了,自然会丢失上下文。这样的理解在这个例子中是正确的,但是当函数作为回调时会复杂一些。
    回调函数的调用方式与回调函数的执行者有关,其 this 与执行者执行函数时为其指定的 context 有关。没有指定 context 的结果与上边的结果是一致的,但指定了 context 的就不一定了,要仔细阅读文档。

    关于 bind

    很多时候,由于执行者的不可靠性,或者其他的原因,我们想为函数手动绑定 context 。JavaScript 为我们提供了 bind 方法,返回一个绑定了上下文的函数,来改写一下上边出现的 setTimeout 的例子:

    特殊语法:[]

    function fn () {
        console.log(this)
    }
    var arr = [fn]
    arr[0]()
    

    这样的函数调用,调用对象是数组 arr 本身,所以它将被作为 context 传入:

    箭头函数

    ES6 为了解决 this binding 这个让人非常头疼的问题,提供了一种新的函数声明方式:箭头函数。箭头函数会自动绑定函数声明时所在的上下文的 this 。关于箭头函数具体的信息可以查阅箭头函数 | MDN
    我们可以用箭头函数改写上面出现的 setTimeout 的实验:

    总结

    函数的 this 最核心的地方就是掌握函数的 call 方法和函数 callcontext 的推导规则。还有就是注意回调函数和闭包的 this ,因为他们的执行可能并没有经过对象调用,所以很可能丢失 context ,或者指向了别的 context

    相关文章

      网友评论

          本文标题:JavaScript Tips: JavaScript 中的 t

          本文链接:https://www.haomeiwen.com/subject/fosgattx.html