JavaScript this机制 this的指向 this借用绑定的方法 改变this的指向 ES6箭头函数的this指向

版权声明:伊凡ED原创 https://blog.csdn.net/Ivan_ED/article/details/89158265

JavaScript 中的 this

本文介绍JavaScript中 所有的this问题 争取做到最全面


普通函数中 函数体中的 this 的指向方向是使用时所用的对象 并不是定义时所在的对象 换句话说 this的指向时谁再调用this所在的函数 this就指向谁 但在箭头函数中箭头函数的this是固定的 箭头如果在箭头函数中使用this 那这个this是指向这个函数定义时所在的对象


1. 一般函数中的this

依照上述 在一个普通函数内的 this 的指向方向是使用时所用的对象 

例如:

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

func()


// - ↑ 输出: window
// 因为该普通函数的调用位置为window 所以该函数的指向为 window

因为普通函数的调用位置为window 所以该函数的指向为 window

凡是没有定义在对象 构造函数或prototype中的函数 都是一般函数 一般函数的this都是全局对象Window

2. 构造函数中的this

function Person(name, age){
  this.name = name;
  this.age = age;
  this.func = function(){
  console.log(this);
  }
console.log(this);
}

var man  = new Person("伊凡ED","18") // ← 这时 函数体内的this指向的是构造函数这个对象
man.func() // ← 在这个构造的对象内使用 this 指向这个构造的对象

Person("伊凡ED","18") // ← 在作为一个一般函数调用时 这里指向window
func()  // ← 在外部使用内部函数时 指向window 因为时window在调用
// - ↑ 这里之所以可以调用 是应为 在执行在window 中调用 Preson()时 在window中创建了 func函数

上面创建的这是一个函数 作为new调用时 他是一个构造函数  在一个构造函数中的this指向的是 这个创建的对象

也就是new关键字 改变了函数内的this指向 让他指向了这个创建的对象

3. 返回值函数中的this

请仔细观察下面两个带有返回值的构造函数 

function Person(name, age){
  this.name = name;
  this.age = age;
  return {
    name:"伊凡AD",
    age:26
    };

};

var man  = new Person("伊凡ED","18") // 这时 函数体内的this指向的是构造函数这个对象
console.log(man.name,man.age) // 这里的输出却是返回值的内容 并不是我们在构建时传入的参数

// - ↑ 输出: 伊凡AD 26
function Person(name, age){
  this.name = name;
  this.age = age;
  return mane = "伊凡CD"

};

var man  = new Person("伊凡ED","18") // 这时 函数体内的this指向的是构造函数这个对象
console.log(man.name,man.age) // 同样的返回值函数 这里this指向的值又变成了我们构造时传入的值

// - ↑ 输出: 伊凡ED 18

发生上面实例的原因是 构造函数的返回值如果是基本数据类型 那返回值和得到的对象无关 但如果返回值为一个对象,那这对象就与这个返回值构成闭包 这个返回值对象被引用

4. 全局函数调用时的this指向

有时候 我们会写一个这样的函数

    function Person(name, age){
        this.name = "伊凡ED";
        this.age = 18;
        this.func = setTimeout(function(){
            console.log(this);  // ← 注意 这里的this指向的是 window
        },3000)
        return console.log(this);// ← 而这里的this指向的是这个构造对象
    }
    new Person("伊凡AD",26) 

// - ↑ 输出:Person {name: "伊凡ED", age: 18, func: 2}
//          Window

 因为这个内部函数中有一个定时器 而这个定时器是属于window的回调函数 所以相当于window 调用了这个方法 所以 指向是window这种情况不止会在计时器这一个单个方法会出现 凡是使用window 的自带方法 都会指向window 如果你在想在使用时让他指向这个构造对象 或者 指向某个特定的方法 就需要绑定一个this来用 下面我详细介绍this绑定的方法 

this 的八种绑定规则 四种绑定机制 以及绑定的方法 与 一些巧妙的技巧

如果不够完整 欢迎其他大神指出 并帮我补充

要介绍绑定this的方法前 我需要先说明 JavaScript 查找this的四种机制


this的绑定机制一共有四种 并且他们分有不同的优先级 

计算机在处理数据的时候也是又顺序的

1.首先JavaScript会查看 (new绑定) 如果是的话this绑定的是新创建的对象。 
2.如果不是 他会查看是否是 (显式绑定) call、apply 或者(硬绑定)bind  
3.如果还不是就去查看是否在某个上下文对象中调用(隐式绑定) 
4.最后还不是的话就去测试是否是在严格模式下运行的代码如果是 就绑定为undefined如果不是就绑定到全局对象window 

