ES5this的绑定、绑定的优先级、ES6箭头函数中的this

110 阅读5分钟

一、this

1.1 ES5中,this是在生成执行上下文时产生的,this很专一,永远指向最后调用它的对象

1.2 this绑定的几种情况:

  • this的默认绑定---独立函数被调用

  • this的隐式绑定---对象用.调用方法

  • this的显示绑定---对象使用apply()、call()、bind()三个方法

  • this的new绑定---new方法调用构造函数

  • 匿名函数()里的this绑定到window

二、this的绑定--this很专一,永远指向最后调用它的对象!

2.1 this的默认绑定:发生在独立函数被调用的情况下。

  • 浏览器非严格模式下,一个独立的函数默认是绑定在window上的,那么,最后调用独立函数的对象就是全局对象windowthis绑定到window上。而在浏览器严格模式下,this指向的是undefined

独立函数被(window)调用this指向window

<script>
    // this 的默认绑定


    function fn() {
        console.log(this); //Window
    }
    fn()
        //  在浏览器中 独立函数调用,函数内部this表示window
</script>


<script>
    function fn() {
        console.log(this); //window
    }

    function gn() {
        console.log(this); //window
        fn(); // 独立函数调用
    }

    function kn() {
        console.log(this); //window
        gn(); // 独立函数调用
    }
    kn();  // 独立函数调
</script>
<script>
    let obj = {
        name: 'wc',
        fn: function() {
            console.log(this); //window
        }
    }
    let gn = obj.fn;
    gn(); // 独立函数调用
</script>
<script>
    function fn() {
        console.log(this); //Window
    }
    var obj = {
        name: 'wc',
        fn: fn
    }

    var gn = obj.fn;
    gn();
</script>
<script>
    function fn() {
        function gn() {
            console.log(this);
        }
        return gn; //返回一个地址 指向堆里的独立函数  
    }
    let kn = fn(); //用地址赋值
    kn(); // 独立函数调用
</script>

2.2 this的隐式绑定: 发生在对象用.调用方法的情况下。

  • this绑定到调用(此时也属于最后调用)方法的对象上。例如,对象a的方法b用.进行调用,形如:window.a.b();,此时最后调用方法b的就是这个对象a,此时this绑定到a

以方法的形式调用时,this指向调用方法的对象

<script>
    // this 的隐式绑定

    function fn() {
        console.log(this); //{name: 'wc', fn: ƒ}
    }

    let obj = {
        name: 'wc',
        fn: fn
    }
    obj.fn(); //不是独立函数的调用
    // 是通过obj打点去调用
    // fn中的this 表示的是 调用者obj
</script>
<script>
    var obj = {
        name: 'wc',
        running: function() {
            console.log(this.name + '在跑步...'); //wc在跑步...
        },
        coding: function() {
            console.log(this.name + '在敲代码...'); //wc在敲代码...
        }
    }
    obj.running(); //this的 隐式绑定
    obj.coding(); //this的 隐式绑定
    // obj调其用方法   this指向obj 
</script>
<script>
    let obj = {
        name: 'wc',
        fn: function() {
            console.log(this); //{name: 'xq', gn: ƒ}
        }
    };
    let obj2 = {
        name: 'xq',
        gn: obj.fn
    };
    obj2.gn(); //隐式绑定
</script>

2.3 this的显示绑定: 发生现在对象使用apply()、call()、bind()三个方法的情况。

  • apply()、call()、bind()三个方法的使用得到的结果略有不同,但是他们仨都有一个指定的this值。例如,apply(A,[参数,/])、bind(B,参数,/)、call(C,参数,/),此时指定的this值分别是A、B、C。call() 和 apply() 是预定义的函数方法。 两个方法可用于调用函数,两个方法的第一个参数必须是对象本身,bind()方法不可以直接调用函数,需要赋值到一个变量上,手动调用。
<script>
    //   this 的显示绑定
    // 在JS中函数有多重角色
    // 1)普通函数
    // 2)对象中的方法
    // 3)对象(JS中一切皆为对象Object)  函数也是对象,对象是属性的无序集合,内部有非常多的默认属性和方法。
    // 4) 函数也是类(构造函数/构造器),通常情况下首字母大写
    
    //要掌握三个方法: call()  apply()  bind()

    function fn() {
        console.log('fn....');
    }
    fn(); //'fn....'

    let obj = {
        // 对象中的函数称为方法
        running: function() {
            console.log('running...');
        }
    }

    function gn() {
        console.log('gn...');
    }
    gn.a = 1;
    gn.b = 2;
    gn.c = 3;
    console.log(gn.a); //1
    console.log(gn.b); //2
    console.log(gn.c); //3

    console.dir(gn); //ƒ gn(){a = 1,b = 2,c = 3} //以对象的方式打印函数 可以查看函数(对象)内部的属性
