this指向详解+笔试题解析

this的出现一定程度上能使前端代码变得更有可读性和灵活性,特别是在触发事件和对象、类的封装中,秋招笔试期间做了很多关于this指向的笔试题,虽然都知道this指向问题很重要但真的能用大白话讲懂的又比较少。故记录下自己一路以来对this指向问题的理解,希望对大家有所帮助

this绑定规则

1 默认绑定

  1. 全局执行环境
    无论是否在严格模式下,在全局执行环境中(在任何函数体外部)this 都指向全局对象window
'use strict'
console.log(this); //window

  1. 普通的函数被独立调用
    非严格模式下,函数内部的this指向window
function f1(){
  return this;
}
console.log(f1()===window);//true

严格模式下,this指向为undefined

  function f1() {
        'use strict';
        return this;
      }
      console.log(f1()); //undefined

PS: 后续代码均默认为非严格模式


  1. 函数定义在对象内, 但独立调用
    独立函数调用:即直接调用fn(),函数fn的前面没有某个对象如obj.fn()且后面没有call、apply、bind改变this指向
 var obj = {
    bar: function () {
      console.log(this);
    },
  };

  var baz = obj.bar;
  baz(); //window
  obj.bar(); //非独立函数调用,this指向boj对象
function foo(fn){
    fn()
}
 var obj = {
    bar: function () {
      console.log(this);  //window
    },
  };
  foo(obj.bar)
  //this仍指向window,obj.bar返回的是一个函数,他真正的调用在foo函数里的fn(),无左边的隐式绑定也无右边call等显示绑定,为独立函数调用,指向window

  1. 立即执行函数
    立即执行函数this指向全局window
 var name = 'a';
  var obj = {
    name: 'b',
    func: (function () {
      console.log(this.name,this); //a,window
    })(),
  };

  1. 闭包this问题

网上的资料几乎都说闭包函数的this返回的为window,但本人不敢苟同,在不同的代码环境下闭包的this指向也会发生改变,下面为代码实践验证,希望各位笔试遇到闭包时不要盲目的认为this指向的均为window,分析代码的执行才可得出正确结论

  //函数里面嵌套函数的闭包
  function foo() {
    function bar() {
      console.log(this);
    }
    return bar;
  }

  var fn = foo();
  fn(); // this指向window,相当于独立函数调用

  var obj = {
    eating: fn,
  };
  obj.eating(); // 隐式绑定,this指向为obj对象

  1. 高阶函数
  function test1() {
    console.log(this);
  }

  function test2() {
    console.log(this);
    test1();
  }

  function test3() {
    console.log(this);
    test2();
  }
  test3();
  //this全部指向window,因为三个函数的调用本质都是独立函数调用

2. 隐式绑定

隐式绑定即通过某个对象对函数进行调用:
object.fn() // object对象会被js引擎绑定到fn函数中的this里面

隐式绑定的条件:在调用的对象内部有一个对函数的引用(如一个值为函数的属性)
正是通过这个引用间接的将this绑定到此对象上,若没有这样的引用,在进行函数调用时会报找不到该函数的错误

  function foo() {
    console.log(this);
  }
  var obj = {
    bar: foo,
  };
  obj.bar();//obj对象

 var obj1 = {
  name: "obj1",
  foo: function() {
    console.log(this)
  }
}
var obj2 = {
  name: "obj2",
  bar: obj1.foo
}
obj2.bar() //obj2
//obj2.bar返回的是打印this的函数,通过隐性绑定到了obj2

  function foo() {
    console.log(this); // obj2
  }
  var obj1 = {
    name: 'obj1',
    foo: foo,
    obj2: {
      name: 'obj2',
      foo: foo,
    },
  };
  obj1.obj2.foo(); //  obj1.obj2返回的是obj2对象,foo函数通过obj1调用
  //隐式绑定this指向技巧:看函数调用时最近的左边绑定对象

3.显示绑定

  1. 借助call或apply(apply为数组,call为参数列表)明确绑定this指向的对象即为显式绑定
  function foo() {
    console.log(this);
  }

  foo.call({ name: 'fang' }); //{name:'fang'}对象
  foo.apply(window); //window
  foo.apply('ddl'); //String {'ddl'}

  1. bind的绑定
    如果希望一个函数总是显示的绑定到某个对象上,可以使用bind方法(返回一个新的绑定函数而不会调用此函数)
  function foo() {
        console.log(this);
      }
      var obj = {
        name: 'fang',
      };
      var test = foo.bind(obj);
      test(); //obj对象

4. 内置函数的绑定

  1. setTimeout
    默认情况下定时器的this指向window
