普通函数的 this 指向
简单说说
首先, 按照惯例, 我们先举个栗子:
- var bar = 2;
- function foo() {
- this.bar = 1;
- this.getBar = function() { console.log(this.bar);
- }
- }
- var test = new foo();
- var getBar = test.getBar;
- test.getBar(); //1
- getBar(); //2
通过这个例子我们就能看到, 虽然是同一个函数, 但是实际上得到的结果却不一样. 这个原因相信大家都能知道. 不知道的也告诉你: this 其实是指向调用该函数的那个对象. 那么当我们在全局环境中调用的时候, this 自然就指向了全局环境.
那么到是有个问题: this 为什么会随调用者变化而变化?
这可能需要你继续往下看看
深入说说
那么如果说深层次的理解 this 的指向, 我觉得大概可以从数据类型讲起
我们都知道, 栈中存放的是基本数据类型, 也就是 String,Number,Boolean,Symbol,Null,Undefined 这七种数据类型, 当然 Symbol 是 ES6 新增的一个数据类型. 那么堆中存放的就是一些引用类型了, 如 Obejct,Function. 实际上当我们定义一个引用类型的时候, js 会同时定义一个地址指针指向内存中的对象.
例如: 当我们声明一个字面量对象时候 let a = {num:1}; 实际上 a 中存放的是指向 {num:1} 的地址
定义引用类型时
现在我们解析一下上面那段代码是如何执行的
- // 在全局环境下定义一个变量 bar
- var bar = 2;
- function foo() {
- // 在 foo 中也声明了一个 bar
- this.bar = 1;
- // 在 foo 中声明一个 getBar 函数
- this.getBar = function() {
- console.log(this.bar);
- }
- }
- // 构造函数模式自定义对象, 将 foo 的 this 赋予 test
- var test = new foo();
- // 将 test 中的 getBar 方法赋予 getBar
- var getBar = test.getBar;
- // 调用 test 中的 getBar
- test.getBar(); //1
- // 调用 getBar
- getBar(); //2
现在列出来一看, 放佛恍然大悟, 终于知道为啥输出的是不同的结果了. 那么我这里倒是有几个问题
为什么调用同一个函数却有不同的结果?
foo 中的 this 是指向 foo 的, 为什么 foo 中的函数可以取得外部的 this?
为什么 this 会随调用它的对象变化而变化?
ok, 其实要弄清楚上述问题, 我们需要明白一点, 函数也是个引用类型. 那么我们上面讲过, 创建引用类型的时候会同时创建一个地址指针. 那么我们就可以这样理解上面的 foo 对象
this.png
实际上 foo 中的 getBar 只是存放了一个函数的地址而已 *. 那么这个函数并不是 foo 所私有. 什么东西是 foo 的呢? 一个值为 1 的 bar 和一个指向
function() {console.log(this.bar);}
函数的 getBar 而已.
这样我们就不难理解, 为什么调用同一个函数会有不一样的结果了, 因为这个函数并不是 foo 所私有. 好比内存就是深圳, 函数就只是深圳的一套房. getBar 就是这套房的钥匙. 那么一开始 foo 这个人建好了这房子, 就他有这房子的钥匙, 那么当然只有他能进出该房子, 后来有一天他把钥匙多配了一把给了 window 这好朋友. 于是乎 window 也能进这套房了. 给 window 配钥匙的过程:
var getBar = test.getBar;
这里只是将该函数的地址赋给全局下的 getBar 而已, 房子也只是一套房子, 函数还是一个函数.
由于函数可以在不同的运行环境执行, 所以需要有一种机制, 能够在函数体内部获得当前的运行环境(context). 所以, this 就出现了, 它的设计目的就是在函数体内部, 指代函数当前的运行环境.
所以当 window 调用这个函数的时候, this 就不是指向 foo 了. 而是指向 window.this 是指向他们自己. window 的衣服不会在进了 foo 的房子以后就变成 foo 的衣服.
ok, 我们现在再把刚刚的代码重新注释一下
- // 在全局环境下定义一个变量 bar
- var bar = 2;
- function foo() {
- // 在 foo 中也声明了一个 bar
- this.bar = 1;
- // 在 foo 中声明一个 getBar 函数, getBar 存放该函数的地址
- this.getBar = function() {
- console.log(this.bar);
- }
- }
- // 构造函数模式自定义对象, 将 foo 的 this 赋予 test
- var test = new foo();
- // 将 test 中的 getBar 方法的地址赋予全局的 getBar
- var getBar = test.getBar;
- // 调用 test 中的 getBar 函数
- test.getBar(); //1
- // 调用 getBar 函数
- getBar(); //2
于是乎我们就把普通的 this 指向弄明白了. 顺便还明白了堆栈的区别. 接下来看看不普通的函数 this 指向是如何的
箭头函数 this 指向
箭头函数内没有 this, 箭头函数的 this 是父级函数的 this
- // 在全局环境下定义一个变量 bar
- var bar = 2;
- function foo() {
- // 在 foo 中也声明了一个 bar
- this.bar = 1;
- // 在 foo 中定义一个箭头函数, getBar 存放该函数的地址
- this.getBar = () => {
- console.log(this.bar);
- }
- }
- // 构造函数模式自定义对象, 将 foo 的 this 赋予 test
- var test = new foo();
- // 将 test 中的 getBar 方法的地址赋予全局的 getBar
- var getBar = test.getBar;
- // 调用 test 中的 getBar 函数
- test.getBar(); //1
- // 调用 getBar 函数
- getBar(); //1
如果定义了箭头函数的情况下, this 执行就不会随意的改变了. 普通函数的 this 是会跟随调用者变化, 但是箭头函数就很特别, 他只会继承父级的 this, 而且一旦建立就不会改变了. 所以在这里我们就可以看见, 尽管全局下面调用 getBar, 但是实际上还是取到了 foo 的 this.
因此箭头函数不可以用来当作构造函数. 因为它本身是没有 this 的!
所以箭头函数使用的话需要与普通函数区别开这点, 它的 this 指向定义函数时候的父级.
后话
关于 this 就介绍到这里, 如果有什么不懂的欢迎随时提问, 我会随时回答大家的问题.
那么最后, 成功不在一朝一夕, 我们都需要努力
来源: http://www.jianshu.com/p/56eedad524b3