第一次翻译,翻译的很差,已经再尽全力s去翻译了,若是哪里看不明点,请出门左转下边原文地址javascript
英文原文点击这里java
javascript中最使人困惑的东西就是this关键字,它在每一个函数做用域中都会自动定义的一个特殊的标识符,可是,它折磨着每个javascript开发者,哪怕是有经验的。git
任何科技的充分进步,都和魔法没什么区别程序员
javascript的this机制事实上并非先进的,可是开发者常常按照他们本身的观点把this解释的复杂和混乱。毫无疑问这是由于缺乏深入的理解。github
既然this机制让开发者甚至是经验丰富的程序员感到困惑。那为何会是有用的,thia弊大于利吗?在此以前,咱们先跳过how的问题,去审查一下why的问题。ide
让咱们来讲明使用this的动机和实用性。函数
function identify() { return this.name.toUpperCase(); } function speak() { var greeting = "Hello, I'm " + identify.call( this ); console.log( greeting ); } var me = { name: "Kyle" }; var you = { name: "Reader" }; identify.call( me ); // KYLE identify.call( you ); // READER speak.call( me ); // Hello, I'm KYLE speak.call( you ); // Hello, I'm READER
若是看不明白这个代码块,不要着急!咱们很快会解释,把这些问题放到一边,咱们先来看关于why的问题。学习
这个代码块容许identify()和speak()两个函数分别被两个对象me和you重用,而不是为每一个对象单独写一个版本的函数。this
经过依赖this。你能够用一种更明确的方式在环境对象中使用两个方法。prototype
function identify(context) { return context.name.toUpperCase(); } function speak(context) { var greeting = "Hello, I'm " + identify( context ); console.log( greeting ); } identify( you ); // READER speak( me ); // Hello, I'm KYLE
this机制提供一种更加优雅的方式去传递一个对象引用,从而实现更加清楚的API设计和更简单的重用。
你用的模式越复杂,你就越能清晰的看到,传递上下文的时候一个显示的参数要比传递一个this上下文更加麻烦。当咱们查看对象和原型的时候,你将能看到一个可以自动引用正确的函数集合上下文对象的实用性。
咱们很快会开始解释this其实是如何工做的,可是首先要纠正一下错误的观念。
第一个常常被解释成困惑的是this指代函数自己,至少这却是一个合理的解释。
为何你会须要在函数内部引用函数自身,最可能的缘由是递归(在函数内部调用函数自身)或者事件处理的时候当第一次调用时解绑事件。
开发者最新的js机制是把函数当成一个对象同样来引用(js里全部的函数都是对象),这样会致使须要在函数互相调用之间存储状态(属性值)。固然这种机制可行的,也有一些有限的用处。这本书剩下的部分将会阐述许多其余的模式,以便更好的存储函数对象以外的状态。
可是等一会,咱们将会探索一种模式,来讲明this是如何不让函数得到自身的引用,就像咱们以前假设的同样。
考虑下边的代码,咱们尝试去跟踪一下foo函数被调用了多少次。
function foo(num) { console.log( "foo: " + num ); // keep track of how many times `foo` is called this.count++; } foo.count = 0; var i; for (i=0; i<10; i++) { if (i > 5) { foo( i ); } } // foo: 6 // foo: 7 // foo: 8 // foo: 9 // how many times was `foo` called? console.log( foo.count ); // 0 -- WTF?
foo.count依然是0,即便通过了四次console以后咱们能清晰的看到foo事实上调用了四次,这个让人失望的源头在于对this是什么的解释太字面。
当代码执行到foo.count = 0的时候,确实,它给foo函数对象添加了一个属性count,可是this.count只是在函数内部引用,this并非指向全部其余的函数对象,即便属性名称相同,由于this所在的对象不同,因此困惑就产生了。
一个有责任的开发者应该问到这一点:若是我增长一个count属性可是这个属性并非我想要的,那我是否增长了。事实上,若是开发者更深的挖掘,她会发现她建立了一个变量count,这个变量当前的值是NAN,一旦她注意到这个奇怪的结果,她将有一系列的疑问,全局对象是什么,为何这个值是NAN而不是数值。(在foo中打印count,会显示出NAN)。
有责任的开发者不该该在这一点中止而是应当深刻挖掘为何这个引用的表现没有想预想的那样,去回答这个棘手可是很重要的问题。许多开发者简单的避免这个问题,而且采起一些其余的解决办法,好比建立另外一个对象去存储count这个属性:
function foo(num) { console.log( "foo: " + num ); // keep track of how many times `foo` is called data.count++; } var data = { count: 0 }; var i; for (i=0; i<10; i++) { if (i > 5) { foo( i ); } } // foo: 6 // foo: 7 // foo: 8 // foo: 9 // how many times was `foo` called? console.log( data.count ); // 4
虽然这个确实解决了问题,可是不幸的是它忽略了真正的问题,不能理解this究竟是什么以及她是如何工做的,而是回到了一个更加熟悉的温馨环境,词法做用域(静态做用域)。
词法做用域是一个完美而有用的机制,我不会以任何方式轻视它,可是,不断的猜想如何使用this,可是一般会带来错误,退回到词法做用域去解决问题并非一个好缘由。
为了在一个函数对象引用自身,this有着显而易见的不足,你一般须要经过指向词法标识符(变量)去引用函数对象。
function foo() { foo.count = 4; // `foo` refers to itself } setTimeout( function(){ // anonymous function (no name), cannot // refer to itself }, 10 );
在第一个方法中,称为命名函数,foo是一个引用,能够被用来在函数内部引用函数自己。
可是第二个例子,这个没有名称标识的函数是经过setTimeout执行回调(因此叫匿名函数),因此,这个时候没有合适的方法经过函数名引用函数对象自身。
上边是旧的用法,如今已经弃用,一个函数中的arguments.callee引用指向当前执行的函数的函数对象。this引用是从匿名函数内部引用自身的惟一方法,不过,最好的方法是避免使用匿名函数,至少在那些须要引用自身的时候,使用命名函数或者表达式。而arguments.callee已经被弃用,不建议使用。
因此,另一个解决方法解决咱们运行的例子是咱们在每一个地方用foo这个标识符做为函数对象的引用,而不是this。
function foo(num) { console.log( "foo: " + num ); // keep track of how many times `foo` is called foo.count++; } foo.count = 0; var i; for (i=0; i<10; i++) { if (i > 5) { foo( i ); } } // foo: 6 // foo: 7 // foo: 8 // foo: 9 // how many times was `foo` called? console.log( foo.count ); // 4
然而这个方法依然侧重于this的实际理解,而且依赖于foo的词法做用域中的变量。
还有另一种方法更关注this在foo函数对象中实际指向的问题。
function foo(num) { console.log( "foo: " + num ); // keep track of how many times `foo` is called // Note: `this` IS actually `foo` now, based on // how `foo` is called (see below) this.count++; } foo.count = 0; var i; for (i=0; i<10; i++) { if (i > 5) { // using `call(..)`, we ensure the `this` // points at the function object (`foo`) itself foo.call( foo, i ); } } // foo: 6 // foo: 7 // foo: 8 // foo: 9 // how many times was `foo` called? console.log( foo.count ); // 4
避免用this,咱们拥抱这种方法,咱们将会稍微解释一下这个技术是如何更完整的工做的,因此,若是你仍然有一点困惑,别着急。
第二个常见的错误观点是认为this的意思是以某种形式指代函数做用域,这是一个让人困惑的问题,由于在必定意义上这是有道理的,可是另外一方面,这是很让人误导的。
要明确的是,在任何状况下,this并非指代函数的词法做用域,在内部,做用域是一个相似能够访问每一个标识符属性的对象,可是这个做用域对象在js代码中是不能访问的,他是引擎内部执行的一部分。
下边代码尝试去跨越代码的边界去使用this隐式的指向函数做用域。
function foo() { var a = 2; this.bar(); } function bar() { console.log( this.a ); } foo(); //undefined
这个代码块里有不止一个错误,彷佛它可能得出想要的结果,这个你看到的代码。。。(原文这里嘲讽了这块代码)。
首先,你想经过this.bar()来引用bar方法,当运行起来无疑会出事故,咱们要尽快解释为什么报错,调用bar()最天然的方式是省略this,只对标识符进行词法引用。
然而,开发者尝试使用this的目的是在foo和bar两个函数之间创造一个桥梁,是的bar能够访问到foo内部做用域中的变量,可是并无这样的桥梁,你不能用this引用去查找词法做用域下的一些东西,这是不可能的。
每当你感受你想尝试把慈父做用域的查找和this混合的时候,记得,这样的桥是不存在的。
抛开那些不正确的解释,如今咱们把注意力,转移到this机制是如何工做的。
咱们以前说过,this是运行的时候绑定而不是声明的时候绑定,它是基于函数调用状况下的上下文,this绑定和函数生命的位置没有关系,而是与函数的调用方式有关系。
当一个函数被调用,将会建立一个激活记录,也就是所谓的执行上下文。该记录包含了函数调用的位置信息(堆栈),以及函数是如何被调用的,还有参数是如何传递的等等,这个记录的其中一个属性是this引用,将会在函数执行的期间被使用。
在下一章,咱们要学习去找一个函数的调用点去肯定在函数执行的时候是如何绑定this的。
this绑定对于没有花时间去搞懂这个机制具体如何工做的js开发者来讲是一个恒定不断的困难。来自stackoverfolw的回答者的猜想,试错和盲目的复制粘贴并非利用这种机制的有效方法。
学习this,首先要去了解this不是什么,尽管任何的假设或者误解均可能致使你。。。this既不是函数自己的引用,也不是函数词法做用域的引用。
this其实是函数调用时进行的绑定,它的引用绑定彻底由函数的调用位置所决定。