setTimeout(function() {
  console.log(this) // window
}, 2000)

改变定时器this指向的方法:箭头函数或bind

setTimeout(function() {
  console.log(this) // Number {123}
}.bind(123), 2000)

  1. 监听点击等事件
    当函数被用作事件处理函数时,它的 this 指向触发事件的元素

一些浏览器在使用非 addEventListener 的函数动态地添加监听函数时不遵守这个约定

var btn = document.querySelector("button")
btn.addEventListener("click", function() {
  console.log(this) //button按钮
})

  1. 数组forEach/map/filter/find传入函数后的第二个参数为this指向的对象
var names = ["a", "b", "c"]
names.forEach(function(item) {
  console.log(this) //window
})
names.map(function(item) {
  console.log( this) //String {'阿巴阿巴'}
}, "阿巴阿巴")

5. new的绑定

当一个函数用作构造函数时(使用new关键字),它的this被绑定到正在构造的新对象

function Person(name) {
  this.name = name
  console.log(this); 
}
var p1 = new Person("fang")//Person {name: 'fang'}
var p2 = new Person("xiao")//Person {name: 'xiao'}

若构造函数返回值为引用类型object或数组,则与this绑定的默认对象被丢弃,返回的为构造函数中的return值

function C(){
  this.a = 37;
}
var o = new C();
console.log(o.a); // 37

function C2(){
  this.a = 37;
  return {a:38};
}

var o2 = new C2();
console.log(o2.a); // 38,返回{a:38};

function C3(){
  this.a = 37;
  return [1,2,3];
}
var o3 = new C2();
console.log(o3); // [1,2,3]

6. 不同绑定规则的优先级

new绑定 >显示绑定(bind优先级高于apply/call) >隐式绑定 (obj.foo()) > 默认绑定(独立函数调用)
PS: new绑定优先级高于bind, 且new绑定和call、apply不允许同时使用

代码测试:

  1. bind高于默认绑定
  function foo() {
    console.log(this);  //String {'abc'}
  }
  var obj = {
    foo: bar,
  };
  var test = obj.foo.bind('cba');
  test();
  //等价于
  //var bar = foo.bind('abc');
  //obj.foo();

  1. new优先级高于bind
  function foo() {
    console.log( this)
  }
  var bindFn = foo.bind("aaa")
  bindFn() //String {'aaa'}
  new bindFn() //foo {}

  1. bind优先级高于apply/call
   function foo() {
        console.log( this) //String {'aaa'}
      }
      var bindFn = foo.bind("aaa")
      bindFn.call("bbb")

7. this绑定规则的特殊情况

  1. 如果在显示绑定apply/call/bind中,当传入null或者undefined时,显示绑定会失效而自动将this绑定成全局对象
  function foo() {
    console.log(this);
  }
  foo.apply(null); //window
  foo.apply('abc'); //abc
  foo.apply({}); //{}
  foo.apply(undefined); //window
  var bar = foo.bind(null);
  bar(); //window

  1. 间接函数引用问题(obj2.foo = obj1.foo)

赋值(obj2.foo = obj1.foo)的结果是foo函数,赋值完foo函数被直接调用(独立函数调用),使用默认绑定this指向window

 var obj1 = {
      name: 'obj1',
      foo: function () {
        console.log(this);
      },
    };

    var obj2 = {
      name: 'obj2',
    };
    // obj2.foo = obj1.foo
    // obj2.foo() //{name: 'obj2', foo: ƒ}
     (obj2.foo = obj1.foo)(); //window

箭头函数的this

箭头函数是根据外层作用域来决定this,不使用前面提到的this的标准规则,只与箭头函数所处的位置环境有关,在全局代码中,this将被设置为全局对象
1.

如果将this传递给call、bind、或者apply来调用箭头函数,它将被忽略。不过你仍然可以为调用添加参数,不过第一个参数(thisArg)应该设置为null

var foo = () => {
  console.log(this)
}
foo()
var obj = {foo: foo}
obj.foo()
foo.call("abc")
foo.bind(123)()
//打印出来的this均为window,因为箭头函数使用call、apply、bind显示绑定与obj.foo()隐式绑定等会失效

2.
 var obj = {
    bar: function () {
      var x = () => console.log(this);
      return x;
    },
  };
  var fn = obj.bar()(); //this指向obj
  //x所处的环境为bar函数内,故箭头函数的this指向bar函数的this,通过obj隐式绑定将bar函数的this指向obj

但是注意如下情况,如果只是引用 obj 的方法而没有调用它, 那么全局调用箭头函数后,this 指向 window。因为fn2即为bar函数,通过fn2()()独立函数调用将bar的this指向了window

 var fn2 = obj.bar; 
 fn2()() //this指向window

