Home >Web Front-end >JS Tutorial >The amazing this_javascript trick in Javascript

The amazing this_javascript trick in Javascript

WBOY
WBOYOriginal
2016-05-16 15:18:591185browse

This in Javascript is a completely different mechanism from other languages, which may confuse some engineers who write other languages.

1. Mistakenly thinking that this points to the function itself

According to the English grammar of this, it is easy to understand this appearing in a function as the function itself. In JavaScript, functions are first-class citizens and can indeed store attribute values ​​when called. But if you use it incorrectly, it may lead to inconsistencies with actual expectations. For specific circumstances, please see the code below

  function fn(num){
    this.count++;
  }
  
  fn.count = 0;
  
  for(var i=0;i<3;i++){
    fn(i);
  }
  console.log(fn.count); // 0

If this in the fn function points to its own function, then the attribute value of the count attribute should change, but in fact it remains unchanged. For this problem, some people will use scope to solve it, for example, write like this

  var data = {
    count:0
  };
  
  function fn(num){
    data.count++;
  }
  
  for(var i=0;i<3;i++){
    fn(i);
  }
  
  console.log(data.count);  //3

Or more directly like this

  function fn(num){
    fn.count++;
  }
  
  fn.count = 0;
  
  for(var i=0;i<3;i++){
    fn(i);
  }
  
  console.log(fn.count);//3

Although both methods output the correct results, they avoid the problem of where this is bound. If the working principle of a thing is not clear, it will often lead to headaches and pain, which will lead to ugly code and poor maintainability.

2. This magical binding rule

2.1 Default binding rules

The first one is the most common this binding, take a look at the code below

  function fn(){
    console.log(window === this); //浏览器环境
  }
  fn(); //true

Function fn is called directly in the global scope without any other modifications. In this case, the default binding of this is used when the function is called, pointing to the global object.

This makes it clear that this in the first example points to the global variable in the fn function, so this.count++ is equivalent to window.count++ (in the browser environment). Of course, it will not affect the count of the fn function. properties have an impact.

One thing to note is that the above situation can only occur in non-strict mode (strict mode). In strict mode, this will be bound to undefined by default. To avoid contamination of global variables.

2.2 Implicit binding rules

If the function is called with an object as the context, the binding of this will change. this will be bound to the object calling this function, see the following code:

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

Even if the function declaration is not in the object, the this pointer will still change

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

It can be seen that the binding of this is not related to the location of the function definition, but to the caller and the calling method.

Under the implicit binding rules, there are some special things that need to be paid attention to.

2.2.1 Multi-layer object calls this pointer

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

Under multi-level object references, this points to the object of the called function.

2.2.2 Implicit assignment may be lost

View the code below

  function fn(){
    console.log(this);
  }
  var  obj = {
    fn:fn
  }
  
  var fun = obj.fn;
  fun(); //window

Although fn refers to obj.fun, the method of calling the function is still without any modification, so this is still bound to window.
There is another situation that is easy for everyone to overlook, that is, when passing parameters, implicit assignment will actually be performed.

 function fn(){
    console.log(this);
  }
  
  function doFn(fn){
    fn();
  }
  
  var obj = {
    fn:fn
  }
  
  doFn(obj.fn); //window

Implicit binding of this is not a very recommended method, because it is very likely to be lost. If there are requirements for the binding of this in the business, it is recommended to use explicit binding.

2.3 Explicit binding rules

Explicit binding uses the apply and call methods on the function prototype to bind this. The usage is to pass the object you want to bind as the first parameter.

  function fn(){
    console.log(this);
  }
  
  var obj = {};
  
  fn.call(obj); //{}    

Sometimes you want to bind the this of a function to an object, but do not need to call it immediately. In this case, it cannot be done directly using call or apply.

  function fn(){
    console.log(this);
  }
  
  function bind(fn){
    fn();
  }
  
  var obj = {
    fn:fn
  }
  
  bind.call(obj,fn); //window

The above example seems to work, but in fact, this of the bind function is bound to the object obj, but fn is still called without any modification, so fn is still the default binding method.

  function fn(){
    console.log(this);
  }
  
  function bind(fn,obj){
    return function(){
      fn.apply(obj,arguments);
    }
  }
  
  var obj = {
    fn:fn
  }
  
  var fun = bind(fn,obj);
  fun(); //obj

