this是什么

this是一个指针,并且永远指向一个对象,对于非箭头函数来说,具体指向哪个对象是在运行时基于函数的执行环境动态绑定的。对于箭头函数来说,具体指向哪个对象是在箭头函数申明时的环境确定的。

this的使用场景--非箭头函数

this的常用使用场景无非就是下面几种

  1. 作为对象的方法调用
  2. 作为普通函数调用
  3. 作为构造函数调用
  4. 作为事件的回调函数调用
  5. Function.prototype.call和Function.prototype.apply调用

1 作为对象的方法调用

例子

<script type = "text/javascript">// 作为对象的方法调用var userName = "wenyaoyao"var obj = {userName:"longhanghang",getName:function(){return this.userName;
        }
    }console.log(obj.getName());//输出 longhanghang</script>复制代码

以上代码运行之后输出 longhanghang

结论

当函数作为对象方法被调用时,this指向该对象。

衍生

如果把对象里面的函数赋值给一个用var定义的全局变量,然后再在全局下面执行会产生什么样的火花呢?

<script type = "text/javascript">// 作为对象的方法调用var userName = "wenyaoyao"var obj = {userName:"longhanghang",getName:function(){return this.userName;
        }
    }var getNameFun = obj.getName;console.log(getNameFun());//输出 wenyaoyao</script>复制代码

以上代码运行之后输出 wenyaoyao

刨析:因为此时getNameFun是在全局作用域下面执行的,所以this指向window。

2 作为普通函数调用

例子
<script type="text/javascript">  // 作为普通函数调用
  var userName = 'wenyaoyao';  function getName() {console.log(this.userName);
  }
  getName();  //输出 wenyaoyao</script>复制代码

以上代码运行之后输出 wenyaoyao

结论

当函数直接被调用时this指向全局对象,游览器里面是window,nodejs里面是global。

衍生

1 全局环境下使用var关键字定义的变量会直接挂靠到window上,如果是使用let和const呢?

<script type="text/javascript">  // 作为普通函数调用
  let userName = 'wenyaoyao';  function getName() {console.log(this.userName);
  }
  getName();  //输出 undefined</script>复制代码

以上代码运行之后输出 undefined

刨析: 可以发现let和const定义的变量并不会挂靠在window上,然后此时this指向的是window,所以打印的时候输出undefined。

3 作为构造函数调用

例子
<script type="text/javascript">  // 作为构造函数调用

  function Person() {this.userName = 'longhanghang';//return this;
  }  let person1 = new Person();  console.log(person1.userName);  //输出 longhanghang</script>复制代码

以上代码运行后输出longhanghang

结论

当使用new运算符调用函数时总是会返回一个对象,this指向这个对象。这也就是大名鼎鼎的构造函数。

衍生

1 函数内部怎么区分一个函数的调用类型呢?

<script type="text/javascript">  // 函数内判断调用类型
  let obj = {};  function gudgeType() {if (this instanceof gudgeType) {      console.log('构造函数调用');
    } else {      console.log('其他方式调用');
    }
  }

  gudgeType();  new gudgeType();  // 输出其他方式调用
  // 构造函数调用</script>复制代码

以上代码运行后依次输出 输出其他方式调用,构造函数调用

刨析: 我们可以巧妙的在函数内部使用instanceof对函数的调用方式进行判断。

4 作为事件的回调函数使用

例子
<!DOCTYPE html><html lang="en">
  <head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>作为事件的回调函数使用</title>
  </head>
  <body>  <div id = "test">点击我</div>
  </body>
  <script type="text/javascript">//作为事件的回调函数使用var testDom = document.querySelector("#test");
    test.onclick = function(e){console.log(this)
    }  </script></html>// 输出 <div id = "test">点击我</div>复制代码

以上代码运行后输出 <div id = "test">点击我</div>

结论

作为事件的回调函数调用时,this指向绑定事件的dom对象。

衍生

如果在事件的回调函数里面咱们再定义一个函数,函数内部的this又会指向哪里呢?

<!DOCTYPE html><html lang="en">
 <head>
   <meta charset="UTF-8" />
   <meta name="viewport" content="width=device-width, initial-scale=1.0" />
   <title>作为事件的回调函数使用</title>
 </head>
 <body> <div id = "test">点击我</div>
 </body>
 <script type="text/javascript">
   //作为事件的回调函数使用
   var testDom = document.querySelector("#test");
   test.onclick = function(e){       var text = function(){           console.log(this);
       }
       text();
   } </script></html>// 输出结果为window复制代码

