JavaScript中的this详细解析-this的绑定规则-this绑定规则的优先级-箭头函数的使用-箭头函数中的this

导读:本篇文章讲解 JavaScript中的this详细解析-this的绑定规则-this绑定规则的优先级-箭头函数的使用-箭头函数中的this,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

1.this到底指向什么

我们先来看一个让人困惑的问题:

  • 定义一个函数,我们采用三种不同的方式对它进行调用,this它产生了三种不同的结果

  • // 定义一个函数
    function foo() {
      console.log(this);
    }
    
    // 调用方式一:直接调用(默认调用)
    foo(); // this指向window
    
    // 调用方式二:将foo仿到一个对象中,在调用
    var obj = {
      name: "kaisa",
      foo: foo,
    };
    obj.foo(); // this指向obj对象
    
    //调用方式三:通过call/apply调用
    foo.call("abc"); // this指向String{"abc"}对象
    

这个的案例可以给我们什么样的启示呢?

  1. 函数在调用时,JavaScript会默认给this绑定一个值
  2. this的绑定和定义的位置(编写的位置)没有关系
  3. this的绑定和调用方式以及调用的位置有关系
  4. this是在运行时被绑定的

那么this到底是怎么样的绑定规则呢?一起来学习一下吧

  • 绑定一:默认绑定;
  • 绑定二:隐式绑定;
  • 绑定三:显示绑定;
  • 绑定四:new绑定;

2.this绑定规则

2.1 绑定一: 默认绑定

什么情况下使用默认绑定呢?独立函数调用。

  • 独立的函数调用我们可以理解成函数没有被绑定到某个对象上进行调用
  • 默认绑定this指向的是window
  • 严格模式下,独立调用的函数中this指向的是undefined

我们通过几个案例来看一下,常见的默认绑定

  • // 案例一:
    function foo() {
      console.log(this); // window
    }
    foo();
    
  • // 案例二:
    function test1() {
      console.log(this); // window
      test2();
    } 
    function test2() {
      console.log(this); // window
      test3();
    }
    function test3() {
      console.log(this);  // window
    }
    test1();
    
  • // 案例三:
    function foo(func) {
      func();
    }
    
    var obj = {
      name: "kaisa",
      bar: function () {
        console.log(this); // window
      },
    };
    
    foo(obj.bar);
    

2.2 规则二: 隐式绑定

隐式绑定比较常见的调用方式是通过某个对象进行调用的:

  • 也就是它的调用位置中,是通过某个对象发起的函数调用, 并且this指向这个发起调用的对象

我们通过几个案例来看一下,常见的隐式绑定

  • // 案例一
    function foo() {
      console.log(this); // obj
    }
    
    var obj = {
      name: "kaisa",
      foo: foo,
    };
    
    obj.foo();
    
  • // 案例二
    function foo() {
      console.log(this); // obj1
    }
    
    var obj1 = {
      name: "obj1",
      foo: foo,
    };
    
    var obj2 = {
      name: "obj2",
      obj1: obj1,
    };
    
    obj2.obj1.foo();
    

2.3 规则三: 显式绑定

2.3.1 显示绑定介绍

隐式绑定有一个前提条件:

  • 必须在调用的对象内部有一个对函数的引用(比如一个属性);

  • 如果没有这样的引用,在进行调用时,会报找不到该函数的错误;

  • 正是通过这个引用,间接的将this绑定到了这个对象上

  • // 隐式绑定前提条件
    var obj = {
      name: "kaisa",
    };
    
    function foo() {
      console.log(this);
    }
    
    // 如果想将this绑定到obj身上
    obj.foo = foo; // 需要在对象的内部有一个对函数的引用
    obj.foo();
    

如果我们不希望在 对象内部 包含这个函数的引用,同时又希望在这个对象上进行强制调用,该怎么做呢?

JavaScript所有的函数都可以使用call和apply方法。

  • // apply语法
    func.apply(thisArg, [argsArray])
    
  • // call语法
    func.call(thisArg, arg1, arg2, ...)
    
  • 第一个参数是相同的,要求传入一个对象;

    • 这个对象的作用是什么呢?就是给this准备的。
    • 调用这个函数时,会将this绑定到这个传入的对象上
  • 后面的参数,apply为数组,call为参数列表;

使用call和apply方法可以明确的绑定this指向的对象

  • // 示例代码
    var obj = {
      name: "kaisa",
    };
    
    function foo() {
      console.log(this);
    }
    
    // 传入对象this会指向传入对象, 若传入的是原始数据类型会指向原始数据类型的包装类
    
    foo.call(obj); // 将对象绑定到obj上 {name: 'kaisa'}
    foo.call("abc"); // 将对象绑定到String包装类上 String {'abc'}
    foo.apply(123); // 将对象绑定到Number包装类上 Number {123}
    

因为上面的过程,我们明确的绑定了this指向的对象,所以称之为 显式绑定。

2.3.2 apply和call区别

apply() 第一个参数: 绑定this, 第二个参数: 传入额外的实参, 以数组的形式

function foo(name, age, height) {
  console.log(this, name, age, height); // String {'abc'} 'kaisa' 18 1.88
}

