这又是一个面试经典问题~/(ㄒoㄒ)/~~也是 ES5中众多坑中的一个,在 ES6 中可能会极大避免 this 产生的错误,可是为了一些老代码的维护,最好仍是了解一下 this 的指向和 call、apply、bind 三者的区别。javascript
var name = "windowsName"; function a() { var name = "Cherry"; console.log(this.name); // windowsName console.log("inner:" + this); // inner: Window } a(); console.log("outer:" + this) // outer: Window
a
的地方
a();
,前面没有调用的对象那么就是全局对象 window,这就至关因而
window.a()
;注意,这里咱们没有使用严格模式,若是使用严格模式的话,全局对象就是
undefined
,那么就会报错
Uncaught TypeError: Cannot read property 'name' of undefined
。
var name = "windowsName"; var a = { name: "Cherry", fn : function () { console.log(this.name); // Cherry } } a.fn();
在这个例子中,函数 fn 是对象 a 调用的,因此打印的值就是 a 中的 name 的值。是否是有一点清晰了呢~java
咱们作一个小小的改动:
例 3:面试
var name = "windowsName"; var a = { name: "Cherry", fn : function () { console.log(this.name); // Cherry } } window.a.fn();
这里打印 Cherry 的缘由也是由于刚刚那句话“this 永远指向最后调用它的那个对象”,最后调用它的对象仍然是对象 a。windows
咱们再来看一下这个例子:
例 4:数组
var name = "windowsName"; var a = { // name: "Cherry", fn : function () { console.log(this.name); // undefined } } window.a.fn();
undefined
呢?这是由于正如刚刚所描述的那样,调用 fn 的是 a 对象,也就是说 fn 的内部的 this 是对象 a,而对象 a 中并无对 name 进行定义,因此 log 的
this.name
的值是
undefined
。
this.name
,而是直接输出
undefined
。
var name = "windowsName"; var a = { name : null, // name: "Cherry", fn : function () { console.log(this.name); // windowsName } } var f = a.fn; f();
Cherry
,这是由于虽然将 a 对象的 fn 方法赋值给变量 f 了,可是没有调用,再接着跟我念这一句话:“
this 永远指向最后调用它的那个对象”,因为刚刚的 f 并无调用,因此
fn()
最后仍然是被 window 调用的。因此 this 指向的也就是 window。
var name = "windowsName"; function fn() { var name = 'Cherry'; innerFunction(); function innerFunction() { console.log(this.name); // windowsName } } fn()
读到如今了应该可以理解这是为何了吧(o゚▽゚)o。浏览器
改变 this 的指向我总结有如下几种方法:app
_this = this
apply
、call
、bind
例 7:函数
var name = "windowsName"; var a = { name : "Cherry", func1: function () { console.log(this.name) }, func2: function () { setTimeout( function () { this.func1() },100); } }; a.func2() // this.func1 is not a function
在不使用箭头函数的状况下,是会报错的,由于最后调用 setTimeout
的对象是 window,可是在 window 中并无 func1 函数。this
咱们在改变 this 指向这一节将把这个例子做为 demo 进行改造。es5
var name = "windowsName"; var a = { name : "Cherry", func1: function () { console.log(this.name) }, func2: function () { setTimeout( () => { this.func1() },100); } }; a.func2() // Cherry
_this = this
若是不使用 ES6,那么这种方式应该是最简单的不会出错的方式了,咱们是先将调用这个函数的对象保存在变量 _this
中,而后在函数中都使用这个 _this
,这样 _this
就不会改变了。
例 9:
var name = "windowsName"; var a = { name : "Cherry", func1: function () { console.log(this.name) }, func2: function () { var _this = this; setTimeout( function() { _this.func1() },100); } }; a.func2() // Cherry
var _this = this;
,这里的
this
是调用
func2
的对象 a,为了防止在
func2
中的 setTimeout 被 window 调用而致使的在 setTimeout 中的 this 为 window。咱们将
this(指向变量 a)
赋值给一个变量
_this
,这样,在
func2
中咱们使用
_this
就是指向对象 a 了。
使用 apply、call、bind 函数也是能够改变 this 的指向的,原理稍后再讲,咱们先来看一下是怎么实现的:
例 10:
var a = { name : "Cherry", func1: function () { console.log(this.name) }, func2: function () { setTimeout( function () { this.func1() }.apply(a),100); } }; a.func2() // Cherry
例 11:
var a = { name : "Cherry", func1: function () { console.log(this.name) }, func2: function () { setTimeout( function () { this.func1() }.call(a),100); } }; a.func2() // Cherry
例 12:
var a = { name : "Cherry", func1: function () { console.log(this.name) }, func2: function () { setTimeout( function () { this.func1() }.bind(a)(),100); } }; a.func2() // Cherry
刚刚咱们已经介绍了 apply、call、bind 都是能够改变 this 的指向的,可是这三个函数稍有不一样。
在 MDN 中定义 apply 以下;
apply() 方法调用一个函数, 其具备一个指定的this值,以及做为一个数组(或相似数组的对象)提供的参数
语法:
fun.apply(thisArg, [argsArray])
其实 apply 和 call 基本相似,他们的区别只是传入的参数不一样。
call 的语法为:
fun.call(thisArg[, arg1[, arg2[, ...]]])
因此 apply 和 call 的区别是 call 方法接受的是若干个参数列表,而 apply 接收的是一个包含多个参数的数组。
例 13:
var a ={ name : "Cherry", fn : function (a,b) { console.log( a + b) } } var b = a.fn; b.apply(a,[1,2]) // 3
例 14:
var a ={ name : "Cherry", fn : function (a,b) { console.log( a + b) } } var b = a.fn; b.call(a,1,2) // 3
咱们先来将刚刚的例子使用 bind 试一下
var a ={ name : "Cherry", fn : function (a,b) { console.log( a + b) } } var b = a.fn; b.bind(a,1,2)
咱们会发现并无输出,这是为何呢,咱们来看一下 MDN 上的文档说明:
bind()方法建立一个新的函数, 当被调用时,将其this关键字设置为提供的值,在调用新函数时,在任何提供以前提供一个给定的参数序列。
因此咱们能够看出,bind 是建立一个新的函数,咱们必需要手动去调用:
var a ={ name : "Cherry", fn : function (a,b) { console.log( a + b) } } var b = a.fn; b.bind(a,1,2)() // 3
看到留言说,不少童靴不理解为何 例 6 的 innerFunction 和 例 7 的 this 是指向 window 的,因此我就来补充一下 JS 中的函数调用。
例 6:
var name = "windowsName"; function fn() { var name = 'Cherry'; innerFunction(); function innerFunction() { console.log(this.name); // windowsName } } fn()
例 7:
var name = "windowsName"; var a = { name : "Cherry", func1: function () { console.log(this.name) }, func2: function () { setTimeout( function () { this.func1() },100); } }; a.func2() // this.func1 is not a function
函数调用的方法一共有 4 种
好比上面的 例 1:
例 1:
var name = "windowsName"; function a() { var name = "Cherry"; console.log(this.name); // windowsName console.log("inner:" + this); // inner: Window } a(); console.log("outer:" + this) // outer: Window
这样一个最简单的函数,不属于任何一个对象,就是一个函数,这样的状况在 JavaScript 的在浏览器中的非严格模式默认是属于全局对象 window 的,在严格模式,就是 undefined。
但这是一个全局的函数,很容易产生命名冲突,因此不建议这样使用。
因此说更多的状况是将函数做为对象的方法使用。好比例 2:
例 2:
var name = "windowsName"; var a = { name: "Cherry", fn : function () { console.log(this.name); // Cherry } } a.fn();
这里定义一个对象 a
,对象 a
有一个属性(name
)和一个方法(fn
)。
而后对象 a
经过 .
方法调用了其中的 fn 方法。
而后咱们一直记住的那句话“this 永远指向最后调用它的那个对象”,因此在 fn 中的 this 就是指向 a 的。
若是函数调用前使用了 new 关键字, 则是调用了构造函数。
这看起来就像建立了新的函数,但实际上 JavaScript 函数是从新建立的对象:
// 构造函数: function myFunction(arg1, arg2) { this.firstName = arg1; this.lastName = arg2; } // This creates a new object var a = new myFunction("Li","Cherry"); a.lastName; // 返回 "Cherry"
这就有要说另外一个面试经典问题:new 的过程了,(ಥ_ಥ)
这里就简单的来看一下 new 的过程吧:
伪代码表示:
var a = new myFunction("Li","Cherry"); new myFunction{ var obj = {}; obj.__proto__ = myFunction.prototype; var result = myFunction.call(obj,"Li","Cherry"); return typeof result === 'obj'? result : obj; }
因此咱们能够看到,在 new 的过程当中,咱们是使用 call 改变了 this 的指向。
在 JavaScript 中, 函数是对象。
JavaScript 函数有它的属性和方法。call() 和 apply() 是预约义的函数方法。 两个方法可用于调用函数,两个方法的第一个参数必须是对象自己
在 JavaScript 严格模式(strict mode)下, 在调用函数时第一个参数会成为 this 的值, 即便该参数不是一个对象。在 JavaScript 非严格模式(non-strict mode)下, 若是第一个参数的值是 null 或 undefined, 它将使用全局对象替代。
这个时候咱们再来看例 6:
例 6:
var name = "windowsName"; function fn() { var name = 'Cherry'; innerFunction(); function innerFunction() { console.log(this.name); // windowsName } } fn()
这里的 innerFunction() 的调用是否是属于第一种调用方式:做为一个函数调用(它就是做为一个函数调用的,没有挂载在任何对象上,因此对于没有挂载在任何对象上的函数,在非严格模式下 this 就是指向 window 的)
而后再看一下 例 7:
例 7:
var name = "windowsName"; var a = { name : "Cherry", func1: function () { console.log(this.name) }, func2: function () { setTimeout( function () { this.func1() },100 ); } }; a.func2() // this.func1 is not a function
这个简单一点的理解能够理解为“匿名函数的 this 永远指向 window”,你能够这样想,仍是那句话this 永远指向最后调用它的那个对象,那么咱们就来找最后调用匿名函数的对象,这就很尴尬了,由于匿名函数名字啊,笑哭,因此咱们是没有办法被其余对象调用匿名函数的。因此说 匿名函数的 this 永远指向 window。
若是这个时候你要问,那匿名函数都是怎么定义的,首先,咱们一般写的匿名函数都是自执行的,就是在匿名函数后面加 ()
让其自执行。其次就是虽然匿名函数不能被其余对象调用,可是能够被其余函数调用啊,好比例 7 中的 setTimeout。