1. this

this是动态的,第一步☝️是明白this既不指向函数自身,也不指向函数的词法作用域,实际上是在函数被调用的时候发生的绑定,指向什么❓完全取决于函数在哪里被调用

2. 调用位置

那么我们需要找到函数的调用位置,就是要寻找“函数被调用的位置”

  1. function baz() {
  2. // 当前调用栈是baz
  3. console.log("baz");
  4. bar();// --> bar的调用位置,是在foo的作用域
  5. }
  6. function bar() {
  7. // 当前调用栈是baz->bar
  8. console.log("bar");
  9. foo(); //--> foo的调用位置,是在bar的作用域
  10. }
  11. function foo(){
  12. // 当前调用栈是baz->bar->foo
  13. console.log("foo");
  14. }
  15. baz();//--> baz的调用位置,是在全局作用域


3. 绑定this的规则

默认绑定

  1. function foo(){
  2. console.log(this.a);
  3. }
  4. var a = 2;
  5. foo();//2

上面👆调用foo()时,this.a被解析成了全局变量a,函数调用时this触发了默认绑定了,this指向了全局对象window

⚠️:在严格模式下(use strict),则不能将全局对象用于默认绑定,为导致this绑定到undefined

隐式绑定

这个规则是根据函数调用位置是否存在上下文对象而生效

  1. "use strict";
  2. function foo(){
  3. console.log(this.a);
  4. }
  5. var obj1= {
  6. a:2,
  7. obj2:obj2
  8. }
  9. var obj2={
  10. a:42,
  11. foo:foo
  12. }
  13. obj1.obj2.foo(); //42

当foo()被调用的时候,函数前面被添加obj2的引用,这个时候就会引入了上下文的关系,this会绑定到这个上下文对象里面去

对象属性引用链中只有上一层或者说最后一层在调用位置起效

⚠️:当这个函数去赋值到一个变量,或者函数当作参数被传递之后,会触发隐式丢失

  • 函数符赋值一个变量,会丢失原本的this绑定,设置是重新赋值到一个对象的属性

    1. function foo() {
    2. console.log(this.a)
    3. }
    4. var obj= {
    5. a:2,
    6. foo:foo
    7. }
    8. var bar = foo.foo; // 函数赋值
    9. var a = "oops, global";
    10. bar(); // "oops, global"
  • 函数当参数传递,也会丢失原本的绑定

    1. function foo() {
    2. console.log(this.a)
    3. }
    4. var obj= {
    5. a:2,
    6. foo:foo
    7. }
    8. var a = "oops, global";
    9. setTimeout(obj.foo, 100); // "oops, global"

显示绑定

能够处理this的绑定指向,就要用到.call(…)和.apply(…)

  1. function foo(){
  2. console.log(this.a)
  3. }
  4. var obj={a:2};
  5. foo.call(obj); // 2

硬绑定,直接使用ES5提供的内置方法,Function.prototype.bind

  1. function foo(something){
  2. console.log(this.a, something);
  3. return this.a + something;
  4. }
  5. var obj= {
  6. a:2
  7. }
  8. var bar = foo.bind(obj);
  9. var b = bar(3); //2 3
  10. console.log(b);// 5

现在很多内置的函数(API),都提供了一个可选参数,通常被叫“上下文”,其作用和bind(…)一样,为了确保回调函数的this指向

  1. function foo(el){
  2. console.log(el, this.id);
  3. }
  4. var obj = {
  5. id: "awesome"
  6. }
  7. [0,1,2].forEach(foo, obj);// 0 awesome 1 awesome 2 awesome
  8. // 后面的可选参数,就是将this的绑定到要使用的对象上面👆去

new绑定

new操作符调用一个普通函数而已

  1. function Foo(a){
  2. this.a = a;
  3. }
  4. var bar = new Foo(2);
  5. console.log(bar.a); // 2

使用一个new操作符来调用函数,或者说发生构造函数的调用,执行以下的步骤:

  1. 创建(构造)一个全新的对象
  2. 这个新对象会被执行[Prototype]连接
  3. 这个新对象会绑定到函数调用的this
  4. 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象

4. 被忽略的this

如果你把null或者undefined作为this的绑定对象传入到call,apply或者bind,在调用的时候会被忽略掉,直接变成默认绑定规则,绑到全局对象里面去

主要应用在哪里,就是使用apply(…)来展开一个数组,并当作参数出入一个函数,当然了,现在有ES6的语法…介入,可以使用foo(…[2,3]),扩展数组

  1. function foo(a,b){
  2. console.log(`a:${a},b:${b}`);
  3. }
  4. // 把数组展开成参数
  5. foo.apply(null,[2,3]); //a:2,b:3
  6. // 使用bind进行柯里化
  7. var bar = foo.bind(null,2)
  8. bar(3); //a:2,b:3

⚠️:这种不好的地方是将this绑定到全局对象,后面涉及到修改全局对象会许多难以分析和追踪的bug

所以可以更安全的做法是创造一个空的非委托的对象,利用Object.create(null),这个比{}”更空”

  1. function foo(a,b){
  2. console.log(`a:${a},b:${b}`);
  3. }
  4. // 创建空的非委托对象(ø一个开发者默认的符号,Mac下 option+o)
  5. var ø = Object.create(null);
  6. // 把数组展开
  7. foo.apply(ø,[2,3]);//a:2,b:3

5. 软绑定

主要是解决硬绑定带来函数的灵活性,使用了硬绑定之后无法使用隐式绑定或者硬绑定了

所以主要思路是,封装个软绑定的函数,首先检查调用时的this,如果this绑定到全局对象或者undefined,那就把指定的默认对象obj绑定到this,否则不会修改this

6. this词法

箭头函数的this应用,它的this不受用在四种标准规则,只由外层的(函数或者全局)作用域来决定this

  1. function foo() {
  2. // 返回一个箭头函数
  3. return (a)=>{
  4. // this的继承自foo();
  5. console.log(this.a);
  6. }
  7. }
  8. var obj1={a:2};
  9. var obj2={a:3};
  10. var bar = foo.call(obj1);
  11. bar.call(obj2);//2, 不是3!!!!

箭头函数最常用于回调函数,例如,事件处理器或者定时器⏲

  1. function foo(){
  2. setTimeout(() => {
  3. // 这里的this在词法上面继承foo()
  4. console.log(this.a);
  5. },100)
  6. }
  7. var obj = {a:2};
  8. foo.call(obj);// 2

提个醒,当你需要编写this风格代码的时候,不要和词法作用域风格混在一起使用

  1. 只使用词法作用域的并完全抛弃错误的this风格代码
  2. 完全采用this风格,在必要时使用bind(…),尽量避免使用self=this或者箭头函数