zoukankan      html  css  js  c++  java
  • (翻译)理解JavaScript函数调用和"this"(by Yehuda Katz)

    多年以来,我看到大量关于javascript函数调用的困惑。尤其,许多人抱怨函数调用中“this”的语意是混乱的。

    在我看来,大量这样的混乱可以通过理解核心函数调用原语被清理,然后再看所有其他在原语之上进行包装的调用函数的方法。实际上,这正好是ECMAScript规格对这个问题的考虑。在某些领域,这个是一个规格的简化,但基本思想是一样的。

    核心原语

    首先,我们来看核心函数调用原语,一个函数的调用方法[1]。这个调用方法是相对直线向前的(The call method is relatively straightforward.)。

    1.     构造参数列表(argList)从参数1到最后

    2.     第一个参数是thisValue

    3.     把this 赋值给thisValue 并用argList 作为参数列表调用函数

    例如:

    function hello(thing) {
      console.log(this + " says hello " + thing);
    }
     
    hello.call("Yehuda", "world") //=> Yehuda says hello world

    正如你看到的,我们通过把this赋值给 "Yehuda"和一个单一参数来调用hello 方法。这就是javascript函数调用核心原语。你能想象所有其他的函数调用都是对这个原语包装。(包装是使用一个便利的语法和按照更基本的核心原语描述它)

    [1] In the ES5 spec, the call method isdescribed in terms of another, more low level primitive, but it’s a very thinwrapper on top of that primitive, so I’m simplifying a bit here. See the end ofthis post for more information.

    简单函数调用

    很明显,任何时候使用call 调用函数都是相当的烦人的。Javascript允许我们使用括弧直接调用函数(hello("world"))。我们这样做的时候,调用包装为:

    function hello(thing) {
      console.log("Hello " + thing);
    }
     
    // this:
    hello("world")
     
    // desugars to:
    hello.call(window, "world");
    

    这个行为在ECMAScript中只有当使用严格模式时改变了:

    // this:
    hello("world")
     
    // desugars to:
    hello.call(undefined, "world");
    

    短版本:函数调用fn(...args)和fn.call(window [ES5-strict: undefined], ...args)等同。

    需要注意的是,函数内联声明也是正确的:(function() {})()和(function(){}).call(window [ES5-strict: undefined)等同。

    [2]实际上,我说了点谎。ECMAScript 5规格说一般(大多情况)传递的是undefined ,但被调用的函数在非严格模式时应该改变它的thisValue 为全局对象。这允许严格模式调用者避免破坏现存的非严格模式库。

    成员函数

    下一个非常常见的方法是调用作为对象的成员方法(person.hello())。在这种情况下,调用包装为:

    var person = {
      name: "Brendan Eich",
      hello: function(thing) {
        console.log(this + " says hello " + thing);
      }
    }
     
    // this:
    person.hello("world")
     
    // desugars to this:
    person.hello.call(person, "world");
    

    注意,和hello 方法是怎么以这种方式附加到对象上的没有关系。 记住我们之前作为独立函数定义的hello 。我们来看看如果动态的附加到对象上发生了什么:

    function hello(thing) {
      console.log(this + " says hello " + thing);
    }
     
    person = { name: "Brendan Eich" }
    person.hello = hello;
     
    person.hello("world") // still desugars to person.hello.call(person, "world")
     
    hello("world") // "[object DOMWindow]world"
    

    注意这个函数没有确定的“this”概念。它总是在调用时基于它的调用者的调用方式设置。

    使用Function.prototype.bind

    因为有时有一个确定this 值的函数引用会方便一些,人们使用一个简单的封闭把戏来转换一个函数为一个不变的this:

    var person = {
      name: "Brendan Eich",
      hello: function(thing) {
        console.log(this.name + " says hello " + thing);
      }
    }
     
    var boundHello = function(thing) { return person.hello.call(person, thing); }
     
    boundHello("world");
    

    尽管通过我们的boundHello 调用仍然解释为boundHello.call(window,"world"),我们转了一圈然然后使用我们原语调用方法来修改this 值为我们期望的值。

    我们可以进行一些调整达到这个通用的目的:

    var bind = function(func, thisValue) {
      return function() {
        return func.apply(thisValue, arguments);
      }
    }
     
    var boundHello = bind(person.hello, person);
    boundHello("world") // "Brendan Eich says hello world"
    

    为了理解这一点,你只需要再多了解两条信息。首先,arguments 是一个代表所有传递到函数的参数的类数组对象。其次,apply方法和call 原语工作原理完全一样,除了它带了一个类数组对象代替每一次列出参数。

    我们的bind 方法简单的返回一个新的函数。当它被调用时,我们的新函数简单的调用了传入的原始函数,设置原始值为this。它也通过参数传递。

    因为这是一个比较常见的习语,ES5为所有的Function 对象引入一个新的bind 方法,它实现下面的行为:

    var boundHello = person.hello.bind(person);
    boundHello("world") // "Brendan Eich says hello world"
    

    当你需要一个原始函数作为回调传递时这更有用:

    var person = {
      name: "Alex Russell",
      hello: function() { console.log(this.name + " says hello world"); }
    }
     
    $("#some-div").click(person.hello.bind(person));
     
    // when the div is clicked, "Alex Russell says hello world" is printed
    

     

    当然,这有点笨拙,并且TC39(委员会正在制定的下一个ECMAScript版本)继续致力于更优雅的,向后兼容的方案。

    在Jquery中

    因为jQuery大量使用匿名回调函数,它内部使用call方法设置那些回调的this 值为更有用的值。例如, 在所有的事件处理中替代接收window 作为this(如你没有特别的处理),jQuery在回调中使用建立事件处理器的元素作为它的第一个参数调用call 。

    这非常有用,因为匿名调用中this 的默认值不是特别的有用,但它可以给JavaScipt初学者的印象,this 通常是奇怪的,常常改变,很难确定的概念。 (原文:but it can give beginners to JavaScriptthe impression that this is, ingeneral a strange, often mutated concept that is hard to reason about.)

    如果你理解了基本规则,转换一个包装的函数调用为一个非包装的func.call(thisValue,...args),你应该能导航不那么变化莫测的Javascript this 值水域。(原文:you should be able to navigate the not sotreacherous waters of the JavaScript this value.)

    附录:我行骗了

    在一些地方,我从规格精确的语法简化了现实一点。可能最重要欺骗是我称func.call为“原语”的习惯。实际上,该规格有一个原语(内部引用为[[Call]])是func.call 和[obj.]func()使用。

    不管怎样,一起来看看func.call定义:

    1.    如果IsCallable(func)是false,则抛出一个TypeError异常。

    2.    置argList为空列表。

    3.    如果方法被调用时使用超过一个参数,那么以从左到右顺序从arg1开始附加每个参数作为argList最后的元素。

    4.    返回调用func的[[Call]]内部方法的结果,提供thisArg作为this值,和argList作为参数列表。

    正如你看到的,这个定义是本质非常简单的对原语[[Call]] 操作的JavaScript语言绑定。

    如果你看一下调用函数的定义,七步的第一步是建立thisValue和argList,最后一步是:“返回在func中调用[[Call]]内部方法的结果,提供thisValue作为this值并提供参数列表作为argument的值。”

    它的本质是相同的语法,一旦argList 和thisValue 被确定。

    我撒了一点谎,在把call 叫做原语,但它的意思的本质是相同的,所以在本文的开始和引用的章节与段落我拉出规格。

    这也有一些附加的情况(尤其包括)在这里没有提到情况。

    这个条目在2011年8月11日星期四上午2:54被张贴,并在JavaScript下存档。你可以通过RSS2.0反馈跟随任意响应到这个入口。你可以跳到最后离开响应。Pinging当前不允许(原文:Pinging is currently not allowed.没有理解是什么意思)。

     

    原文地址:http://yehudakatz.com/2011/08/11/understanding-javascript-function-invocation-and-this/

  • 相关阅读:
    supervisor启动错误解决(二)
    删除文件某行
    离线安装
    docker中/var/lib/docker目录迁移
    if else 使用
    Django views函数添加装饰器
    Netty4
    Fast DFS(二)
    Fast DFS(一)
    SpringBoot和app之间跨域问题
  • 原文地址:https://www.cnblogs.com/zhepama/p/3080427.html
Copyright © 2011-2022 走看看