美文网首页
笔记-this绑定

笔记-this绑定

作者: 强子ly | 来源:发表于2023-10-12 18:44 被阅读0次

目录

  • 1、核心
  • 2、绑定规则
  • 3、绑定例外
  • 4、优先级
  • 5、箭头函数
  • 6、总结
  • 7、面试题
  • 8、参考书籍、视频

很久没更新文章了,起因是源于gpt,这东西太强大了,对博客造成了很大的冲击。后来想想,有些东西总结下来是对自己的沉淀,遂重新提起笔来...

2021年的时候,刚开始写ReactNative,当时最苦恼就是this的绑定的逻辑。对于this我最初的理解就是它本身,和之前OC中的self大概是一样的吧。开始的时候遇到个例,发现都乱了,可能是A的方法调用到B的方法上了,B调用刷新可能刷到C上了...

后来我发现了一个很省事的方法,直接用ES6箭头函数就没事儿了,或者在初始化的时候直接用bind方法先绑定上也不会出现啥问题,用了大半年,不亦乐乎。

直到我发现一本用了大篇幅讲 this 的书 《你不知道的JavaScript》,又搜到一个叫codewhy的前端讲师的 高级JS视频,才算彻底把这个搞明白了。

书上有一段话我觉得写的特别好,也送给正在读博客的你

遇到这样的问题时,许多开发者并不会深入思考为什么 this 的行为和预期的不一致,也不会试图回答那些很难解决但却非常重要的问题。他们只会回避这个问题并使用其他方法来达到目的。

从某种角度来说这个方法确实“解决”了问题,但可惜它忽略了真正的问题——无法理解 this 的含义和工作原理——而是返回舒适区。


一、核心

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


二、绑定规则

  • 2.1、默认绑定
  • 函数直接调用(不绑定任何对象或采用对象引用方式调用)
  • this指向: 严格模式-> window;非严格模式-> undefined
- 2.1.1、简单函数调用
function foo() {
  console.log(this); // window / undefined
}

foo();



- 2.1.2、函数调用链
function foo1() {
    console.log(this); // window / undefined
    foo2();
}

function foo2() {
    console.log(this); // window / undefined
    foo3();
}

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

foo();



- 2.1.3、将函数作为参数,传入到另一个函数中
function foo(func) {
  func();
}

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

foo(foo1);
  • 2.2、隐式绑定
  • 通过某个对象发起的函数调用
  • 隐式绑定丢失:应用默认绑定
- 2.2.1、通过对象调用函数
function foo() {
    console.log(this); // obj对象
}

var obj = {
    name:"qiangzi",
    foo: foo
}

obj.foo();



- 2.2.2、对象属性引用链
function foo() {
    console.log(this); // obj1对象
}

var obj1 = {
    name:"qiangzi-1",
    foo: foo
}

var obj2 = {
    name:"qiangzi-1",
    obj1: obj1
}

obj2.obj1.foo();



- 2.2.3、隐式绑定丢失(应用默认绑定)
function foo() {
    console.log(this);  // window / undefined
}

var obj1 = {
    name: "qinagzi-1",
    foo: foo
}

// 将obj1的foo赋值给foo2
var foo2 = obj1.foo;
foo2();
  • 2.3、显示绑定
  • 在分析 '隐式绑定' 时,必须在一个对象内部包含一个只想函数的属性,通过这个属性间接引用函数,从而把this间接(隐式)绑定到这个对象中。

  • 如果不想在对象内部包含函数引用,同是又希望对象上强制调用函数,该如何做呢?

  • 可以使用函数的 call(..) 和 apply(..) 方法,在调用函数同时,会将this绑定到这个传入的对象上。

  • 直接指定 this 的绑定对象,称为 显示绑定

  • call(..) 和 apply(..) ,解决之前 2.2.3 绑定丢失问题,可以使用 bind 硬绑定

- 2.3.1、call、apply
function foo() {
    console.log(this);
}

var obj1 = {
    name:"qiangzi-1",
    foo: foo
}

var obj2 = {
    name:"qiangzi-2",
    foo: foo
}
foo.call(obj1);   // obj1对象
foo.apply(obj2);  // obj2对象

* 从this绑定的角度来说,call和apply函数效果是一样的,区别体现在其他参数上。



- 2.3.2、硬绑定:显示绑定变种(解决绑定丢失问题),创建一个包裹函数,内部手动调用显示绑定
function foo() {
    console.log(this);
}

var obj = {
    name: "qiangzi"
}

var bar = function() {
    foo.call(obj);
}

bar(); // obj
setTimeout(bar, 100); // obj

// 硬绑定的bar不可能在修改它的this
bar.call(window); // obj



