前言: 名字取得可能有点大,this 关键字是 JavaScript 中最复杂的机制之一。笔者也困扰已久,但自从阅读了《你不知道的Javascript》之后豁然开朗,整理成文。如需更更详细的解释,请阅读《你不知道的Javascript》第二部分第1章第2章。es6
最经常使用的函数调用类型:独立函数调用。能够把这条规则看做是没法应用 其余规则时的默认规则。app
function foo() { console.log( this.a ); } var a = 2; foo(); // 2
foo() 是直接使用不带任何修饰的函数引用进行调用的,所以只能使用 默认绑定,没法应用其余规则。
ps:不带任何修饰的函数的描述若是有疑惑的话请不要纠结,继续看下去就就会明白。框架
须要说明的一点是:若是使用严格模式(strict mode),那么全局对象将没法使用默认绑定,所以 this 会绑定 到 undefined:函数
function foo() { "use strict"; console.log( this.a ); } var a = 2; foo(); // TypeError: this is undefined
另外一条须要考虑的规则是调用位置是否有上下文对象,或者说是否被某个对象拥有或者包 含,不过这种说法可能会形成一些误导。oop
function foo() { console.log( this.a ); } var obj = { a: 2, foo: foo }; obj.foo(); // 2 此处foo里的this就是指的.前面的对象
ps:啰嗦两句,为何obj.foo()能够执行,这是一种属性的引用链的写法,由于obj 和foo 都挂在全局做用域上。若是还不明白再举一个例子。this
function foo() { console.log( this.a ); } var obj2 = { a: 42, foo: foo }; var obj1 = { a: 2, obj2: obj2 }; obj1.obj2.foo(); // 42
对象属性引用链中只有最顶层或者说最后一层会影响调用位置.此处也就是obj2编码
这里说一个比较容易出错的地方:es5
function foo() { console.log( this.a ); } var obj = { a: 2, foo: foo }; var a = "oops, global"; // a 是全局对象的属性 setTimeout( obj.foo, 100 ); // "oops, global
JavaScript 环境中内置的 setTimeout() 函数实现和下面的伪代码相似:prototype
function setTimeout(fn,delay) { // 等待 delay 毫秒code
fn(); // <-- 调用位置! }
setTimeout的参数fn能够看作调用时候传入的函数复制给fn,这个是挂在全局做用域上的(此处的说法不严谨,不少时候框架的不一样此处的this被绑定到哪儿很不肯定).因此,回调函数丢失 this 绑定是很是常见的。es6 的=>很好的解决了这个问题。
JavaScript 提供的绝大多数函数以及你自 己建立的全部函数均可以使用 call(..) 和 apply(..) 方法。它们会把这个对象绑定到 this,接着在调用函数时指定这个 this。由于你能够直接指定 this 的绑定对象,所以我 们称之为显式绑定。
function foo() { console.log( this.a ); } var obj = { a:2 }; foo.call( obj ); // 2
经过 foo.call(..),咱们能够在调用 foo 时强制把它的 this 绑定到 obj 上。
若是你传入了一个原始值(字符串类型、布尔类型或者数字类型)来看成 this 的绑定对 象,这个原始值会被转换成它的对象形式(也就是 new String(..)、new Boolean(..) 或者 new Number(..))。这一般被称为“装箱”。
ps:硬绑定只能执行一次,以后再绑别的都是绑不上去的。
因为硬绑定是一种很是经常使用的模式,因此在 ES5 中提供了内置的方法 Function.prototype. bind,它的用法以下:
function foo(something) { console.log( this.a, something ); return this.a + something; } var obj = { a:2 }; var bar = foo.bind( obj ); var b = bar( 3 ); // 2 3 console.log( b ); // 5
bind(..) 会返回一个硬编码的新函数,它会把参数设置为 this 的上下文并调用原始函数。
对于js的new 初学者可能会把它和JAVA类的语言混淆
首先咱们从新定义一下 JavaScript 中的“构造函数” 。在 JavaScript 中,构造函数只是一些 使用 new 操做符时被调用的函数。它们并不会属于某个类,也不会实例化一个类。实际上, 它们甚至都不能说是一种特殊的函数类型,它们只是被 new 操做符调用的普通函数而已。
2涉及到原型链的知识,本篇可忽视。
function foo(a) { this.a = a; } var bar = new foo(2); console.log( bar.a ); // 2
使用 new 来调用 foo(..) 时,咱们会构造一个新对象并把它绑定到 foo(..) 调用中的 this 上。new 是最后一种能够影响函数调用时 this 绑定行为的方法,咱们称之为 new 绑定。
ps:此处可能看得比较费解,请阅读《你不知道的js》第二部的关于类与原型链的章节,仍是比较复杂的,在此就不展开了。此处你能够理解为用函数new 了一个新对象,而后绑定到 函数的this上使用。
主要目的是预先设置函数的一些参数,这样在使用 new 进行初始化时就能够只传入其他的参数。bind(..) 的功能之一就是能够把除了第一个 参数(第一个参数用于绑定 this)以外的其余参数都传给下层的函数(这种技术称为“部 分应用”,是“柯里化”的一种)。举例来讲:
function foo(p1,p2) { this.val = p1 + p2; } // 之因此使用 null 是由于在本例中咱们并不关心硬绑定的 this 是什么 // 反正使用 new 时 this 会被修改 var bar = foo.bind( null, "p1" ); var baz = new bar( "p2" ); baz.val; // p1p2
咱们能够根据优先级来判断函数在某个调用位置应用的是哪条规则。能够按照下面的 顺序来进行判断:
(数值越小优先级越高)
1 函数是否在 new 中调用(new 绑定)?若是是的话 this 绑定的是新建立的对象。
var bar = new foo()
2 函数是否经过 call、apply(显式绑定)或者硬绑定调用?若是是的话,this 绑定的是 指定的对象。
var bar = foo.call(obj2)
3 函数是否在某个上下文对象中调用(隐式绑定)?若是是的话,this 绑定的是那个上 下文对象。
var bar = obj1.foo()
4 若是都不是的话,使用默认绑定。若是在严格模式下,就绑定到 undefined,不然绑定到 全局对象。
var bar = foo()
在某些场景下 this 的绑定行为会出乎意料,你认为应当应用其余绑定规则时,实际上应用 的多是默认绑定规则。
若是你把 null 或者 undefined 做为 this 的绑定对象传入 call、apply 或者 bind,这些值 在调用时会被忽略,实际应用的是默认绑定规则.
另外一个须要注意的是,你有可能(有意或者无心地)建立一个函数的“间接引用”,在这 种状况下,调用这个函数会应用默认绑定规则。
function foo() { console.log( this.a ); } var a = 2; var o = { a: 3, foo: foo }; var p = { a: 4 }; o.foo(); // 3 (p.foo = o.foo)(); // 2
赋值表达式 p.foo = o.foo 的返回值是目标函数的引用,所以调用位置是 foo() 而不是 p.foo() 或者 o.foo()。
1
用硬绑定以后就没法使用隐式绑定或者显式绑定来修改 this。
若是能够给默认绑定指定一个全局对象和 undefined 之外的值,那就能够实现和硬绑定相 同的效果,同时保留隐式绑定或者显式绑定修改 this 的能力。
if (!Function.prototype.softBind) {
Function.prototype.softBind = function(obj) { var fn = this; // 捕获全部 curried 参数 var curried = [].slice.call( arguments, 1 ); var bound = function() { return fn.apply( (!this || this === (window || global)) ?obj : this curried.concat.apply( curried, arguments ) ); }; bound.prototype = Object.create( fn.prototype ); return bound; }; }
箭头函数并非使用 function 关键字定义的,而是使用被称为“胖箭头”的操做符 => 定 义的。箭头函数不使用 this 的四种标准规则,而是根据外层(函数或者全局)做用域来决 定 this。
以前咱们有说过setTimeout的this绑定丢失的问题es6和es6之前的解决办法以下,能够比较着看
es6:
function foo() { setTimeout(() => { // 这里的 this 在此法上继承自 foo() console.log( this.a ); },100); } var obj = { a:2 }; foo.call( obj ); // 2
es5
function foo() { var self = this; // lexical capture of this setTimeout( function(){ console.log( self.a ); }, 100 ); } var obj = { a: 2 }; foo.call( obj ); // 2
ES6 中的箭头函数并不会使用四条标准的绑定规则,而是根据当前的词法做用域来决定 this,具体来讲,箭头函数会继承外层函数调用的 this 绑定(不管 this 绑定到什么)。这 其实和 ES6 以前代码中的 self = this 机制同样。