我会从最底的优先级 讲到最高的优先级 并在这里介绍他们的绑定方法 

1.this的默认绑定

默认绑定就是 没有其他绑定规则存在时默认的规则 默认规则是在Java中最常用的规则 在上面的代码中 充分的已经介绍了这种规则  但有时候 我们需要打破这种规则来完成我们的需求 我们便需要时用其他的方法 默认绑定的规则 是在优先级中最低的一种 

注意:在严格模式下 全局对象是无法使用默认绑定 执行时就会报出 undefied

2 this的显示绑定 call与apply

显示绑定主要是通过改变对象的prototype关联对象

    >1.function.call()

将函数的this指向切换到另一个对象当中 并且立即执行一次 

// ----- 创建了一个对象 -----    
var Person = {
        name :"伊凡ED",
        age : 18,
        func : function(){
            console.log(this.name,this.age);
        }
    };

// ↓ ----创建了一个新的对象 ------
    var newPerson = {
        name : "伊凡DJ",
        age : 20
    };
// ↓--- 使用第一个对象内的方法 ---
    Person.func();

// ↓ ---将第一个对象内的this指向 新创建的对象---
    Person.func.call(newPerson)
    
/* *   
 *  上面的两个输出 :  伊凡ED 18
 *                   伊凡DJ 20
 * */
 

call方法还可以进行传参     

    var Person = {
        name :"伊凡ED",
        age : 18,
        profession:"代码仔",
        func : function(profession){
            console.log(this.name,this.age,profession);
        } 
    };
    var newPerson = {
        name : "伊凡DJ",
        age : 20
    };
    Person.func.call(newPerson,"代码仔")

    // - ↑ 上面的输出:伊凡DJ 20 代码仔

    >2.function.apply[]

当你弄懂了上面的.call方法时 .apply方法自然也会了  他的用法与 .call相同 唯一不同的地方时在传参时 是一数组的形式进行传参的

    var Person = {
        name :"伊凡ED",
        age : 18,
        profession:"代码仔",
        func : function(profession){
            console.log(this.name,this.age,profession);
        } 
    };
    var newPerson = {
        name : "伊凡DJ",
        age : 20
    };
    Person.func.apply(newPerson,["代码仔"])

    // - ↑ 上面的输出:伊凡DJ 20 代码仔

    >3.function.bind

这种方法与上面的 .call .apply 方法稍有不同 他是被动的改变了this的指向 在使用bind的时候 返回的仍然是一个函数 他不会像.call .apply这两种方法自动执行 当你需要执行时 是需要像执行函数那样在执行 所以 他在传参时 也必须像函数那样执行 这种方法常用在像改变计时器这种全局指向时使用    bind方法传参的方法:

    var Person = {
        name :"伊凡ED",
        age : 18,
        profession:"代码仔",
        func : function(profession){
            console.log(this.name,this.age,profession);
        }
    };
    var newPerson = {
        name : "伊凡DJ",
        age : 20
    };

//  ↓ ------ 这是我知道的三种 使用bind传参的方法 -------
    Person.func.bind(newPerson,"代码仔")();
    Person.func.bind(newPerson)("菜鸡");
    var new_func = Person.func.bind(newPerson);
    new_func("小白博主")
    
    // - ↑ 上面的输出:
    /* *
     * 伊凡DJ 20 代码仔
     * 伊凡DJ 20 菜鸡
     * 伊凡DJ 20 小白博主
     */

3. 隐式绑定 

this的隐式绑定 刚才上面已经出现过很多次 但是并没有 详细的介绍 这里介绍this绑定方法时 详细介绍一下  隐式绑定 时函调用时是在某个对象上触发的 即调用时 位置上存在上下文的对象 这里写一个一目了然的隐式绑定

function Person_func() {
        console.log(this.name,this.age);
    }
    var Person = {
        name :"伊凡CD",
        age : 18,
        Person_func:Person_func

    };
   

   Person.Person_func()

// 输出结果:伊凡ED 18 

这种方法就是利用隐式绑定将 函数作为引用函数 添加到了Person这个对象上了

当然 有时候代码写多了 会变成下面这样 这种叫多层调用链关系

4.多层调用链

   function Person_func() {
        console.log(this.name,this.age);
    }
  var Person_1 = {
        name :"伊凡AD",
        age : 20,
        Person:Person

    };
    var Person_2 = {
        name :"伊凡ED",
        age : 18,
        Person_func:Person_func

    };
  
    Person_1.Person_2.Person_func()