类的this

类内部总是严格模式,和其他普通函数一样,类的方法中this 的值取决于它们如何被调用

static 关键字用来定义一个类的一个静态方法,调用静态方法不需要实例化该类且不能通过一个类实的例调用静态方法,它们只是类自身的属性。

 class Person {
        constructor(name, age) {
          this.name = name;
        }
        // 实例方法
        running() {
          console.log(this.name + ' running');
        }
        //静态方法只能为类自身的属性
        static displayName = '存在即合理';
        static add(a, b) {
          return a + b;
        }
      }
      var p1 = new Person('fang');
      var p2 = new Person('阿巴阿巴');
      console.log(p1.displayName); //undefined
      console.log(Person.displayName); //存在即合理
      console.log(Person.add(1, 2)); //3
      p1.running(); //fang  running
      p2.running(); //阿巴阿巴 running

笔试题解析

var name = "window";
var person = {
  name: "person",
  sayName: function () {
    console.log(this.name);
  }
};

function sayName() {
  var foo = person.sayName;
  foo(); // window:foo为sayName函数,无左边隐式绑定右边显示绑定,为独立函数调用
  person.sayName(); // person: 隐式绑定
  (person.sayName)(); // person: 隐式调用
  (b = person.sayName)();
  // window, this绑定规则的特殊情况: 间接函数引用,赋值表达式(独立函数调用)
}
 sayName();

var name = 'window'
var obj1 = {
  name: 'obj1',
  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 obj2 = { name: 'obj2' }
obj1.foo1(); // obj1(隐式绑定)
obj1.foo1.call(obj2); // obj2(显示绑定优先级大于隐式绑定),即函数左右两边都有规则时看右边的显示绑定规则

obj1.foo2(); // window,foo2是箭头函数,this指向为上层作用域obj1的this,即全局window
obj1.foo2.call(obj2); // window,箭头函数中call失效

obj1.foo3()(); 
// window(独立函数调用),obj1.foo3()返回函数ƒ () {console.log(this.name)},之后直接进行独立函数调用
obj1.foo3.call(obj2)();
 // window(独立函数调用),obj1.foo3.call(obj2)改变的是foo3的this指向,但返回的仍为打印this.name的函数,之后直接加小括号进行独立函数调用
obj1.foo3().call(obj2); 
// obj2,将返回的函数使用显式绑定调用,this指向显式绑定的obj2

obj1.foo4()(); // obj1(箭头函数不绑定this, 上层作用域foo4的this是obj1)
obj1.foo4.call(obj2)(); // obj2(上层作用域foo4的this被显示的绑定到了obj2)
obj1.foo4().call(obj2);
 // obj1,obj1.foo4()返回的是个箭头函数不可使用call,根据上层作用域foo4的this指向obj1

  1. 做题时注意看清call、apply等显示绑定的是哪个函数
  var name = 'window';

  function Person(name) {
    this.name = name;
    this.obj = {
      name: 'obj',
      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 person1 = new Person('person1');
  var person2 = new Person('person2');

  person1.obj.foo1(); //隐式绑定: obj
  person1.obj.foo1.call(person2); // 显式绑定: person2

  person1.obj.foo2(); 
  // foo2为箭头函数,this指向在上层作用域查找,即obj的this指向:person1
  person1.obj.foo2.call(person2); // call失效,在上层作用域查找: person1

  person1.obj.foo3()(); 
  //  window(独立函数调用), person1.obj.foo3()返回打印name的函数直接调用
  person1.obj.foo3.call(person2)(); 
  // window(独立函数调用),call改变的是foo3的this指向,不是返回函数的this指向
  person1.obj.foo3().call(person2); 
  // 显式绑定: person2,call改变的为返回函数的this指向

  person1.obj.foo4()(); 
  //obj, 箭头函数找上层作用域foo4的this指向为obj
  person1.obj.foo4.call(person2)();
   //箭头函数在上层作用域查找foo4的this指向显示绑定为: person2
  person1.obj.foo4().call(person2); 
  // 上层作用域查找:obj,call无法改变箭头函数的this

后记:希望自己这篇文章能帮大家对this指向有更深的理解,若能消化完这篇文章,以后遇到的笔试面试题中this指向就基本没什么大问题,今年的秋招大环境教我重新做人,去年工作室的各位师兄师姐轻轻松松手握几个大厂offer,今年作为本科生的我在一群硕士大佬间苦苦挣扎,希望各位师弟师妹想找工作的话尽早准备,只想说一声:菜菜,捞捞,在池子里要泡发了/(ㄒoㄒ)/~~


参考资料

MDN this

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值