</script>

控制台输出见下图:

image.png

call()方法下this的绑定

<script>
    function fn(num1, num2) {

        console.log(this, num1 + num2);
    }
    let obj = {
            name: 'wc'
        }
        // 函数也是对象,call()是其内部的一个方法
        // 此方法可以让我们显示绑定this

    fn.call(obj); // {name: 'wc'} NaN
    //将fn的this 显示绑定到obj上,并执行
  
    fn.call(obj, 666, 111); //{name: 'wc'}  777
    // call传参:从第二个参数起,传递参数给函数
    fn(1, 2); //Window 3
</script>
    // call()的作用:
    // 1) 显示绑定this
    // 2) 让fn执行
    // 3) 可以传参

apply()方法下this的绑定

<script>
    function fn(num1, num2) {

        console.log(this, num1 + num2);
    }
    let obj = {
            name: 'wc'
        }
        // apply()方法示绑定this与call()方法相似,细微差别在apply()以[数组]的形式传参
        // 此方法可以让我们显示绑定this     



    fn.apply(obj); // {name: 'wc'} NaN
    //将fn的this 显示绑定到obj上,并执行
   
    fn.apply(obj, [666, 111]); //{name: 'wc'}  777   apply()与call()方法差别,参数是以数组类型传入
    // call传参:从第二个参数起,传递参数给函数
    fn(1, 2); //Window  3
</script>
 // apply()的作用:
    // 1) 显示绑定this
    // 2) 让fn执行
    // 3) 可以传参[以数组形式]

apply()方法下this的绑定

<script>
    function fn(num1, num2) {

        console.log(this, num1 + num2);
    }
    let obj = {
        name: 'wc'
    }
    let newFn = fn.bind(obj, 666, 111); 
   
    newFn();//{name: 'wc'} 777    手动调用
</script>
 // bind()作用:
    // 1) 显示绑定this 
    // 2) 可以传参 但不会让函数执行 call()、apply()会让函数执行,bind()会返回一个绑定了this的新函数
    // 3) bind()返回绑定this之后的新函数,需手动调用

String 、Number、undefined、null 原始数据类型作为this绑定对象的情况

<script>
    function fn() {
        console.log(this);
    }
    //当用显示绑定方法,把this绑定到一个字符串(String)上时:会把该字符串包装成一个String{}形式的对象
    fn.call('hello'); //String {'hello'}
    fn.apply('hello'); //String {'hello'}
    let b1 = fn.bind('hello');
    b1(); //String {'hello'}
    // String {'hello'}是一个对象
    // 

    //当用显示绑定方法,把this绑定到一个数值(Number)上时:会把该数值包装成一个Number{}形式的对象
    fn.call(1); // Number {1}
    fn.apply(1); // Number {1}
    let b2 = fn.bind(1);
    b2(); // Number {1}
    //



    //当用显示绑定方法,把this绑定到undefined上时:实际是绑到window上
    fn.call(undefined); //Window{}
    fn.apply(undefined); //Window{}
    let b3 = fn.bind(undefined);
    b3(); //Window{}
    // 


    //当用显示绑定方法,把this绑定到null上时:实际是绑到window上
    fn.call(null); //Window{}
    fn.apply(null); //Window{}
    let b4 = fn.bind(null);
    b4(); //Window{}
    //
    //控制台打印见下图↓
</script>

image.png

显示绑定总结

示绑定总结:
1) 对象call()方法  fn.call(obj,1,2)  显示绑定this 到obj上   让fn执行  能传参。
2)对象apply()方法  fn.apply(obj,[1,2])  显示绑定this 到obj上   让fn执行  能传参[以数组形式]。
3)对象bind()方法  fn.bind(obj,1,2)  显示绑定this 到obj上   不能让fn直接执行 而是返回一个新函数 能传参。