foo.apply("abc", ["kaisa", 18, 1.88]);

call() 第一个参数: 绑定this, 后续参数: 传入额外的实参, 以参数列表的形式

function foo(name, age, height) {
  console.log(this, name, age, height); // String {'abc'} 'kaisa' 18 1.88
}

foo.call("abc", "kaisa", 18, 1.88);

2.3.3 bind方法

如果我们希望一个函数总是显示的绑定到一个对象上,可以怎么做呢?

  • 通过apply和call方法绑定this, 不能总是绑定到一个对象上, 每次调用时都需要绑定对应的对象

  • 使用bind方法,bind() 方法创建一个新的绑定函数(bound function,BF);

  • 绑定函数是一个 exotic function object(怪异函数对象,ECMAScript 2015 中的术语)

  • 在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

bind语法

fun.bind(thisArg[, arg1[, arg2[, ...]]])

bind基本使用

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

var obj = {
  name: "kaisa",
};

// 需求: 调用foo函数时, 总是绑定到obj对象上(不希望obj对象身上有函数).
var bar = foo.bind(obj);
bar(); //this -> obj
bar(); //this -> obj
bar(); //this -> obj

bind的参数使用

// 可以在bind中将实参传入
function foo(name, age, height) {
  console.log("参数", name, age, height);
}

var obj = {
  name: "kaisa",
};

var bar = foo.bind(obj, "coder", 18, 1.88);
bar();
// 也可以在创建出来的函数bar中传入参数, bar中的参数时在bind中参数的后面
function foo(name, age, height) {
  console.log("参数", name, age, height);
}

var obj = {
  name: "kaisa",
};

var bar = foo.bind(obj, "coder", 18);
bar(1.88);

2.4 规则四: new绑定

JavaScript中的函数可以当做一个类的构造函数来使用,也就是使用new关键字。

使用new关键字来调用函数是,会执行如下的操作:

  1. 创建一个全新的对象
  2. 这个新对象会被执行prototype连接;
  3. 构造函数内部的this, 会指向创建出来的新对象this的绑定在这个步骤完成);
  4. 执行构造函数内部的代码(函数体代码)
  5. 如果函数没有明确返回其他对象,表达式会返回这个创建出来的新对象
function Person(name) {
  console.log(this); // Person {}
  this.name = name; // Person {name: "kaisa"}
}

var p = new Person("kaisa");
console.log(p); // Person {name: "kaisa"}

3.内置函数的this

有些时候,我们会调用一些JavaScript的内置函数,或者一些第三方库中的内置函数。

  • 这些内置函数会要求我们传入另外一个函数(比如定时器);
  • 我们自己并不会显示的调用这些函数,而且JavaScript内部或者第三方库内部会帮助我们执行(调用)
  • 这些函数中它们的this又是如何绑定的呢?

比如setTimeout、数组的forEach、元素的事件监听

  • // 定时器
    setTimeout(function () {
      console.log(this); // window
    }, 1000);
    
  • // 数组的forEach
    var names = ["abc", "cab", "bac"];
    names.forEach(function (item) {
      console.log(this); // window window window
    });
    
    // forEach中第二个参数可以指定绑定this
    names.forEach(function (item) {
      console.log(this); // String {'aaa'} String {'aaa'} String {'aaa'}
    }, "aaa");
    
  • // 元素的点击
    <button>按钮</button>
    <script>
      var btnEl = document.querySelector("button");
      btnEl.onclick = function () {
        console.log(this); //<button>按钮</button>
      };
    </script>
    

针对一些JavaScript的内置函数,或者一些第三方库中的内置函数中的this, 我们只能根据经验总结

比如上述代码中, 定时器的this指向window, 事件函数中的this指向事件调用者, forEach中的this默认指向window

4.this绑定优先级

学习了四条规则,接下来开发中我们只需要去查找函数的调用应用了哪条规则即可,但是如果一个函数调用位置应用了多
条规则,优先级谁更高呢?

  1. 默认规则的优先级最低

    • 毫无疑问,默认规则的优先级是最低的,因为存在其他规则时,就会通过其他规则的方式来绑定this
  2. 显示绑定优先级高于隐式绑定

    // 显式绑定高于隐式绑定
    // apply/call高于隐式绑定
    function foo() {
      console.log(this);
    }
    
    var obj = {
      name: "kaisa",
      foo: foo,
    };
    obj.foo.apply("aaa"); // String {'aaa'}
    obj.foo.call("bbb"); // String {'bbb'}
    
    // bind高于隐式绑定
    function foo() {
      console.log(this);
    }
    
    var bar = foo.bind(123);
    
    var obj = {
      name: "kaisa",
      brz: bar,
    };
    
    obj.brz(); // Number {123}
    
  3. new绑定优先级高于隐式绑定

    // new绑定高于隐式绑定
    function foo() {
      console.log(this);
    }
    
    var obj = {
      name: "kaisa",
      foo: foo,
    };
    new obj.foo(); // foo {}
    
  4. new绑定优先级高于bind

    • new绑定和call、apply是不允许同时使用的,所以不存在谁的优先级更高

    • new绑定可以和bind一起使用,new绑定优先级更高

      function foo() {
        console.log(this);
      }
      
      var bar = foo.bind(123);
      
      var obj = {
        name: "kaisa",
        brz: bar,
      };
      
      new obj.brz(); // foo {}
      

