上个月写过一篇V8是如何运行JavaScript(let a = 1)代码的?,写完之后我就发现,我对平常使用的工具V8引擎,偏底层的知识了解的竟然是如此甚少。同时我真正从事前端的时间还算是比较短的,那么基础也算是非常的薄弱。结合以上,我打算有时间就去从底层的角度去学习了解,便于在使用过程中的理解和解决遇到的问题,理解JavaScript的本质,能够更好的学习JavaScript。如果你跟我有同样的困惑,那我们可以结伴同行,共同学习。
本系列我会从我的视角不断的去总结:
1、前言
通过本文可以学习到以下三点:
- 了解严格模式和非严格模式的区别
- 了解this在不同场景下的区别
- 了解如何改变this的指向问题
2、普通函数
<script> console.log(this, '全局') function foo() { console.log(this, '函数') } foo() </script>
执行发现两个this都指向了全局的window
.也就是全局执行上下文
中的this和函数执行上下文
中的this都指向了全局window
.所以默认的情况下,在全局中调用一个函数,函数中执行上下文中的this都是指向window对象的。
而我们平常工作中的项目,你可以去试试通常都是严格模式
下的。
在ECMAscript 5的时候,JavaScript引擎添加了另外一种运行模式:严格模式
,这种情况下代码会在更严格的条件下运行。而我们上面的代码其实就是运行在非严格模式
下,我只是在一个html文件中添加如上代码,没有其他任何的设置配置。而我们平常工作中的项目,你可以去试试通常都是严格模式
下的。
我们来看上面的代码如何在严格模式
下运行。
<script> "use strict" console.log(this, '全局') function foo() { console.log(this, '函数') } foo() </script>
可以发现,函数中的this打印,变成了undefined。在全局调用一个函数执行,其函数执行上下文中的this值是undefined。
总结:全局上下文中的this,无论在严格模式
还是非严格模式
下,它的指向都是全局变量window。 普通函数中的this,在严格模式
下的指向是undefined,在非严格模式
下的指向为全局变量的window。
3、var和let声明的变量
<script> console.log(this, '全局') var name1 = 'name1' let name2 = 'name2' console.log(this.name1, '全局this.name1') console.log(this.name2, '全局this.name2'); function foo() { console.log(this, '函数') console.log(this.name1, '函数this.name1') console.log(this.name2, '函数this.name2'); } foo() </script>
通过运行可以发现通过let
声明的变量,无论是在全局执行上下文
,还是函数执行上下文
中打印的都是undefined。
总结:通过执行上下文的角度,我们去查看过var和let声明变量的区别,有兴趣的可以去查看一下( www.baidu.com )。var声明的变量被存放到执行上下文的变量环境,let声明的变量都被存放到执行上下的词法环境中,但是var声明的变量同时被存放到全局变量window下,而全局执行上下文中的this指向是window。而let不在其中,所以无论是全局执行上下文还是函数执行上下,this中是访问不到let声明的变量的。
4、对象中的函数
<script> 'use strict'; let bar = { name: 'aehyok', fun: function () { console.log(this.name,'bar对象') } } bar.fun() const bb = bar.fun bb() </script>
通过截图执行情况可以发现,在严格模式下,直接调用bar.fun()
打印出来的this指向的是bar对象。如果将bar.fun
赋值给一个变量,这个变量的引用其实存储的就是bar对象中的fun函数
,这样相当于变成了一个普通的函数,在严格模式下打印出来就为undefined。
总结:使用对象来调用其内部的一个函数,该函数的this指向为对象本身。在全局上下文中调用一个函数,函数内部的this指向为(严格模式下指向undefined,非严格模式下指向为window)
那调用bb()的时候有没有办法将this指向原来的bar对象呢?
5、call、apply、bind
<script> 'use strict'; let bar = { name: 'aehyok', fun: function () { console.log(this,'bar对象') } } bar.fun() const bb = bar.fun bb() bar.fun.call(bar) bar.fun.apply(bar) bar.fun.bind(bar)() </script>
通过截图的执行结果可以发现,最后三个的打印this指向全部是bar对象本身。再看最后三行代码,call、apply、bind,都可以改变函数的this指向。
6、箭头函数
<script> 'use strict'; let bar = { name: 'aehyok', fun:function() { console.log(this,'bar函数') function inner () { console.log(this, 'inner函数') } inner() } } bar.fun() </script>
通过之前的解析我们可以非常的清楚,如截图所示的执行结果。在fun函数中的this指向为bar对象,而在inner函数中调用的this指向为undefined。这里也就说明了嵌套函数里的this并不会传递,并不会将我们能否将this指向改为bar对象呢?当然上一小节,我们已经可以通过call、apply、bind改变this的指向。
第一种方式:通过定义变量进行传递this
<script> 'use strict'; let bar = { name: 'aehyok', fun:function() { console.log(this,'bar函数') let self = this function inner () { console.log(self, 'inner函数') } inner() } } bar.fun() </script>
定义一个self
变量,如果嵌套函数inner中需要,在inner函数中直接使用self,而不是this了。
这里我们来看一下另外一种方式:箭头函数的妙用
<script> 'use strict'; let bar = { name: 'aehyok', fun:function() { console.log(this,'bar函数') let inner = () => { console.log(this, 'inner函数') } inner() } } bar.fun() </script>
通过运行的截图可以发现,inner箭头函数中this的指向是bar对象。这是因为ES6中的箭头函数并不会创建自身的执行上下文,所以箭头函数中的this取决于它的外部函数。那么很自然的就指向了bar对象了。
7、构造函数
还是通过vue创建实例来看结果
<script> // "use strict" function Vue (name) { let vm = this vm.name = name console.log(vm) } const _vue = Vue('普通函数') const vue = new Vue('构造函数') </script>
执行结果如下
总结:
_vue
通过普通函数赋值,其中的this
指向指向了window
(这里是非严格模式)。
如果是严格模式this指向会是undefined。
而通过构造函数
new
出来,其中的this
指向了构造函数创建出来的实例对象。这也是构造函数比较特殊的地方。
8、总结
- 1、全局执行上下文中的this
- 在严格模式下和非严格模式下,this的指向都为全局变量window。
- 2、函数执行上下文中的this(在全局执行上下文中调用函数)
- 在非严格模式下,this的指向为全局变量window。
- 在严格模式下,this的指向为undefined。
- 3、通常我们在项目中都是在严格模式下调用的,当然也有配置可配。(你可以在公司的vue项目中进行测试)
- 4、对象中的函数
- 通过对象.函数的方式,this的指向便是对象本身
- 但是要注意不能将对象.函数赋值给别的变量,然后再调用额
- 5、构造函数
- 通过构造函数 new 出来的,其中的this指向了new出来的实例对象
- 6、改变 this 指向的方式
- 通过箭头函数
- 通过声明 let self = this,然后在需要的嵌套函数中使用self 即可
- 就是使用 call apply bind
- fun.apply(thisArgs, [arg1, arg2]) 参数通过数组的方式传递
- fun.call(thisArgs, arg1, arg2) 参数通过多个参数传递
- fun.bind(thisArgs, arg1, arg2)() bind 相当于创建一个新的函数,我们还需要手动调用