MDN
JavaScript thisJavaScript 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 函数调用