以上代码运行后输出 window

刨析:此时的text是作为普通函数被调用的,所以指向windw。

5. Function.prototype.call和Function.prototype.apply调用

1 Function.prototype.call

<script type="text/javascript"> //Function.prototype.call()

 function getName(name, age) {   console.log(this.name, name, age);
 } var obj = { name: 'longhanghang' };
 getName.call(obj,'wenyaoyao',11); //输出为 longhanghang wenyaoyao 11</script>复制代码

1 Function.prototype.apply

<script type="text/javascript">//Function.prototype.apply()function getName(name, age) {  console.log(this.name, name, age);
}var obj = { name: 'longhanghang' };
getName.apply(obj,['wenyaoyao',11]);//输出为 longhanghang wenyaoyao 11</script>复制代码

以上代码最终输出都为longhanghang wenyaoyao 11

结论

执行一个函数我们通常都是采用的后面加()的方式,例如有名为 getName 这样的一个函数,我们通常会 使用getName()进行调用,这样调用的方式this的指向会遵循我们前面说的四种情况,但是如果我们不这样调用,而使用call和apply方法来调用时,那么this的指向取决于我们传的第一个参数,正如上面的示例,第一个参数我们传递的obj,那么函数内部的this指向的就是这个obj,后面的参数是函数执行时需要的参数,apply方法跟call方法作用差不多,只是后面的函数参数形式不同。

衍生

如果我们将函数作为对象的方法调用,然后再使用call或者apply时,会擦出怎么样的火花呢?

<script type="text/javascript">  //Function.prototype.apply()

  var obj1 = {name: 'longhanghang',getName: function () {      console.log(this.name);
    },
  };  var obj2 = { name: 'wenyaoyao' };
  obj1.getName.call(obj2);  //输出为 wenyaoyao</script>复制代码

以上代码输出为wenyaoyao

刨析:当我们使用了call或者apply执行函数时,优先级会高于作为对象的方法调用

this的使用场景--箭头函数

1 作为对象的方法调用

例子
<script type="text/javascript">  // 作为对象的方法调用
  var userName = 'wenyaoyao';  var obj = {userName: 'longhanghang',getName: () => {      return this.userName;
    },
  };  console.log(obj.getName());  //输出 wenyaoyao</script>复制代码

以上代码运行之后输出 wenyaoyao。

结论

this指向被申明时的环境,因为getName被申明时实在全局环境下的,所以这里的this指向window。

衍生1

如果把对象里面的函数赋值给一个用var定义的全局变量,然后再在全局下面执行会产生什么样的火花呢?

<script type="text/javascript">  // 作为对象的方法调用
  var userName = 'wenyaoyao';  var obj = {userName: 'longhanghang',getName: () => {      return this.userName;
    },
  };  var getNameFun = obj.getName;  console.log(getNameFun());  //输出 wenyaoyao</script>复制代码

以上代码运行之后输出 wenyaoyao

刨析:当箭头函数被申明的时候,this指向的是window。

衍生2

如果箭头函数里面嵌套一个箭头函数呢?

<script type="text/javascript">  // 作为对象的方法调用
  var userName = 'wenyaoyao';  var obj = {userName: 'longhanghang',getName: () => {      var getFirstName = () => {          return this.userName
      }      return getFirstName;
    },
  };  var getNameFun = obj.getName();  console.log(getNameFun());  //输出 wenyaoyao</script>复制代码

以上代码输出结果为wenyaoyao

刨析:当getFirstName申明的时候是在getName的这个作用域里面,this指向的就是getName指向的this,而getName在申明的时候this指向的是window,所以结论就是getName种的this也是指向window。

2 作为普通方法调用

例子
<script type = "text/javascript">let getName = () => {console.log(this)
    }
    getName();
</script>//输出是window复制代码

以上代码输出的结果是window

结论

this指向被申明时的环境,因为getName被申明时实在全局环境下的,所以这里的this指向window。

3 作为构造函数调用

例子
<script type = "text/javascript">var getName = () => {console.log(this)
    }var aa = new getName();//报错 Uncaught TypeError: getName is not a constructor</script>复制代码

以上代码会报错 Uncaught TypeError: getName is not a constructor

结论

箭头函数不能作为构造函数调用。

4 作为事件的回调函数调用

例子
<!DOCTYPE html><html lang="en">
  <head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>作为事件的回调函数使用</title>
  </head>
  <body>  <div id = "test">点击我</div>
  </body>
  <script type="text/javascript">//作为事件的回调函数使用var testDom = document.querySelector("#test");
    test.onclick = () => {console.log(this)
    }  </script>
  //输出为window</html>复制代码

