this的指向是JavaScript中一个经久不衰的热门问题,在不一样的场景下指向规则也不一样,在此本文总结了this在不一样场景下的指向规则以及ES6中新增的箭头函数中this的指向问题。数组
this提供了一种更优雅的方式来隐式"传递"一个对象引用,所以能够将API设计得更加简洁而且易于复用.浏览器
指向函数的做用域安全
this的指向:函数在代码中被调用的位置(而不是声明位置)bash
调用栈:为了到达当前执行位置所调用的全部函数.
function baz() { // 当前调用栈是:baz // 所以,当前调用位置是全局做用域 console.log( "baz" ); bar(); // <-- bar的调用位置 } function bar() { // 当前调用栈是baz -> bar // 所以,当前调用位置在baz中 console.log( "bar" ); foo(); // <-- foo的调用位置 } function foo() {debugger; // 当前调用栈是baz -> bar -> foo // 所以当前调用位置在bar中 console.log( "foo" ); } baz(); // <-- baz的调用位置
经过浏览器的调试工具能够查看调用栈,能够查看当前函数体内this的指向:app
直接使用不带任何修饰的函数引用进行调用的,会进行默认绑定。函数
function foo() { console.log(this.a); } var a = 2; foo(); // 2
调用位置是否有上下文对象,或者说是否被某个对象拥有或包含。工具
function foo() { console.log( this.a ); } var obj = { a: 2, foo: foo }; obj.foo(); // 2
须要注意的是不管是直接在obj中定义仍是先定义再添加为引用属性,这个函数严格来讲都不属于obj对象。 然而,调用位置会使用obj上下文来引用函数,所以你能够说函数被调用时obj对象“拥有”或者“包含”它。
当foo()被调用时,它的落脚点确实指向obj对象。当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。由于调用foo()时this被绑定到obj,所以this.a和obj.a是同样的。oop
function foo() { console.log( this.a ); } var obj = { a: 2, foo: foo }; var bar = obj.foo; // 函数别名! var a = "oops, global"; // a是全局对象的属性 bar(); // "oops, global"
虽然bar是obj.foo的引用,但实际上其引用的是foo自己,所以此时的bar是不带修饰的函数调用,所以应用默认绑定。
实际状况里比较常见的是回调函数的this丢失问题:this
function foo() { console.log( this.a ); } var obj = { a: 2, foo: foo }; var a = "oops, global"; // a是全局对象的属性 setTimeout( obj.foo, 100 ); // "oops, global"
回调函数失去this值绑定也是同样的缘由,func(fn)中fn实际上是赋值给了函数的参数
在上面这段代码里obj.foo在传入setTimeout时实际上是作了一个隐式的赋值操做,所以,在setTimeout函数体内调用时,其实调用的参数直接引用了function foo(){},致使this绑定到全局对象上。编码
像隐式绑定中的例子里,若是不想将函数包含在对象体内的属性上,那么能够利用显式绑定方法(call、apply),它们会把这个对象绑定到this,接着在调用函数时指定这个this。
function foo() { console.log( this.a ); } var obj = { a:2 }; foo.call( obj ); // 2
利用显式绑定的一个变种方法能够解决隐式丢失的问题,即建立一个包裹函数,传入全部的参数并返回接收到的全部值:
function foo(something) { console.log( this.a, something ); return this.a + something; } var obj = { a:2 }; var bar = function() { return foo.apply( obj, arguments ); }; var b = bar( 3 ); // 2 3 console.log( b ); // 5
经过建立函数bar(),并在它的内部手动调用foo.call(obj),从而强制把foo的this绑定到了obj。不管以后如何调用函数bar,它总会手动在obj上调用foo。这种绑定是一种显式的强制绑定。
在ES5中也提供了内置的这种绑定方法:Function.prototype.bind,bind(..)会返回一个硬编码的新函数,它会把参数设置为this的上下文并调用原始函数。
bind()与call()和apply()这两种方法不一样之处在于其处理后函数不会执行,而是返回了一个改变了上下文的函数副本,然后二者则会直接执行函数。
与其余语言中不一样,在JavaScript中,构造函数只是一些使用new操做符时被调用的函数。它们并不会属于某个类,也不会实例化一个类。实际上,它们甚至都不能说是一种特殊的函数类型,它们只是被new操做符调用的普通函数而已。
包括内置对象函数在内的全部函数均可以用new来调用,这种函数调用被称为构造函数调用。这里有一个重要可是很是细微的区别:实际上并不存在所谓的“构造函数”,只有对于函数的“构造调用”。
new操做符进行构造函数调用时的操做流程:
function foo(a) { this.a = a; } var bar = new foo(2); console.log( bar.a ); // 2
使用new来调用foo(..)时,咱们会构造一个新对象并把它绑定到foo(..)调用中的this上.
箭头函数是ES6新增的一种函数表达式,不是使用function关键字来定义,也不适用上述四条断定规则,而是依据外层做用域来决定this。
this指向的固定化,并非由于箭头函数内部有绑定this的机制,实际缘由是箭头函数根本没有本身的this,致使内部的this就是外层代码块的this。正是由于它没有this,因此也就不能用做构造函数。
箭头函数实质上就是保存了其外层做用域的this指向,从而在函数体内进行调用。ES6在Babel转换后的ES5代码以下:
// ES6 function foo() { setTimeout(() => { console.log('id:', this.id); }, 100); } // ES5 function foo() { var _this = this; setTimeout(function () { console.log('id:', _this.id); }, 100); }
这种方法主要是用apply展开数组并传入函数.可是若是这个函数确实使用了this,那么this会绑定到全局对象,形成恶劣后果。
function foo(a,b) { console.log( "a:" + a + ", b:" + b ); } // 咱们的DMZ空对象 var ø = Object.create( null ); // 把数组展开成参数 foo.apply( ø, [2, 3] ); // a:2, b:3 // 使用bind(..)进行柯里化 var bar = foo.bind( ø, 2 ); bar( 3 ); // a:2, b:3
参考:《你不知道的JavaScript》等