- 2.3.3、bind函数(包裹函数的应用)
function foo(something) { 
    console.log( this.a, something);
    return this.a + something; 
}

var obj = { a:2 };
var bar = function() {
    return foo.apply( obj, arguments );
};
var b = bar( 3 ); // 2 3 
console.log( b ); // 5
  • 2.4、new绑定

使用 new 来调用函数,会自动执行下面的操作

  1. 创建(或者说构造)一个全新的对象。
  2. 这个新对象会被执行 [[ 原型 ]] 连接。
  3. 这个新对象会绑定到函数调用的 this。
  4. 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象。
// 创建Person
function Person(name) {
  console.log(this); // Person {}
  this.name = name; // Person {name: "qiangzi"}
}

var p = new Person("qiangzi");
console.log(p);

三、绑定例外

  • 3.1、忽略显示绑定

在显示绑定中,传入一个null或undefined,这个显示绑定会被忽略,使用默认规则

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

var obj = {
  name: "qiangzi-01"
}

foo.call(obj); // obj对象
foo.call(null); // window
foo.call(undefined); // window

var bar = foo.bind(null);
bar(); // window
  • 3.2、间接函数引用

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

- 3.2.1、值赋值结果:(num2 = num1)的结果是num1的值;

var num1 = 100;
var num2 = 0;
var result = (num2 = num1);
console.log(result); // 100



- 3.2.2、函数赋值结果:赋值(obj2.foo = obj1.foo)的结果是foo函数;函数直接调用,默认绑定。

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

var obj1 = {
  name: "obj1",
  foo: foo
}; 

var obj2 = {
  name: "obj2"
}

obj1.foo(); // obj1对象
(obj2.foo = obj1.foo)();  // window

四、优先级

new绑定 > 显示绑定(bind)> 隐式绑定 > 默认绑定

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

4.1、默认绑定的优先级是四条规则中最低的
- 因为存在其他规则时,就会通过其他规则的方式来绑定this;


4.2、显示绑定 > 隐示绑定
function foo() {
    console.log(this);
}

var obj1 = {
    name: "obj1",
    foo: foo
}

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

// 隐式绑定
obj1.foo();  // obj1
obj2.foo();  // obj2

// 隐式绑定和显示绑定同时存在(根据打印结果,说明显式绑定优先级更高)
obj1.foo.call(obj2); // obj2, 
obj2.foo.call(obj1); // obj1, 



4.3、new绑定 > 隐式绑定
function foo() {
  console.log(this);
}

var obj = {
  name: "why",
  foo: foo
}

new obj.foo(); // foo对象, 说明new绑定优先级更高

五、箭头函数

箭头函数不使用 this 的四种标准规则,而是根据外层(函数或者全局)作用域来决定this。

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

- 5.1、箭头函数
function foo() {
    return () => {
        console.log(this.a);
    };
}

let obj1 = {
    a: 2
};

let obj2 = {
    a: 22
};

let bar = foo.call(obj1); // foo this指向obj1
bar.call(obj2); // 输出2🌟🌟🌟🌟  这里执行箭头函数 并试图绑定this指向到obj2



- 5.2、箭头函数-setTimeout 1
var obj = {
    data: [],
    getData: function() {
        setTimeout(() => { // 箭头函数向上层作用域查找, getData在调用时被绑定到了obj
            var res = ["abc", "cba", "nba"];
            this.data.push(...res);
        }, 1000);
    }
}

obj.getData();


- 5.2、箭头函数-setTimeout 2
var obj = {
    data: [],
    getData: () => {
        setTimeout(() => {
        console.log(this); // 箭头函数向上层作用域查找, getData还是箭头函数,再向上找到全局 window
      }, 1000);
    }
}

obj.getData();

六、总结

判断一个运行中函数的 this 绑定,直接找到这个函数的调用位置,顺序应用下面这四条规则来判断 this 的绑定对象。

1、由 new 调用:绑定到新创建的对象。 
2、由 call 或者 apply(或者 bind)调用:绑定到指定的对象。
3、由上下文对象调用:绑定到那个上下文对象。 
4、默认:在严格模式下绑定到 undefined,否则绑定到全局对象。


- 上下文对象调用
function foo(el) { 
    console.log( el, this.id ); 
}

var obj = { id: "awesome" };

// 调用 foo(..) 时把 this 绑定到 obj [1, 2, 3].forEach( foo, obj );
// 1 awesome 2 awesome 3 awesome

七、面试题

这些面试题来源于 CodeWhy 的公众号,虽然有些故意为之,但是也不失为检测对this绑定的理解程度,盖上答案自己去检测一下看看吧。

  • 7.1、面试题一