5.this的规则之外

我们讲到的规则已经足以应付平时的开发,但是总有一些语法,超出了我们的规则之外。(神话故事和动漫中总是有类似这样的
人物)

5.1 情况一: 忽略显式绑定

如果在显示绑定中,我们传入一个null或者undefined,那么这个显示绑定会被忽略,使用默认规则绑定window对象:

  • 因为null和undefined没有默认的包装类型, 因此会使用默认绑定规则
function foo() {
  console.log(this);
}

foo.apply(undefined); // this指向window
foo.call(null); // this指向window
var bar = foo.bind(undefined); // this指向window
bar();

5.2 情况二: 间接函数引用

创建一个函数的 间接引用,这种情况使用默认绑定规则。

  • 赋值 (obj2.foo = obj1.foo) 返回的结果是foo函数;
  • foo函数被直接调用,那么是默认绑定;
var obj1 = {
  name: "obj1",
  foo: function () {
    console.log(this);
  },
};

var obj2 = {
  name: "obj2",
};

obj2.foo = obj1.foo;
obj2.foo(); // obj2

// 但是如果写成这种 因为(obj2.foo = obj1.foo)会返回一个函数
// 索引会当做独立函数调用 绑定默认规则 this指向window
(obj2.foo = obj1.foo)(); // window

5.3 情况三: 箭头函数

箭头函数(arrow function)是ES6之后增加的一种编写函数的方法, 箭头函数也是属于规则之外的一种情况, 接下来详细介绍箭头函数

6.箭头函数

6.1 箭头函数的介绍

箭头函数(arrow function)是ES6之后增加的一种编写函数的方法,并且它比函数表达式要更加简洁:

  • 箭头函数不会绑定this、arguments属性
  • 箭头函数不能作为构造函数来使用不能和new一起来使用,会抛出错误);

箭头函数如何编写呢?

  • (): 函数的参数

  • {}: 函数的执行体

    // 演示箭头函数
    var foo = () => {
      console("箭头函数的函数体")
    }
    
    nums.forEach((item, index, arr) => {
      console(item, index, arr)
    })
    
    setTimeout(() => {
      console.log("setTimeout");
    }, 1000);
    

6.2 箭头函数的编写优化

优化一: 如果只有一个参数()可以省略

var names = ["aaa", "bbb", "ccc"]

nums.forEach(item => {}) 

优化二: 如果函数执行体中只有一行代码, 那么可以省略大括号

  • 并且这行代码的返回值会作为整个函数的返回值

    var names = ["aaa", "bbb", "ccc"]
    
    nums.forEach(item => console(item))
    nums.filter(item => true)
    

优化三: 如果函数执行体只有返回一个对象, 那么需要给这个对象加上()

// 函数体中返回一个对象
var foo = () => {
  return { name: "kaisa" }
}

// 给对象加上()
var bar = () => ({ name: "kaisa" })

练习代码: 计算数组中偶数的平方的和

var nums = [20, 30, 40, 65, 67, 21, 33];
var result = nums.filter(item => item % 2 === 0)
                 .map(item => item * item)
                 .reduce((prevValue, item) => prevValue + item);
console.log(result);

6.3 箭头函数this规则之外

**箭头函数不使用this的四种标准规则(也就是不绑定this),箭头函数的this是根据外层作用域来决定的。**因为箭头函数内部, 压根就没有this

我们来看一个模拟网络请求的案例:

  • 这里我使用setTimeout来模拟网络请求,请求到数据后如何可以存放到data中呢?

  • 我们需要拿到obj对象,设置data;

  • 但是setTimeout直接拿到的this是window,我们需要在外层定义:var _this = this

  • 在setTimeout的回调函数中使用_this就代表了obj对象

    // 早期ES6之前的做法
    var obj = {
      data: [],
      getDate: function () {
        var _this = this;
        setTimeout(function () {
          // 定时器中有自己的this 指向window
          // 模拟获取到的数据
          var res = ["aaa", "bbb", "ccc"];
          _this.data.push(...res);
        }, 1000);
      },
    };
    
    obj.getDate();
    

之前的代码在ES6之前是我们最常用的方式,从ES6开始,我们会使用箭头函数:

  • 为什么在setTimeout的回调函数中可以直接使用this呢?

  • 因为箭头函数并不绑定this对象(箭头函数中并没有this),那么this引用就会从上层作用于中找到对应的this

    // ES6之后的做法
    var obj = {
      data: [],
      getDate: function () {
        
        setTimeout(() => {
          // 模拟获取到的数据
          var res = ["aaa", "bbb", "ccc"];
          this.data.push(...res);
        }, 1000);
        
      },
    };
    
    obj.getDate();
    

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由半码博客整理,本文链接:https://www.bmabk.com/index.php/post/120146.html

(0)

相关推荐

发表回复

登录后才能评论
半码博客——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!