最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • (翻译)理解JavaScript函数调用和"this"

    正文概述 掘金(Husky13)   2020-12-11   321

    多年以来,我看到了许多人对于JavaScript函数调用方面存在困惑与不解。特别是许多人会抱怨,"this"在函数调用中的语义是令人疑惑的。

    在我看来,通过理解核心函数调用的基元,并且去看一下在此基础之上的其他方式的函数调用(对原始调用的思想的抽取)可以消除这些困惑。实际上,这正是ECMAScript 标准对于此的想法所在。在某些领域内来看,这篇文章是标准的简化,但是二者的基本思想是一致的。

    核心的原始函数调用

    首先,让我们来看一下核心的函数调用原始模型,一个Functioncall方法[1]。call方法相对比较直接。

    1. 取参数的第一个到最后一个组成一个参数列表(argList);
    2. 第一个参数是thisValue
    3. 调用函数,并把this设置为thisValue,同时argList作为它的参数列表。

    例如:

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

    正如你所见,我们调用了hello函数,把this设置为"Yehuda" 并传入了一个参数"world"。这是JavaScript函数调用的主要原始形式。你可以把所有其他的函数调用作为这个原始模式的替代来考虑。(要运用原始模型来调用其他函数就要用更便利的语法,并依据一个更基本的主要原始模型)

    注:[1] 在ES5标准中,call方法的描述基于其他的,更底层的基元,但是它是在那个原始模型上的非常简单的包裹,因此我在这里将其简化了。想了解更多可以参考这篇文章的最后的信息。

    简单的函数调用

    很明显,总是用call来调用函数将是令人难以忍受的。JavaScript允许我们用括号语法来直接调用函数(hello("world"))。当我们这么做的时候,调用是这样的:

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

    在ECMAScript 5 中,在严格模式下这个行为已经发生了变化[2]:

    // 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(几乎)总是被传入,当不在严格模式下时,被调用的函数应该改变this的值为全局对象。这允许严格模式的调用者避免打破已经存在的非严格模式库。

    成员函数

    下面一种非常常用的函数调用方式是函数作为一个对象的方法成员来调用(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") ,我们转换了一个角度,应用我们的基元call方法来改变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。它也遍历参数。

    因为this在某种程度上是一个常见的习语,ES5引入了一个新的bind方法给所有的Function对象来实现下面的行为:

    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里面的bind

    因为jQuery里面大量的应用匿名回调函数,它内部使用call方法来设置那些回调函数的this值为更有用的值。比如,在所有的事件处理器函数中,jQuery没有接收window作为this的值(如果你没有特殊的干预),而是对回调函数调用call方法,并将事件处理器函数作为元素的第一个参数。

    这极其有用,因为在匿名函数内部的this的默认值并不是特别有用,但是它会给JavaScript初学者一个这样的感觉:this一般是很奇怪的,并且是难以推测的经常变化的一个概念。

    如果你理解了从一个有糖分的函数调用到无糖分的函数调用func.call(thisValue, ...args)的转换规则,你应该就能操纵这个并不是十分阴险的 JavaScript this 值这一领域。

    typethis
    func(…args)Windowfunc(…args) func defined in ES5 Strict Modeundefinedpath.to.obj.func(…ars)path.to.obj

    附:我有所‘欺骗’

    在几个地方,对于规范的措辞我有所简化。或许最重要的‘欺骗’是我将func.call称为一个基元("primitive")。实际上,这个规范有一个基元(在内部被称为[[Call]])为func.callobj.]func()所共有。

    然而,让我们来看一下func.call的定义:

    1. 如果IsCallable(func) 结果为false,那么就抛出一个类型异常;
    2. argList 为一个空列表;
    3. 如果这个方法被调用的时候参数不止一个,那么从左到右开始将arg1追加每一个参数作为 argList 的最新元素;
    4. 返回调用func的内部方法[[Call]]的执行结果,提供thisArg作为this的值,argList作为参数的列表。

    正如你所见,这个定义本质上是一个很简单的JavaScript的语言绑定到基元[[Call]]操作符。

    如果你看一下函数调用的定义,前七步是设置thisValueargList,最后一步是:“返回 调用func的内部方法 [[Call]]的结果值,提供thisArg作为this的值,argList作为参数的列表”。

    一旦thisValueargList的值被确定,func.call的定义和函数调用的定义本质上是相同的字眼。

    我在称call为一个基元上做了一点欺骗,但是在本质上他们意思还是一样的,我在文章开头拿出规范且做了引用。

    还有很多案例(大多数文章会明显的包含with)我没有在文章中进行讨论。


    起源地下载网 » (翻译)理解JavaScript函数调用和"this"

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元