这样调用,就可以将灵活多变的 this ,牢牢的控制住了,因为 fn 的调用方式为 apply 调用。所以,this 就被绑定在传入的 obj 对象上,在 ES5 当中,函数的原型方法上多了一个 bind。效果与上面的函数基本一致,具体用法限于篇幅就不多说了。

2.4 new 绑定

new 是一个被很多人误解的一个关键字,但实际上 javascript 的 new 与传统面向对象的语言完全不同。
个人把 new 理解为一种特殊的函数调用,当使用 new 关键字来调用函数的时候,会执行下面操作,

  • 创建一个全新的对象
  • 将空对象的 __proto__ 指向构造函数的 prototype
  • 将新对象的 this 绑定到调用的函数上
  • 如果函数返回值为基本类型或者为 this又或者不返回任何值,那么将会返回这个创建的新对象,如果返回了一个对象,那么则会返回这个对象,而不会返回创建的新对象。
  function fn(a){
    this.a = a;
  }
  fn.prototype.hi = function(){
    console.log('hi')
  }
  
  var obj = new fn(2);
  
  console.log(obj);


  function fn(a){
    this.a = a;
    return {};
  }
  
  var obj = new fn(2);
  
  console.log(obj); //{}

2.5 特殊的传参

null 和 undefined 也是可以作为 this 的绑定对象的,但是实际上应用的是默认的绑定。
但是这种传参的实际效用是什么呢?
常见的用法是将一个数组展开,作为参数传入参数。比如

  function fn(a,b){
    console.log('a:',a,'b:',b);
  }
  
  fn.apply(null,[1,2]); // a: 1 b: 2

但是这种用法会有一个坑,那就是如果函数存在了 this ,那么就会应用默认的绑定规则,将 this 绑定在全局对象上,发生于预期不一致的情况。为了代码更加稳健,可以使创建一个比空对象更空的对象。

var obj = Object.create(null);
console.log(obj.__proto__); //undefined

var obj2 = {}
console.log(obj2.__proto__); //Object {}

Object原型上有一个 create 方法,这个方法会创建一个对象,然后将对象的原型指向传入的参数,所以传入 null 的话,产生一个没有 prototype 的对象,所以会比空对象更加"空"。

所以传入这个对象,会比传入 null 更加安全。

var obj = Object.create(null);

fn.apply(obj,[1,2]);

2.6 根据作用域来决定 this 的绑定

在 ES6 当中,出现了一个新的函数类型,箭头函数。

如果使用箭头函数,那么就不会使用上面提到的四种 this 绑定方式,而是根据作用域来决定

比较常见的是用于事件函数和定时器的情况。

下面是比较常见的传统 this 写法

  function fn(){
    var _this = this;
    setTimeout(function(){
      console.log(_this.a);
    },100)
  }

  var obj = {
    a:2
  }
  
  fn.call(obj); //2
  

如果使用箭头函数则可以这么写

  function fn(){
    setTimeout(()=>{
      //this 来源于 fn 函数的作用域
      console.log(this.a);
    },100)
  }

  var obj = {
    a:2
  }
  
  fn.call(obj); //2

2.7 事件函数当中 this 的绑定机制

如果是在事件函数当中,this 的绑定是指向触发事件的 DOM 元素的,

$('body')[0].addEventListener('click',function(){
  console.log(this);
},false);

点击 body 元素之后,控制台则会显示 body 元素

3. 小结

如果想判断一个函数的 this 绑定在哪里,首先是找到函数的调用位置,之后是按照规则来判断。

  • 如果函数调用时没有任何修饰条件,那么在严格模式下则会绑定到 undefined ,非严格模式下会绑定到全局。
  • 如果是用对象做上下文,来对函数进行调用,那么则会绑定到调用的这个对象上。
  • 如果是用 call 或者 apply 方法来进行调用的,则会绑定到第一个传入参数上。
  • 如果是使用 new 关键字来调用函数的,则会绑定到新创建的那个对象上.
  • 如果是在事件函数内,则会绑定到触发事件的那个DOM元素上。

以上就是关于Javascript中神奇的this的相关介绍,希望对大家的学习有所帮助。

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn