this绑定

默认绑定

默认绑定,在不能应用其它绑定规则时使用的默认规则,通常是独立函数调用。

  1. function sayHi(){
  2. console.log('Hello,', this.name);
  3. }
  4. var name = 'YvetteLau';
  5. sayHi(); //Hello, YvetteLau

在调用Hi()时,应用了默认绑定,this指向全局对象(非严格模式下),严格模式下,this指向undefined,undefined上没有this对象,会抛出错误。

隐式绑定

函数的调用是在某个对象上触发的,即调用位置上存在上下文对象。

  1. function sayHi(){
  2. console.log('Hello,', this.name);
  3. }
  4. var person = {
  5. name: 'YvetteLau',
  6. sayHi: sayHi
  7. }
  8. var name = 'Wiliam';
  9. person.sayHi(); //Hello,YvetteLau

sayHi函数声明在外部,严格来说并不属于person,但是在调用sayHi时,调用位置会使用person的上下文来引用函数,隐式绑定会把函数调用中的this(即此例sayHi函数中的this)绑定到这个上下文对象(即此例中的person)

需要注意的是:对象属性链中只有最后一层会影响到调用位置。

隐式绑定有一个大陷阱,绑定很容易丢失(或者说容易给我们造成误导,我们以为this指向的是什么,但是实际上并非如此).

  1. function sayHi(){
  2. console.log('Hello,', this.name);
  3. }
  4. var person = {
  5. name: 'YvetteLau',
  6. sayHi: sayHi
  7. }
  8. var name = 'Wiliam';
  9. var Hi = person.sayHi;
  10. Hi(); //Hello,Wiliam

Hi直接指向了sayHi的引用,在调用的时候,跟person没有关系。可以理解为赋值给了Hi变量。

以下代码可以加深隐式绑定丢失的理解

  1. function sayHi(){
  2. console.log('Hello,', this.name);
  3. }
  4. var person1 = {
  5. name: 'YvetteLau',
  6. sayHi: function(){
  7. setTimeout(function(){
  8. console.log('Hello,',this.name);
  9. })
  10. }
  11. }
  12. var person2 = {
  13. name: 'Christina',
  14. sayHi: sayHi
  15. }
  16. var name='Wiliam';
  17. person1.sayHi();
  18. setTimeout(person2.sayHi,100);
  19. setTimeout(function(){
  20. person2.sayHi();
  21. },200);
  22. // Hello, Wiliam
  23. // Hello, Wiliam
  24. // Hello, Christina
  • 第一条输出很容易理解,setTimeout的回调函数中,this使用的是默认绑定,非严格模式下,执行的是全局对象
  • 第二条相当于是将person2.sayHi赋值给了一个变量,最后执行了变量,这个时候,sayHi中的this显然和person2就没有关系了。
  • 第三条虽然也是在setTimeout的回调中,但是我们可以看出,这是执行的是person2.sayHi()使用的是隐式绑定,因此这是this指向的是person2,跟当前的作用域没有任何关系。

    显式绑定

    通过call,apply,bind的方式,显式的指定this所指向的对象

    注意: 如果将null或者是undefined作为this的绑定对象传入call、apply或者是bind,这些值在调用时会被忽略,实际应用的是默认绑定规则。

  1. function sayHi(){
  2. console.log('Hello,', this.name);
  3. }
  4. var person = {
  5. name: 'YvetteLau',
  6. sayHi: sayHi
  7. }
  8. var name = 'Wiliam';
  9. var Hi = person.sayHi;
  10. Hi();//Hello, Wiliam
  11. Hi.call(person); || Hi.apply(person) //Hello, YvetteLau

显示绑定丢失

  1. function sayHi(){
  2. console.log('Hello,', this.name);
  3. }
  4. var person = {
  5. name: 'YvetteLau',
  6. sayHi: sayHi
  7. }
  8. var name = 'Wiliam';
  9. var Hi = function(fn) {
  10. fn();
  11. }
  12. Hi.call(person, person.sayHi);
  13. //上述代码执行结果可以理解为
  14. var Hi = function(fn) {
  15. //这里this被call改为了person
  16. //但是sayHi函数被window调用 所有this指向window
  17. //如果sayHi是个箭头函数 则往外找到this是指向person
  18. (function sayHi(){
  19. console.log('Hello,', this.name);
  20. })()
  21. }