4)当用显示绑定方法,把this绑定到一个  字符串(String)上时:会把该字符串包装成一个String{}形式的对象。
5)当用显示绑定方法,把this绑定到一个  数值(Number)上时:会把该数值包装成一个Number{}形式的对象。
6)当用显示绑定方法,把this绑定到undefined上时:实际是绑到window上。
7)当用显示绑定方法,把this绑定到null上时:实际是绑到window上。

2.4this的new绑定: 发生在new方法调用构造函数的情况。

  • new方法会构造一个全新的对象,this绑定到构造出来全新的对象上
<script>
    // 使用构造函数调用函数
    // 如果函数调用前使用了 new 关键字, 则是调用了构造函数。
    // 这看起来就像创建了新的函数, 但实际上 JavaScript 函数是重新创建的对象:

    // 构造函数:
    function myFunction(arg1, arg2) {
        this.firstName = arg1;
        this.lastName = arg2;
        this.age = 18
    }

    // This    creates a new object
    var a = new myFunction("Li", "Cherry");
    console.log(a.lastName); // 返回 "Cherry"
    console.log(a.age); //18
</script>

<script>
    // 这就要说另一个面试经典问题:new 的过程了
    // 这里就简单的来看一下 new 的过程吧:
    // 伪代码表示:
    function myFunction(arg1, arg2) {
        this.firstName = arg1;
        this.lastName = arg2;
        this.age = 18
    }
    
    var a = new myFunction("Li", "Cherry");

    new myFunction {
        var obj = {age = 18};// new方法创建的新对象
        obj.__proto__ = myFunction.prototype;
        var result = myFunction.call(obj, "Li", "Cherry");
        return typeof result === 'obj' ? result : obj;
    }
    // 创建一个空对象 obj;
    // 将新创建的空对象的隐式原型指向其构造函数的显示原型。
    // 使用 call 改变 this 的指向
    // 如果无返回值或者返回一个非对象值, 则将 obj 返回作为新对象; 如果返回值是一个新对象的话那么直接直接返回该对象。

    // 所以我们可以看到, 在 new 的过程中, 我们是使用 call 改变了 this 的指向。
</script>

2.5 匿名函数里的this绑定

2.5.1 JS中的匿名函数:

定义:匿名函数顾名思义指的是没有名字的函数。

形如:
function (arguments){
console.log("我是匿名函数");//此时没有调用函数,所以不会执行函数体内的代码
}

2.5.2 匿名函数表达式:

函数名是函数(此函数是所有类型函数的总称包括匿名函数箭头函数等,函数也是对象)内部的私有属性,是与生俱来的,默认情况下是空字符串“”,在使用function声明函数时,从:想个适合的名称到落实到敲代码这个过程,其实是对函数name属性赋值的过程。

匿名函数的name属性是空字符串“”,当需要使用()来调用匿名函数时,需要将其转换成函数表达式的形式。

匿名函数放在小括号()里,使其成为函数表达式,形如:(function(){})或用等号赋值的形式nm=function(){},再用()放在函数表达式后面调用,形如:(function(){})()或nm()亦或采用setTimeout()等其他形式回调此时的匿名函数的里面的this永远指向window。

<script>
    console.log('------------------------一、函数内部属性------------------------------------');
    console.log('------1.1 普通函数↓------');
    // 声明普通函数
    function fn() {};
    console.dir(fn);


    console.log('------1.2 匿名函数↓------');
    // 匿名函数
    console.dir(function() {});


    console.log('------1.3 箭头函数↓------');
    // 箭头函数
    console.dir(() => {});



    console.log('------------------------二、匿名函数调用------------------------------------');


    console.log('------2.1 函数表达式A型↓------');
    let nm = function() {
        console.log('我是个匿名函数表达式,我被用()调用啦!');
    }
    console.dir(nm);
    // ()调用匿名函数表达式A型
    nm(); //我是个匿名函数表达式,我被用()调用啦!


    console.log('------2.2 函数表达式B型↓------');
    // ()立即调用匿名函数表达式B型
    (function() {
        console.log('我是个被()调用的立即执行函数表达式,大家都叫我立即执行函数IIFE。');
    })(); //我是个被()调用的立即执行函数表达式,大家都叫我立即执行函数IIFE。
</script>


控制台输出:↓

image.png

(function(arguments){
    console.log("我在调用匿名函数表达式")
})();

此时调用的函数表达式 也称为:立即执行的函数表达式(IIFE)。

2.5.3 匿名函数表达式内部的this绑定

