MDN
JavaScript this
JavaScript Function.prototype.call()
JavaScript Function.prototype.apply()
JavaScript Function.prototype.bind()
this
的概念理解
this
的指向
在绝大多数情况下,函数的调用方式决定了
this
的值。this
不能在执行期间被赋值,并且在每次函数被调用时this的值也可能会不同。ES5引入了bind方法来设置函数的this值,而不用考虑函数如何被调用的,ES2015 引入了支持this词法解析的箭头函数(它在闭合的执行环境内设置this的值)。(来自MDN)
全局环境(Global context)
无论是否在严格模式下,在全局执行环境中(在任何函数体外部)
this
都指向全局对象 (来自MDN)
1 | conosole.log(this === window); // true |
函数(运行内)环境(Function context)
在函数内部,
this
的值取决于函数被调用的方式。(来自MDN)
简单调用
非严格模式下:
因为下面的代码不在严格模式下,且 this
的值不是由该调用设置的,所以 this
的值默认指向全局对象(window
)。
1 | // non-strict mode |
严格模式下:this
将保持他进入执行环境时的值,所以下面的this
将会默认为undefined
。
1 | // strict mode |
所以,可以得出结论在严格模式下,如果 this
没有被执行环境(execution context)定义,那它将保持为 undefined
。
首先,来看下面一个简单的例子:
例 1:
1 | var name = 'windowsName'; |
为什么这里console.log
是 windowsName?
因为“this
永远指向最后调用它的那个对象”,调用foo
的地方foo()
,前面没有调用的对象那么就是指向全局对象 Object window
,相当于window.foo()
这里没有使用严格模式,如果使用严格模式的情况下,全局对象就是undefined
,那么就会报错Uncaught TypeError: Cannot read property 'name' of undefined
请看下面的例子:
例 2:
1 | // Use Strict Mode |
再看下面的例子:
例 3:
1 | var name = 'windowsName'; |
在这个例子中,函数 fn
是对象 bar
调用的,所以打印的值就是 bar
中的 name
的值。
基于上面的例子,再做个改动:
例 4:
1 | var name = 'windowsName'; |
这里console.log
为Br3ad
,最后调用它的对象是bar
,还是因为“this
永远指向最后调用它的那个对象”
再来看下面这个例子:
例 5:
1 | var name = 'windowName'; |
为什么console.log
会打印undefined
呢?
因为,如刚刚所描述的那样,调用fn
的是bar
这个对象,也就是说fn
内部的this
是对象bar
,而对象bar
中并没有对name
字段进行定义,所以console.log
的this.name
的值为undefined
。
这个例子还是印证了刚才的结论:this 永远指向最后调用它的那个对象,因为最后调用fn
的对象是bar
,所以就算bar
中没有name
这个属性,也不会继续向上一个对象寻找this.name
,而是直接输出undefined
。
再来看一个复杂点的例子:
例 6:
1 | var name = 'windowsName'; |
为什么这里console.log
打印出的不是Br3ad
?因为虽然bar
对象的fn
方法赋值给了变量fun
了,但是没有调用,回到之前我们的结论:“this 永远指向最后调用它的那个对象”,由于刚刚的fun
并没有调用,所以fn()
最后仍然是被window
调用的。所以这里this
指向的也就是window
。
以上的例子,不难发现this
的指向并不是在创建的时候就可以确定的,在 es5
中,永远是:this
永远指向最后调用它的那个对象
再来看一个例子:
例 7:
1 | var name = 'windowName'; |
怎么改变 this
的指向?
改变this的指向主要有以下几种方法:
- 使用
ES6
的箭头函数(Arrow function expressions)
- 在函数内部使用
_this = this
- 使用
apply()
、call()
、bind()
new
实例化一个对象
请看下面的例子:
例子8:
1 | var name = 'windowsName'; |
在不使用箭头函数的情况下,是会报错的,因为最后调用setTimeout
的对象是window
,但是在window
中并没有func1
函数。
在改变 this
指向这一节将把这个例子作为 Demo
进行改造
那么,箭头函数是如何实现的?
箭头函数(Arrow function expressions)
箭头函数的 this
始终指向函数定义时的 this
,而非执行时。记住:“箭头函数没有单独的this
绑定,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数包含,则 this
绑定的是最近一层非箭头函数的 this
,否则,this
为 undefined
(箭头函数会从自己的作用域链的上一层继承this
)”。
请看下面的例子:
例9:
1 | var name = 'windowsName'; |
在函数内部使用_that = this
如果不使用ES6,那么这种方式应该是最简单的不会出错的方式,先将调用这个函数的对象保存在变量_this
中,然后在函数中都是用这个_that
,这样_that
就不会改变了。
请看下面的例子:
例10:
1 | var name = 'windowsName'; |
这个例子中,在func2
中,首先设置var _that = this;
,这里的this
是调用func2的对象bar
,为了防止在func2
中的setTimeout
被window
调用而导致的在setTimeout
中的this
为window
。将this(指向变量bar)
赋值给一个变量_that
,这样,在func2
中使用_that
就是指向对象bar
了。
使用 apply()
、call()
、bind()
使用apply()
、call()
、bind()
函数也是可以改变this
的指向,先来看一下是怎么实现的:
使用apply()
来看看MDN对apply()
的用法定义:
apply()
方法调用一个具有给定this
值的函数,以及作为一个数组(或类似数组对象)提供的参数
语法
1 | function.apply(thisArg, [argsArray]) |
请看下面的例子:
例11:
1 | // function.apply() |
使用call()
来看看MDN对call()
的用法定义:
call()
方法使用一个指定的this
值和单独给出的一个或多个参数来调用一个函数。
语法
1 | function.call(thisArg, arg1, arg2, arg3, ar4, ...); |
请看下面的例子:
例子12:
1 | // function.call() |
使用bind()
来看看MDN对bind()
的用法定义:
bind()
方法创建一个新的函数,在bind()
被调用时,这个新函数的this
被指定为bind()
的第一个参数,而其余参数将作为新函数的参数,供调用时使用
语法
1 | function.bind(thisArg[, arg1[, arg2[, ...]]]) |
请看下面的例子:
例子13
1 | // function.bind() |
JavaScript 中 call()
、apply()
、bind()
的区别?
现在,我们都知道使用call()
、apply()
、bind()
函数都可以改变JavaScript中this
的指向,但是这三个函数稍有不同。
在MDN
中定义apply()
如下:
apply()
方法调用一个具有给定this
值的函数,以及作为一个数组(或类似数组对象)提供的参数
语法
1 | function.apply(thisArg, [argsArray]) |
参数:
thisArg
(必须) 在func
函数运行时使用的this
值。请注意,this
可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为null
或undefined
时会自动替换为指向全局对象,原始值会被包装。
argsArray
(可选) 一个数组或者类数组对象,其中的数组元素将作为单独的参数传给func
函数。如果该参数的值为null
或undefined
,则表示不需要传入任何参数。从ECMAScript 5
开始可以使用类数组对象。
apply()
和 call()
的区别
apply()
和 call()
基本类似,他们的区别只是传入的参数不同
call()的语法为:
1 | function.call(thisArg, arg1, arg2, ...) |
apply()
和call()
的区别就是:
call()
方法接受的是若干个参数列表,而apply()
接收的是一个包含多个参数的数组。
请看下面的例子:
例子14:
1 | // function.apply() |
例子15:
1 | // function.call() |
bind()
和apply()
、call()
的区别
现在,将刚刚的例子使用bind()
试一下
1 | // function.bind()() |
会发现并没有输出,这是为什么呢,我们来看一下 MDN
上的文档说明:
bind()
方法创建一个新的函数,在bind()
被调用时,这个新函数的this
被指定为bind()
的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
所以我们可以看出,bind()
是创建一个新的函数,我们必须要手动去调用:
1 | // function |
JavaScript 中的函数调用方式
例7:
1 | var name = 'windowsName'; |
例8:
1 | var name = 'windowsName'; |
函数调用的方法一共有 4 种
作为函数调用
比如上面的例子1:
例1:
1 | // non-strict mode |
这是一个简单的函数,在浏览器运行环境中的非严格模式(non-strict mode)默认是属于全局对象 window
的,在严格模式(strict mode),this指向的就是 undefined
。这是一个全局的函数,很容易产生命名冲突,不建议这样使用。
1 | // strict mode |
函数作为方法调用
将函数作为对象的方法使用。比如:
例2:
1 | var name = 'windowName'; |
这里定义一个对象foo
,对象foo
有一个属性(name
)和一个方法(fn
)。
然后,对象foo
通过.
方法调用了其中的fn
方法
还记得那句话“this永远指向最后调用它的那个对象”,所以在fn
中的this
就是指向 foo
的
作为构造函数调用函数
构造函数:关键字new建一个对象并调用一个函数(这个函数称作构造函数 Constructor)初始化新对象的属性
如果函数调用前使用了new运算符,则是调用了构造函数
这看起来就像创建了新的函数,但实际上JavaScript函数是重新创建的对象
1 | // 构造函数 |
new 的过程
1 | var foo = new myFunction('Li', 'Cherry'); |
1、创建一个空对象obj;
2、将新创建的空对象的隐式原型指向其构造函数的显示原型
3、使用call
改变this
的指向
4、如果无返回值或者返回一个非对象值,则将 obj 返回作为新对象;
如果返回值是一个新对象的话那么直接直接返回该对象。
可以看到,在new的过程中,使用call
改变了this的指向
通过它们的call()
和apply()
方法间接调用
JavaScript 中,函数是对象
JavaScript 函数有它的属性和方法。call()
和apply()
是预定义的函数方法。两个方法可用于调用函数,两个方法的第一个参数必须是对象本身
JavaScript 严格模式(strict mode)下,在调用函数时第一个参数会成为this
的值,即使该参数不是一个对象
JavaScript 非严格模式(non-strict mode)下,如果第一个参数的值是null
或undefined
,它将使用全局对象替代。
再来看例子6:
1 | var name = 'windowsName'; |
这里的innerFunction()
的调用属于第一种调用方式:作为一个函数调用(作为一个函数调用、没有挂载在任何对象上,所以对于没有挂载在任何对象的函数,在非严格模式(non-strict mode)下就是指向window的)
然后再看一下例7:
例7:
1 | var name = 'windowsName'; |
得出结论,可以简单理解为:匿名函数的this
永远指向window
在这之前,我们得出结论:this
永远指向最后调用它的那个对象,那么去找最后调用匿名函数的对象,
但是因为匿名函数没有名字,所以没有办法被其他对象调用匿名函数的。所以:匿名函数的 this
永远指向 window
那么问题来了,匿名函数是如何被定义的?匿名函数是自执行的,就是在匿名函数后面加()
让其自执行。其次就是虽然匿名函数不能被其他对象调用,但是可以被其他函数调用,比如例7中的setTimeout
严格模式
在严格版中的默认的
this
不再是window
,而是undefined。
几条判断this
指向的方法:
1、查看函数在哪被调用
2、点左侧有没有对象?如果有,它就是 “this
” 的引用。如果没有,继续往下。
3、该函数是不是用 “call
”、“apply
” 或者 “bind
” 调用的?如果是,它会显式地指明 “this
” 的引用。如果没有,继续往下。
4、该函数是不是用 “new
” 调用的?如果是,“this
” 指向的就是 JavaScript
解释器新创建的对象。如果没有,继续往下。
5、是否在“严格模式”下?如果是,“this
” 就是 undefined
,如果不是
6、JavaScript,“this
” 会指向 “window
” 对象
7、匿名函数的执行环境this
具有全局性,其this
对象通常指向window
(听过call()
或apply()
改变函数执行环境的情况下,this
就会指向其他对象)
参考链接
稀土掘金-this、apply、call、bind
阮一峰-JavaScript 的 this 原理
阮一峰-Javascript 的 this 用法
javascript this指向
How to use the apply(), call(), and bind() methods in JavaScript
Understanding This, Bind, Call, and Apply in JavaScript
Function.prototype.apply()
Function.prototype.call()
Function.prototype.bind()
理解 JavaScript 中的 this、call、apply 和 bind
Understanding the “this” keyword, call, apply, and bind in JavaScript
JavaScript 之 this 指南
javascript 基础之 call, apply, bind
JavaScript中的call、apply、bind深入理解
彻底弄清 this call apply bind 以及原生实现
如何在 JavaScript 中使用 apply(),call(),bind()
JavaScript 函数调用