花了半天时间, 终于彻底弄懂了JS中的this指向问题

先说结论, 然后我们一步一步去验证它
Rule1: 只有对象中有this!!!, 普通的代码片段是没有this, 例如: function中(关于function是不是对象, 这个问题业界一直没有停止过讨论, 本文你就把它当成不是一个对象就好了, 我说的, lol....), if语句中...
Rule2: this某种意义上来说其实就是某个作用域下的一个变量, 所以它也满足作用域链的查找原则, 不了解什么是作用域链的可以先了解一下作用域链, 会对你对以下的内容有帮助(简单来说就是当某个作用域中想使用某个变量在当前作用域找不到时,它就会往它的上级作用域去找)
Rule3: this的指向在函数创建时是决定不了的, 在调用的时候才可以决定,谁调用就指向谁。
Rule4: 对于call和apply的理解, functionA.call(obj), 其实就是相当于把functionA拿到obj里面去执行, apply也是一样, call和apply唯一的区别就是传参的时候的区别, 具体可以查资料哈
Rule5: 对了bind的理解, 看这个var functionB = functionA.bind(obj); bind, 意思就是绑定嘛,就是把functionA捆绑到obj里面了, 你要执行的话, 它始终在obj里面, 当你使用call的时候, 比如functionB.call(aotherObj), 这里只是改变functionB的指向,并不会改变functionA的指向, 这里说的有点多, 如果不理解请看第七题, 你就知道是什么意思了!
Rule6: 对于new的理解, new的时候相当于直接copy代码, 比如这一段:function demo(){

this.abcddd = 'abc'
console.log(this);

}
它相当于就是简单的复制这么代码到自己的对象中, 意思就是说如果demo.bind了一个对象, 它是不会拿它上面那一层的!

function Fn(){
  this.user = "Hi Felix";
}
var demo = new Fn();

这里发生了什么?

  • 第一步: 生成一个空对象 {}
  • 第二步: 把函数Fn的代码填进去, 就是这样
    {}.user = "Hi Felix";
    也就是:
    {user: "Hi Felix"}
  • 第三步: 把这个空对象的引用地址(也就是栈区的那个地址)赋给demo

Rule7: 当函数里面出现return时, 如果renturn是的一个对象, 就会指向这个对象, null除外, 如果返回值不是一个对象,那么this还是指向函数的实例, 看第九题
Rule8: 箭头函数中, this的指向只跟定义时的指向有关, 跟使用无关


先来了解一下作用域的尽头,分为三种情况:

  • 在非严格模式下,浏览器中的尽头当然就是window。
  • 在严格模式下也就是开启了"use strict"的情况下,尽头就是undefined。
  • node的全局环境中尽头是global。

下文中情况主要从第一种非严格模式下来对this的指向进行解释和说明


话不多说,直接上代码
第一题:

function demo(){
  var user = "Hi Felix";
  console.log(this.user);   // undefined
  console.log(this);        // window
}

demo();

函数作用域下是没有this的, 所以往上找, 找到全局作用域, 全局作用域下的this就是window啦, 然后window上也没有定义user,所以this.user是undefined, 验证Rule1和Rule2, 这题过


第二题

var obj = {
  user: "Hi Felix",
  fn:function(){
     console.log(this.user);   // Hi Felix
  }
}

obj.fn();

函数作用域下是没有this的, 所以往上找, 找到obj, 所以输出Hi Felix, 验证Rule1和Rule2, 这题过,


第三题

var obj = {
  user:"Hi Felix";
  fn:function(){
     console.log(this.user); // Hi Felix
  }
}
window.obj.fn(); 

额...还是 验证Rule1和Rule2....


第四题

var obj = {
    a:1,
    b:{
        a:2,
        fn:function(){
            console.log(this.a);  // 2
        }
    }
}

obj.b.fn();

恩???还是 验证Rule1和Rule2....怎么样, 是不是觉很简单?重点来了...


第五题

var obj = {
    a:1,
    b:{
        a:2,
        fn:function(){
            console.log(this.a);  // undefined
            console.log(this);   //window 
        }
    }
}
var demo = obj.b.fn;
demo();

啥?你不是说this满足作用域链吗?这里this.a不是应该是2吗,this不应该是b对象吗?不不不, 让我们看看这段代码var demo = obj.b.fn;, 它这里相当于做了一个什么操作呢?其实就相当于是这样

window.demo = obj.b.fn;
// 也就是 >>> 
window.demo = fn:function(){
    console.log(this.a);  // undefined
    console.log(this);    //window 
}