简单的事件绑定,this指向事件源,但使用定时器setTimeout()或setInterval()回调匿名函数表达式,表达式内部的this指向window。不要忘记this永远指向最后调用它的对象。

<body>

    <button id="btn">点击</button>

</body>

<script>
    var a = 0;
    //获取元素
    var btn = document.querySelector("#btn");
    //绑定事件
    btn.onclick=function(){  //简单绑定事件中的匿名函数,事件绑定最好不要理解成调用匿名函数表达式
        console.log(this); //<button id="btn">点我</button>
    }
    
    btn.addEventListener('click',function (){
         setTimeout(function(){ //  setTimeout()回调匿名函数表达式  this指向window
             console.log(this.a); //0 
          },2000);
    })

 
</script>


<script>
    var a = 0;
    var fn = function() {
            return this.a;
        }
        //()调用匿名函数表达式 this指向window
    console.log(fn()); // 0


    setTimeout(function() {
        console.log(this.a); //0   定时器回调匿名函数表达式 this指向window
    }, 2000);
</script>

<script>
    //将匿名函数作为返回值
    var a = 0; //使用let不会声明不会挂在window上

    function fn() {
        return function() {
            let a = 1;
            this.a = "禁止套娃";
            console.log(a);
            return this.a;
        }
    }
    //调用匿名函数表达式
    console.log(fn()()); //禁止套娃

    console.log('------------------------------------');
    //或
    var box = fn();
    console.log(box()); //禁止套娃
    console.log(a); //禁止套娃
</script>

image.png

image.png

易混淆的匿名函数内部this的指向

  • 匿名函数作为对象方法,被用.调用,此时是隐式绑定,this指向最后调用它的对象
<script>
    var obj={
        name:"wc",
        age:100,
        fn:function(){
            return this.name+"今年"+this.age+"岁!";
        }
    };
    console.log(obj.fn());// 用括号()调用匿名函数fn // wc今年100岁!
    
</script>

记住: this永远指向最后调用它的对象!

三、this绑定改的优先级

  • 默认绑定(独立函数调用) 最低
  • 显示绑定优先级 > 隐式绑定
  • new 绑定优先级 > 隐式绑定
  • new 绑定优先级 > bind(显示绑定)

3.1显示绑定优先级高于隐式绑定

script>
    // 显示绑定优先级高于隐式绑定
    function fn() {
        console.log(this);
    }
    let obj = {
            name: 'wc',
            // 显示绑定  优先级高
            fn: fn.bind({
                name: 'xq'
            })
        }
        // 隐式绑定
    obj.fn(); //{name: 'xq'}
</script>

3.2显示绑定优先级高于隐式绑定

<script>
    // new优先级高于隐式绑定
    let obj = {
            name: 'wc',
            fn: function() {
                console.log(this);
            }
        }
        // 
    let res = new obj.fn(); //fn {}  构造的新对象
</script>

3.3 new 绑定优先级 > bind(显示绑定)

<script>
    // new绑定优先级高于bind()显示绑定
    function fn() {
        console.log(this);
    }
    // bind 返回一个绑定this之后的对象
    let gn = fn.bind({
        name: 'wc'
    })
    gn(); //{name: 'wc'}

    new gn(); //fn {}  新对象,
</script>

四、箭头函数中的this

我们先记住箭头函数的两个特性:

  • 箭头函数的this不指向箭头函数本身,而是指向它的上一层。

  • 箭头函数使用call()方法修改this指向无效。

<script>
    // 箭头函数中的this 需要往上找一层
    var gn = () => {
        console.log(this);
    }
    var obj = {
            name: 'wc'
        }
        // 显示绑定
    gn.call(obj) //window  此时call方法无效
</script>

<script>
    let fn = () => {
        console.log(this);
    }
    fn.call('hello'); //Window
    fn.call({}); //Window
</script>

<script>
    var obj = {
        name: 'wc',
        fn: () => {
            console.log(this);
        }
    }
    obj.fn(); //window
    //如不是箭头函数   就实现this隐式绑定,this指向obj
</script>
<script>
    setTimeout(() => {
        console.log(this);
    }, 2000); //Window
</script>

记住:箭头函数中的this指向它的上一层!

引用文章:

this、apply、call、bind - 掘金 (juejin.cn)

js中的匿名函数 - 掘金 (juejin.cn)

JS中的箭头函数与this - 掘金 (juejin.cn)