以上代码输出为window

结论

this指向被申明时的环境,因为回调函数在申明时的环境是window,所以this指向window。

5. Function.prototype.call和Function.prototype.apply调用

例子
<script type = "text/javascript">var getName = () => {console.log(this)
    }
    
    getName.call(Object)
    getName.apply(Object)// 输出为window</script>复制代码

以上代码输出为 window

结论

Function.prototype.call和Function.prototype.apply无法改变箭头函数this的指向,this的指向还是在申明时就决定了。因为申明时的环境时window,所以this指向window而并不是指向构造函数Object。

箭头函数和非箭头函数的区别

  1. 箭头函数长得帅,非箭头函数长得漂亮
  2. 箭头函数都是匿名函数,非箭头函数可以是匿名函数也可以是具名函数
  3. 箭头函数不能使用new关键字,非箭头函数可以
  4. 箭头函数没有prototype属性,非箭头函数有
  5. 箭头函数不绑定arguments,非箭头函数要绑定
  6. 箭头函数无法使用call和apply修改的this的指向,非箭头函数可以
  7. 箭头函数的this在申明时决定,非箭头函数的this在执行时确定

自己实现call,apply,bind

前面用了这么多call和apply,那么咱们来点干货,自己手写一下这几个函数

1 call
   <script type="text/javascript">  Function.prototype.selfCall = function () {var args = arguments; //获取到实际参数//类型判断if (Object.prototype.toString.call(this) !== '[object Function]') {      throw new Error('必须使用函数调用该方法');
    }var realThis = Array.prototype.shift.call(args) || window; //截取出第一个参数也就是自定义this指向的这个对象var funName = Symbol('funName'); //定义一个Symbol类型的属性名realThis[funName] = this; //将方法赋值给对象的funName属性var res = realThis[funName](...args); // 将此方法作为传递进来的对象的方法调用,此时this已经指向我们传递进来的这个对象delete realThis[funName]; //删除这个对象上的临时属性return res;
  };  function getName(aa) {console.log(this);
  }  let obj = {};
  getName.selfCall(obj, 11);  //输出obj这个对象</script>复制代码

最终输出的是obj对象和11

2 apply
<script type="text/javascript">  Function.prototype.selfCall = function () {//类型判断if (Object.prototype.toString.call(this) !== '[object Function]') {      throw new Error('必须使用函数调用该方法');
    }var realThis = arguments[0] || window; //截取出第一个参数也就是自定义this指向的这个对象var args = arguments[1]; //取出剩下的参数作为函数运行的参数var funName = Symbol('funName'); //定义一个Symbol类型的属性名realThis[funName] = this; //将方法赋值给对象的funName属性var res = realThis[funName](...args); // 将此方法作为传递进来的对象的方法调用,此时this已经指向我们传递进来的这个对象delete realThis[funName]; //删除这个对象上的临时属性return res;
  };  function getName(aa, bb) {console.log(this, aa, bb);
  }  let obj = {};
  getName.selfCall(obj, [11, 22]);  //输出obj这个对象和11</script>复制代码
bind

bind和call以及apply有一点区别就是函数调用bind后会返回一个新的this指向的函数,而使用call和apply是改成this指向并执行

<script type="text/javascript">  Function.prototype.selfCall = function () {//类型判断if (Object.prototype.toString.call(this) !== '[object Function]') {      throw new Error('必须使用函数调用该方法');
    }var args = arguments; //获取到参数var realThis = [].shift.call(args) || window; //截取出第一个参数也就是自定义this指向的这个对象var funName = Symbol('funName');//定义一个Symbol类型的属性名realThis[funName] = this;//将方法赋值给对象的funName属性//返回一个匿名函数,匿名函数中去执行我们要改变this指向的函数return function () {      var diaoyongArgs = arguments;//获取到内层函数的参数  let allArgs = [...args, ...diaoyongArgs];//合并参数  let ret = realThis[funName](...allArgs);//将此方法作为传递进来的对象的方法调用,此时this已经指向我们传递进来的这个对象  delete realThis[funName];//方法执行完后,删除这个对象上的临时属性  return ret;
    };
  };  function getName() {console.log(this, ...arguments);
  }  let obj = {};
  getName.bind(obj, 11, 22)(33);  //输出 {} 11 22 33</script>复制代码

以上代码最终输出 {} 11 22 33

总结

史上最详细的this讲解_this