这样我想你就明报了吧?( Rule3)


第六题

function returnThis(){
  return this;
}
var user = {name: "Hi Felix"};

console.log(returnThis());            // window
console.log(returnThis.call(user));   // Hi Felix
console.log(returnThis.apply(user));  // Hi Felix

第一个log, 函数中没有this, 往外找到window, return window(rule2)
第二个log, 把returnThis拿到user对象里面去执行, 拿到user, return user;
第三个log, 同2


第七题

function returnThis(){
  return this;
}

var user1 = {name:"Hi Felix"};
var user1returnThis = returnThis.bind(user1);
console.log(user1returnThis());                 // Hi Felix
var user2 = {name:"Hi Chan"};
console.log(user1returnThis.call(user2));       // still Hi Felix

对比Rule5来理解这里, 第一个log, 因为user1returnThis相当于就是这样

{
    // user1对象
    returnThis()
}

所以打印出来是HiFelix, 而第二个log, 则相当于是这样
`
{

// user2对象
user1对象: {
    // user1对象
    returnThis()
}

}
`
根据Rule2和Rule5是不是很好理解啦?


第八题

function Fn(){
  this.user = "Hi Felix";
}
var demo = new Fn();
console.log(demo.user);          // Hi Felix

再看Rule6你是不是就明白了呢?
再看这个

function demo(){
  console.log(this);
}

demo();             // window, 如果不懂请看Rule2
new demo();         // demo{}实例, 请看Rule6和Rule2

var user1 = {name:"Hi Felix"};
demo.call(user1);   // Hi Felix, 这里请看Rule4

var user2 = demo.bind(user1);
user2();            // Hi Felix, 这里请看Rule5

new user2();        // 这里有点小复杂, 我要分析一下, 请往下看

我们从右边开始往左边看var user2 = demo.bind(user1), 这里参考Rule5就可以知道, 它把demo函数和user做了一个捆绑操作, 就是说, 你要执行demo函数的话, 就必须拿到user里面来执行,所以当我们在user2(), 输出的是user1的内容, 而当我们new的时候, 相当于直接拿demo中的代码来执行, 所以输出的demo{}实例, 这里需要注意的是, 这里的demo{}和上面的demo{}是不一样的, 因为是两个不同的实例, 这里有点绕, 需要花点事件琢磨一下哦(Rule6)


第九题:
这里需要说明一下, 这些代码你要一段一段的执行, 否则你会遇到,关于函数提升和变量提升的问题, 恭喜你, 如果感兴趣的话, 你可以去了解一下这一块的知识.

function fn(){
    this.user = "Felix";
    return{};
}
var a = new fn;
console.log(a.user);  // undefined

function fn(){
    this.user = "Felix";
    return function(){};
}
var a = new fn;
console.log(a.user);  // undefined

function fn(){
    this.user = "Felix";
    return 1;
}
var a = new fn;
console.log(a.user); // Felix

function fn(){
    this.user = "Felix";
    return undefined;
}
var a = new fn;
console.log(a.user); // Felix
        
function fn(){
    this.user = "Felix";
    return null;
}
var a = new fn;
console.log(a.user); // Felix

最后, 我们有请今天的大boss, 箭头函数, 如果你看了上面的内容还是弄不清this的指向, 那就尽量使用箭头函数来避免吧
箭头函数中, this的指向只跟定义时的指向有关, 跟使用无关Rule8, 下面我们来看几个例子:

function callback(qdx){
  qdx();
}
var user = {
    name:"Felix",
    callback,
    callback1(){
      callback(()=>{console.log(this)});
    },
    obj2: {
        name:"Chan",
        callback2(){
          callback(()=>{console.log(this)});
        }
    },
    obj3: {
        name:"Mr",
        callback3(func){
          func()
        }
    }
}
user.callback(()=>{console.log(this)});
user.callback1();
user.obj2.callback2();
user.obj3.callback3(()=>{console.log(this)});
var test = {
    name: "I dont know what!",
    method() {
        user.callback(()=>{console.log(this)});
    }
}
test.method()

结果我直接截图了
QQ截图20200427143851.png
由此可见, 箭头函数的this指向跟定义的时候的this指向, 如果作用域不是一个对象(函数除外), 继续往上面找,同样满足作用域链的规则!


本文参考:https://mp.weixin.qq.com/s/DG...
致谢......

你可能感兴趣的:(this,作用域链,函数,变量)