输出的结果是 Hello, Wiliam. 原因很简单,Hi.call(person, person.sayHi)的确是将this绑定到Hi中的this了。但是在执行fn的时候,相当于直接调用了sayHi方法(记住: person.sayHi已经被赋值给fn了,隐式绑定也丢了),没有指定this的值,对应的是默认绑定。

如何修改为不不丢失? :fn.call(this);强制把this指向回去

new绑定

在javaScript中,构造函数只是使用new操作符时被调用的函数,这些函数和普通的函数并没有什么不同,它不属于某个类,也不可能实例化出一个类。任何一个函数都可以使用new来调用,因此其实并不存在构造函数,而只有对于函数的“构造调用”。

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

  1. 创建一个空对象,构造函数中的this指向这个空对象
  2. 这个新对象被执行 [[原型]] 连接
  3. 执行构造函数方法,属性和方法被添加到this引用的对象中
  4. 如果构造函数中没有返回其它对象,那么返回this,即创建的这个的新对象,否则,返回构造函数中返回的对象。
    1. function _new() {
    2. let target = {}; //创建的新对象
    3. //第一个参数是构造函数
    4. let [constructor, ...args] = [...arguments];
    5. //执行[[原型]]连接;target 是 constructor 的实例
    6. target.__proto__ = constructor.prototype;
    7. //执行构造函数,将属性或方法添加到创建的空对象上
    8. let result = constructor.apply(target, args);
    9. if (result && (typeof (result) == "object" || typeof (result) == "function")) {
    10. //如果构造函数执行的结构返回的是一个对象,那么返回这个对象
    11. return result;
    12. }
    13. //如果构造函数返回的不是一个对象,返回创建的新对象
    14. return target;
    15. }
    因此,我们使用new来调用函数的时候,就会新对象绑定到这个函数的this上。 ```javascript function sayHi(name){ this.name = name;

} var Hi = new sayHi(‘Yevtte’); console.log(‘Hello,’, Hi.name);

  1. > 输出结果为 Hello, Yevtte, 原因是因为在var Hi = new sayHi('Yevtte');这一步,会将sayHi中的this绑定到Hi对象上。
  2. <a name="uqpcC"></a>
  3. ## 箭头函数
  4. 箭头函数没有自己的this,它的this继承于外层代码库中的this。箭头函数在使用时,需要注意以下几点:
  5. 1. **函数体内的this对象,继承的是外层代码块的this。**
  6. 1. 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
  7. 1. 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
  8. 1. 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
  9. 1. 箭头函数没有自己的this,所以不能用call()、apply()、bind()这些方法去改变this的指向.
  10. ```javascript
  11. var obj = {
  12. hi: function(){
  13. console.log(this);
  14. return ()=>{
  15. console.log(this);
  16. }
  17. },
  18. sayHi: function(){
  19. return function() {
  20. console.log(this);
  21. return ()=>{
  22. console.log(this);
  23. }
  24. }
  25. },
  26. say: ()=>{
  27. console.log(this);
  28. }
  29. }
  30. let hi = obj.hi(); //输出obj对象
  31. hi(); //输出obj对象
  32. let sayHi = obj.sayHi();
  33. let fun1 = sayHi(); //输出window
  34. fun1(); //输出window
  35. obj.say(); //输出window
  1. obj.hi(); 对应了this的隐式绑定规则,this绑定在obj上,所以输出obj,很好理解。
  2. hi(); 这一步执行的就是箭头函数,箭头函数继承上一个代码库的this,刚刚我们得出上一层的this是obj
  3. 执行sayHi();这一步也很好理解,我们前面说过这种隐式绑定丢失的情况,这个时候this执行的是默认绑定,this指向的是全局对象window.
  4. fun1(); 按照箭头函数的this是继承于外层代码库的this就很好理解了。外层代码库我们刚刚分析了,this指向的是window
  5. obj.say(); 执行的是箭头函数,当前的代码块obj不能产生作用域不存在this,只能往上找,就找到了全局的this,指向的是window.

    测试题

    var number = 5;
    var obj = {
     number: 3,
     fn: (function () {
         var number;
         this.number *= 2;
         number = number * 2;
         number = 3;
         return function () {
             var num = this.number;
             this.number *= 2;
             console.log(num);
             number *= 3;
             console.log(number);
         }
     })()
    }
    var myFun = obj.fn;
    myFun.call(null);
    obj.fn();
    console.log(window.number);
    

    结果: 10 9 3 27 20

