与其余语言相比,js中的this有所不一样,也是比较头疼的问题。在参考了一些资料后,今天,就来深刻解析一下this指向问题,有不对的地方望你们指出。前端
对于前端开发者来讲,this是比较复杂的机制,那么为何要花大量时间来学习呢,先来看一段代码。
若是不使用this,要给identify( )和speak( )显式传入一个对象:数组
function identify(context) { return context.name.toUpperCase(); } function speak(context) { var greeting = "Hello, I'm" + identify(context); console.log(greeting); } identify(you); speak(me);
能够看到,speak( )里面直接写了identify( )的函数名,然而,随着使用模式愈来愈复杂,显式传递的上下文会让代码变得混乱,尤为体如今面向对象中。
显然,this提供了一种方式来隐式“传递”一个对象的引用,更加简洁,易于复用。浏览器
记录函数foo被调用的次数:app
function foo(num) { console.log("foo:" + num); // 记录次数 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 //foo被调用了多少次? console.log(foo.count); // 0
从前两次的console.log( )能够看出,foo确实被调用了4次,可是foo.count仍然为0,显然this指向函数自己的理解是错误的。ide
要明确的是,this在任何状况下都不指向函数的词法做用域。由于,做用域“对象”没法经过JavaScript代码访问,它存在于JavaScript引擎内部。
下面的代码试图使用this来隐式引用函数的词法做用域,没有成功:函数
function foo() { var a = 2; this.bar(); } function bar() { console.log(this.a); } foo(); // ReferenceError: a is not defined
直接报出了访问不到foo( )中的a。ReferenceError和做用域判别失败相关,而TypeError表明做用域判别成功,可是对结果的操做是非法的、不合理的。学习
排除了以上两个误解以后,来看一下this究竟是什么。
this是运行时绑定的,它和函数声明的位置没有任何关系,只取决于函数的调用方式。当一个函数被调用时,会建立一个活动记录(执行上下文),这个记录包含函数在哪里被调用,函数的调用方式、传入的参数等,this就是这个记录的一个属性,在函数执行的过程当中用到。即,this老是指向调用它所在方法的对象this
function foo() { console.log(this.a); } var a = 2; foo(); // 2
在全局中声明变量a = 2,而后在全局中直接调用foo( ),this指向了全局对象,获得a的值。
要注意的是,在严格模式(strict mood)下,若是this没有被执行环境定义,那它将绑定为undefined。code
function foo() { "use strict"; console.log(this.a); } var a = 2; foo(); // TypeError: this is undefined
在严格模式下,调用foo( )不影响this绑定。对象
function foo() { console.log(this.a); } var a = 2; (function() { "use strict"; foo(); // 2 })();
function foo() { console.log(this.a); } var obj = { a: 2, foo: foo }; var a = "global"; setTimeout(obj.foo, 100); // "global"
JavaScript中的setTimeout( )的实现和下面伪代码类似:
function setTimeout(fn, delay) { // 等待delay毫秒 fn(); // 调用函数 }
function foo() { console.log(this.a); } var obj = { a: 2 }; foo.call(obj); // 2 foo.apply(obj); // 2 foo.bind(obj); // 2
call和apply的区别在于第二个参数,call是把args所有列出来,用“,”分隔,而apply是一个类数组。call、apply是硬绑定,经过硬绑定的函数不能再修改它的this。
function foo() { console.log(this.a); } var obj = { a: 2 }; var bar = function() { foo.call(obj); } bar(); // 2 setTimeout(bar, 100); // 2 bar.call(window); // 2
函数foo( )内部手动调用了foo.call(obj),把foo的this强制绑定到了obj,因此后面即便又把bar( )绑定到了window,仍是没法改变this指向。
在传统的面向对象语言中,会使用new初始化类,然而在JavaScript中new的机制和面向对象语言彻底不一样。在js中,构造函数只是使用new操做符时被调用的函数,它们并不属于一个类,也不会实例化一个类。也就是说,js中,不存在所谓的“构造函数”,只有对函数的“构造调用”。
function foo(a) { this.a = a; } var bar = new foo(2); console.log(bar.a); // 2
使用new调用foo( ),会构造一个新对象并把它绑定到foo( )调用中的this上。
既然有那么多能够改变this的指向,那么它们的优先级是怎么样的呢,记住这句话:范围越小,优先级越高。能够按照下面的顺序来判断:
判断函数是否在new中调用过:
var bar = new foo();
判断函数是否经过call、apply、bind绑定过:
var bar = foo.call(obj);
判断函数是否在某个上下文对象中调用过:
var bar = obj.foo();
若是以上状况均不存在,那么在严格模式下,绑定到undefined,不然绑定到全局对象:
var bar = foo();