// 输出结果:伊凡ED 18     

就像上面的代码 先引用获取 Person_1 内的对象 然后我又去获取了Person_2 内的对象 这里写的短 说不定我最后会获取Person_10、Person_100、Person_1000...... 但这里的绑定原则却是很简单的 this只会绑定在最后一层调用的下文对象上

5.隐式丢失

上面的隐式绑定不是很安全 有时稍微不注意就会发生下面的情况 我称这种情况叫做隐式丢失

    var name = "伊凡ED",
        age = 18
    function Person_func() {
        console.log(this.name,this.age);
    }
    var Person = {
        name :"伊凡FD",
        age : 20,
        Person_func:Person_func

    };
    var Person_1 = {
        name :"伊凡AD",
        age : 20,
        Person:Person

    };


    setTimeout(Person_1.Person.Person_func,3000)

// 输出结果:伊凡ED 18   

在这里使用了一个全局的方法 因为绑定优先级的原因 这里绑定的this又去了全局 window 你所绑定的隐式绑定 又没又作用了

6.找回隐式丢失

既然隐式绑定会因为优先级问题 被别的绑定方式带走 就必须找到一个优先级更高的方法把他带回来 

    var name = "伊凡GD",
        age = 18
    function Person_func() {
        console.log(this.name,this.age);
    }
    var Person = {
        name :"伊凡ED",
        age : 20,
        Person_func:Person_func

    };
    var Person_1 = {
        name :"伊凡AD",
        age : 20,
        Person:Person

    };


    setTimeout(Person_1.Person.Person_func.call(Person),3000)

// 输出结果:伊凡ED 18 

 找回丢失的隐式绑定 的方法是找到一个权重更高的绑定方法 并且重新选择 绑定this的方向 只要权重比带走绑定的优先级高的都可以使用 

7 .硬绑式

    function Person(name, age){
        this.name = "伊凡ED";
        this.age = 18;
        this.func = setTimeout(function(){
            console.log(this); 
        }.bind(this),3000)
        // ↑ 注意 这里的this本来指向的是 window 但使用bind方法后 指向的这个this是当前这个构造对象
        return console.log(this);// ← 而这里的this指向的是这个构造对象
    }
    new Person("伊凡AD",26)
    
/* * 
 * 上面的输出 :    Person {name: "伊凡ED", age: 18, func: 2}
 *                 Person {name: "伊凡ED", age: 18, func: 2}
 * */

根据上面的方法 同样可以去除其他被带走的 this方向 这里建议使用bind 去除计时器带走的this

8. new绑定

我把最上面的代码直接搬下来 来介绍最后一种绑定的方法 new一个对象 应该是最简单 最方便 最常用的方法 就像我介绍前面的构造对象 在new只需要 记得返回值函数this绑定的机制就可以 这里不多说 如果没有看懂 请从本文第一行开始看

function Person(name, age){
  this.name = name;
  this.age = age;
  this.func = function(){
  console.log(this);
  }
console.log(this);
}

var man  = new Person("伊凡ED","18") // ← 这时 函数体内的this指向的是构造函数这个对象
man.func() // ← 在这个构造的对象内使用 this 指向这个构造的对象

Person("伊凡ED","18") // ← 在作为一个一般函数调用时 这里指向window
func()  // ← 在外部使用内部函数时 指向window 因为时window在调用
// - ↑ 这里之所以可以调用 是应为 在执行在window 中调用 Preson()时 在window中创建了 func函数

 

ES6 箭头函数的this指向机制


这里不介绍ES6箭头函数  如果不了解箭头函数 可以查看我的 博文 查找ES6箭头函数 或自行百度

箭头函数的绑定是几位轻松自在的一种方式 箭头函数的this指向 只取决于外层的作用域 其外层的作用域是什么 他的指向就是什么 大大的解决了很多绑定的复杂性 箭头函数并不会绑定this 箭头函数不会改变this本来的绑定 但是通过second构造函数绑定的this将会被保留 在setInterval函数中 this依然指向我们新创建的start对象 这里我将 second函数中的this绑定到that 然后再每秒判断他们是否相等

    function second() {
        this.num = 0;
        this.timer = setInterval(() => {
            this.num ++;
            console.log(this.num);
        }, 1000);
        var that = this;
        this.timer = setInterval(() => {
            console.log(this === that);
        }, 1000);
    }
    var start = new second();

//  - ↑ 每秒输出秒数  每秒测试指向是否相等  1true 2true 保证全等

猜你喜欢

转载自blog.csdn.net/Ivan_ED/article/details/89158265
今日推荐