解析:

var number = 5;
var obj = {
    number: 3,
    fn: (function () {
          //1.自执行函数,被调用前已经执行,此时this指向window
          //且只有一次形成闭包return中函数一直在访问修改闭包中的number
        var number; //undefined
        this.number *= 2; //window.number*2=10
        number = number * 2; //undefined*2=NaN
        number = 3;//修改当前变量number=3

        return function () {
          //2.第一次: this指向window
          //3.第二次: this指向obj
            var num = this.number; //window.number=10,obj.number=3
            this.number *= 2;//window.number=20,obj.number=6
            console.log(num);//10 3
            number *= 3;//obj.number=9,obj.number=27这个number对应的闭包中的number;闭包中的number的此时是9
            console.log(number);
        }
    })()
}
var myFun = obj.fn;//
myFun.call(null);//传入null默认绑定为window

obj.fn();//让this指向obj
console.log(window.number);//20

案例

普通函数this指向

// 1.普通函数 - window
function fn(){};
fn()
fn.call(person);//person对象
//普通函数定义在可产生作用域的方法中且自调用 - undefined
class Person {
  constructor() {
    function fn() {
      console.log(this); //undefined
    }
    fn()

    this.fn1 = function () {
      console.log(this); //Preson 指向对象
    }
    this.fn1();
  }
}

// 2.对象的方法(谁调用我,我指向谁)
var person ={
  name:'张三',
  run:function(){
    console.log(this.name);
  }
}
person.run(); //张三
person.run.call({name:'李四'},30) //李四

// 3.构造函数 -指向构造函数本身
function Star(){
    console.log(this) // Star {}
}
var ldh = new Star() 
var zxy = new Star() 

// 4.绑定事件函数 - btn
btn.onclick = function(){}

// 5.定时函数 - window
//window.setTimeout 被window的所以放在哪都指向window
setTimeout(function(){},1000)

// 6.立即执行函数 参考上边第1点 - window
(function(){})()

箭头函数thsi指向

//箭头函数没有this,不能通过apply等修改this指向
//this 是静态的. this 始终指向函数声明时所在作用域下的 this 的值 (this会指向定义位置)
//1
var name='李四';

var all ={
  name:'张三',
  run1:function(){
    console.log(this.name);
  },
  run2:()=>{//all对象不能产生作用域, 此时this指向window
    console.log(this.name);
  }
}
all.run1(); //张三
all.run2(); //李四

//2
function fn (){
  console.log(this); //call改变this指向了obj ->{name: '张三'}
  return ()=>{
    // 没有this 往外找到obj ->{name: '张三'}
    console.log(this); 
    // 如果没有call改变this指向 则会找到window, 因为fn所在作用域就是window
    // 在没有call条件下 往fn添加this.name 输出指向都会是该值, 因为就是往window添加的,
    // 此时输出window.name也是该值, 因为fn所在作用域就是window
  }
}

const obj ={name:'张三'}
const resFn = fn.call(obj); //call会指向函数 此时resFn拿到的是fn函数的返回值
resFn(); //调用fn函数的返回值, 此时this被call改变为obj

//3
div.addEventListener("click", function(){
  //此时this指向div 因为div的click事件是普通函数
  //如果div的click函数也改为箭头函数 则this都往外找到window并指向window
    console.log(this);
  //定时器
  setTimeout(() => {
    //setTimeout原本this指向window 但箭头函数没有this,所有此处this往外找到div并指向它
    //如果div的click函数也改为箭头函数 则this都往外找到window并指向window
    console.log(this);//不使用箭头函数->window
  }, 2000);
});