var name = "window";
var person = {
  name: "person",
  sayName: function () {
    console.log(this.name);
  }
};
function sayName() {
  var sss = person.sayName;
  sss(); 
  person.sayName(); 
  (person.sayName)(); 
  (b = person.sayName)(); 
}
sayName();



分析
function sayName() {
  var sss = person.sayName;
  // 独立函数调用,没有和任何对象关联
  sss(); // window
  // 关联
  person.sayName(); // person
  (person.sayName)(); // person
  (b = person.sayName)(); // window
}
  • 7.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.foo1.call(person2); 

person1.foo2();
person1.foo2.call(person2);

person1.foo3()();
person1.foo3.call(person2)();
person1.foo3().call(person2);

person1.foo4()();
person1.foo4.call(person2)();
person1.foo4().call(person2);



分析
// 隐式绑定,肯定是person1
person1.foo1(); // person1
// 隐式绑定和显示绑定的结合,显示绑定生效,所以是person2
person1.foo1.call(person2); // person2

// foo2()是一个箭头函数,不适用所有的规则
person1.foo2() // window
// foo2依然是箭头函数,不适用于显示绑定的规则
person1.foo2.call(person2) // window

// 获取到foo3,但是调用位置是全局作用于下,所以是默认绑定window
person1.foo3()() // window
// foo3显示绑定到person2中
// 但是拿到的返回函数依然是在全局下调用,所以依然是window
person1.foo3.call(person2)() // window
// 拿到foo3返回的函数,通过显示绑定到person2中,所以是person2
person1.foo3().call(person2) // person2

// foo4()的函数返回的是一个箭头函数
// 箭头函数的执行找上层作用域,是person1
person1.foo4()() // person1
// foo4()显示绑定到person2中,并且返回一个箭头函数
// 箭头函数找上层作用域,是person2
person1.foo4.call(person2)() // person2
// foo4返回的是箭头函数,箭头函数只看上层作用域
person1.foo4().call(person2) // person1
  • 7.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.foo1.call(person2)

person1.foo2()
person1.foo2.call(person2)

person1.foo3()()
person1.foo3.call(person2)()
person1.foo3().call(person2)

person1.foo4()()
person1.foo4.call(person2)()
person1.foo4().call(person2)




分析
// 隐式绑定
person1.foo1() // peron1
// 显示绑定优先级大于隐式绑定
person1.foo1.call(person2) // person2

// foo是一个箭头函数,会找上层作用域中的this,那么就是person1
person1.foo2() // person1
// foo是一个箭头函数,使用call调用不会影响this的绑定,和上面一样向上层查找
person1.foo2.call(person2) // person1

// 调用位置是全局直接调用,所以依然是window(默认绑定)
person1.foo3()() // window
// 最终还是拿到了foo3返回的函数,在全局直接调用(默认绑定)
person1.foo3.call(person2)() // window
// 拿到foo3返回的函数后,通过call绑定到person2中进行了调用
person1.foo3().call(person2) // person2

// foo4返回了箭头函数,和自身绑定没有关系,上层找到person1
person1.foo4()() // person1
// foo4调用时绑定了person2,返回的函数是箭头函数,调用时,找到了上层绑定的person2
person1.foo4.call(person2)() // person2
// foo4调用返回的箭头函数,和call调用没有关系,找到上层的person1
person1.foo4().call(person2) // person1
  • 7.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()()
person1.obj.foo1.call(person2)()
person1.obj.foo1().call(person2)

person1.obj.foo2()()
person1.obj.foo2.call(person2)()
person1.obj.foo2().call(person2)



分析
// obj.foo1()返回一个函数
// 这个函数在全局作用于下直接执行(默认绑定)
person1.obj.foo1()() // window
// 最终还是拿到一个返回的函数(虽然多了一步call的绑定)
// 这个函数在全局作用于下直接执行(默认绑定)
person1.obj.foo1.call(person2)() // window
person1.obj.foo1().call(person2) // person2

// 拿到foo2()的返回值,是一个箭头函数
// 箭头函数在执行时找上层作用域下的this,就是obj
person1.obj.foo2()() // obj
// foo2()的返回值,依然是箭头函数,但是在执行foo2时绑定了person2
// 箭头函数在执行时找上层作用域下的this,找到的是person2
person1.obj.foo2.call(person2)() // person2
// foo2()的返回值,依然是箭头函数
// 箭头函数通过call调用是不会绑定this,所以找上层作用域下的this是obj
person1.obj.foo2().call(person2) // obj

八、参考:

相关文章

网友评论

      本文标题:笔记-this绑定

      本文链接:https://www.haomeiwen.com/subject/ikjobdtx.html