JavaScript this 绑定详解

  • JavaScript this 绑定详解已关闭评论
  • 53 次浏览
  • A+
所属分类:Web前端
摘要

函数内this的绑定和函数定义的位置没有关系,和调用的方式和调用位置有关系,函数内的this是在被调用执行时被绑定的。


函数内 this 绑定

函数内this的绑定和函数定义的位置没有关系,和调用的方式和调用位置有关系,函数内的this是在被调用执行时被绑定的。

this的具体绑定规则

this 绑定基本包含下面4种绑定规则,以及一些其它的特殊绑定规则:

  1. 默认绑定
  2. 隐式绑定
  3. 显式绑定
  4. new绑定

默认绑定

独立的函数被调用,那么里面的this绑定的是window,也就是调用时没有被绑定到具体的对象。

比如普通的函数调用:

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(); 

在调用每一个方法的时候,方法的前面没有绑定对象,并不是像obj.test1()这种形式前面绑定了对象。

第三种情况是函数作为参数:

function foo(func) {   func(); }  function bar() {   console.log(this);    // window }  foo(bar); 

同上,虽然看起来bar到了foo的上下文中了,但是调用bar函数时,前面没有绑定对象,那么还是默认绑定,是由window调用的。

可以再看一下上例中的变例:

function foo(func) {   func(); }  var obj = {   name: "easylee",   bar: function () {     console.log(this);  // window   }, };  foo(obj.bar); 

上面的变例,表面上看,调用的是obj对象的bar方法,但是具体执行的时候,是将这个方法整体作为参数传入foo,foo里面执行func()的时候,执行func()并没有绑定任何对象,调用obj.bar传入参数的时候,只是调用并没有执行,所以还是window

一定要注意,只有在调用执行的时候绑定了对象才算绑定,定义的时候就算它绑定了,但是调用并执行的时候未绑定就不算绑定。

提示:

在严格模式下,默认绑定模式,直接调用this(也就是window)会打印undefined,在webpack等打包工具中,通常会使用严格模式,来避免一些低级的语法错误。

所以在直接打印this的地方,直接使用window来代替 this

隐式绑定

通过一个对象,调用对象里面的函数,也就是通过某个对象发起的函数调用,此时函数的this就会绑定为这个对象。

function foo() {   console.log(this); }  let obj1 = {   name: "obj1",   foo: foo };  let obj2 = {   name: 'obj2',   obj1: obj1 }  obj1.foo();  // obj1 obj2.obj1.foo();  // obj1  let bar = obj1.foo; bar();  // window,还是默认绑定,最终还是window在调用这个函数 

隐式绑定有一个前提是调用的对象内部一定有一个对函数的引用,如上例中的foo:foo,对象一定有一个引用函数的属性,通过这个引用,间接的将函数里面的this绑定到这个对象中。

显式绑定

如果不希望在对象内部包含函数引用的属性,同时又希望this绑定到这个对象上,那么可以使用显示绑定,就不需要在这个对象设置函数引用属性了。

通常使用 callapplybind 来给this显式的绑定一个对象。

使用callapply均是显示的指定函数执行时,函数中this的指向,通过第一个参数传递需要绑定的对象。

三者的区别,可以看:https://www.cnblogs.com/easy1996/p/17954254

let name = "window";  function foo() {   console.log(this.name); }  let obj1 = {   name: "obj1", };  foo();  // window  foo.call(obj1); // obj1 foo.apply(obj1);  // obj1 

可以看到,后面两个函数显式指定了对象,其中的this打印的是 obj1name 属性。

bind函数也是一样的,只是还需要调用一遍:

function foo() {   console.log(this.name); }  let obj1 = {   name: "obj1", };  let bar = foo.bind(obj1); bar(); // obj1 

临时绑定对象一般使用call和apply即可,像是很多框架中,使用bind来绑定并创建新的函数,之后均使用新函数即可,这样做的好处是,可以固定的绑定一个对象,因为新函数是绑定后创建的,那么里面的this则不会再发生改变。

new绑定

new绑定则非常简单,类里面的 this 就是 new Class() 创建对象时,创建后的对象。

function Person() {   console.log(this);  // {name: "person1"} 和 {name: "person2"} }  let p = new Person(); p.name = 'person1'  let p2 = new Person(); p2.name = 'person2' 

其它绑定规则

有一些绑定规则不遵循上面的四条绑定规则,同时也是非常常用的规则:

1.显式绑定忽略

如果callapplybind绑定的对象是null或者undefined那么显示绑定会被忽略,转变为默认绑定,this 指向 window

2.自执行间接函数引用

如果使用自执行的方式去执行,引用其它对象的函数的这种形式,那么会指向window。具体效果查看代码,这个记住就行,不要去套上面的规则,肯定套不上:

let obj1 = {   name: 'obj1' }; let obj2 = {   name: 'obj2',   foo: function () {     console.log(this.name);   }, }; obj2.foo(); // obj2 obj1.bar = obj2.foo; obj1.bar();  // obj1 (obj1.foo = obj2.foo)();  // window不是obj1,是window调用的 

3.内置函数的绑定

setTimeout内绑定的是window:

setTimeout(function () {   console.log(this); // window }); 

forEach等遍历函数的绑定:

["foo", "bar"].forEach(function () {   console.log(this); // window 默认是window }); let obj1 = {   name: "obj1", }; ["foo", "bar"].forEach(function () {   console.log(this); // obj1,可以传递绑定的对象参数 }, obj1); 

元素的事件默认绑定的是元素对象,除非使用箭头函数:

const box = document.querySelector(".box"); box.onclick = () => {   console.log(this);  // window };  box.onclick = function() {   console.log(this);  // box对象 } 

4.箭头函数的绑定

箭头函数的this不绑定任何对象,而是从上层作用域中找到对应的this的绑定对象。

let obj1 = {   name: "obj1",   foo: () => {     console.log(this);   },   bar: function () {     console.log(this);   }, }; obj1.foo(); // window 当前默认是obj1对象中,使用箭头函数所以上一层是window obj1.bar(); // obj1 

要注意箭头函数不仅默认this不绑定对象,而且是无法绑定,就算通过显式绑定,也无法生效。

常见题目

常见题目1

let name = "window"; let person = {   name: "person",   sayName: function () {     console.log(this.name);   }, }; function sayName() {   let sss = person.sayName;   sss();  // window   person.sayName(); // person   (person.sayName)(); // person 这里就是单纯的执行一下,和上面没区别   (b = person.sayName)(); // window  } sayName(); 

常见题目2

var name = "window"; var person1 = {   name: "person1",   foo1: function () {     console.log(this.name);   },   foo2: () => console.log(this.name),   foo3: function () {     return function () {       console.log(this.name);     };   },   foo4: function () {     return () => {       console.log(this.name);     };   }, }; var person2 = { name: "person2" };  person1.foo1(); // person1 person1.foo1.call(person2); // person2  person1.foo2(); // window person1.foo2.call(person2); // window  person1.foo3()(); // window person1.foo3.call(person2)(); // window person1.foo3().call(person2); // person2  person1.foo4()(); // person1 person1.foo4.call(person2)(); // person2 person1.foo4().call(person2); // person1 

常见题目3

var name = "window"; function Person(name) {   this.name = name;   this.foo1 = function () {     console.log(this.name);   };   this.foo2 = () => console.log(this.name);   this.foo3 = function () {     return function () {       console.log(this.name);     };   };   this.foo4 = function () {     return () => {       console.log(this.name);     };   }; } var person1 = new Person("person1"); var person2 = new Person("person2");  person1.foo1(); // person1 person1.foo1.call(person2);   // person2  person1.foo2(); // person1 person1.foo2.call(person2); // person1  person1.foo3()(); // window person1.foo3.call(person2)(); // window person1.foo3().call(person2); // person2  person1.foo4()(); // person1 person1.foo4.call(person2)(); // person2 person1.foo4().call(person2); // person1 

常见题目4

var name = "window"; function Person(name) {   this.name = name;   this.obj = {     name: "obj",     foo1: function () {       return function () {         console.log(this.name);       };     },     foo2: function () {       return () => {         console.log(this.name);       };     },   }; } var person1 = new Person("person1"); var person2 = new Person("person2");  person1.obj.foo1()(); // window 最终调用函数时,没有任何隐式绑定和显示绑定,就是普通调用 person1.obj.foo1.call(person2)(); // window person1.obj.foo1().call(person2); // person2  person1.obj.foo2()(); // obj person1.obj.foo2.call(person2)(); // person2 person1.obj.foo2().call(person2); // obj