• this用法总结


    1.常规下this的指向

    this关键字可以用在构造函数之中,表示实例对象。除此之外还可以用在别的场合。但是不管是什么场合,this都有一个共同点:它总是返回一份对象

    1.1 全局环境中的this

    函数在浏览器全局环境下被简单调用,在非严格模式下 this 指向 window,在通过 use strict 指明严格模式的情况下指向 undefined
    例1:

    function f1() {
        console.log(this);
    }
    
    function f2() {
        'use strict'
        console.log(this);
    }
    
    f1()     // window or global
    f2()     // undefined
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    例2:

    const foo = {
        bar : 10,
        fn : function(){
            console.log(this); 
            console.log(this.bar); 
        }
    }
    var fn1 = foo.fn;
    fn1();
    
    // window or global
    // undefined
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    虽然fn函数在foo对象中作为该对象的一个方法,但是赋值给fn1之后,fn1仍然是在window的全局环境下执行的。因此this指向的还是window

    例3:

    const foo = {
        bar : 10,
        fn : function(){
            console.log(this); 
            console.log(this.bar); 
        }
    }
    foo.fn();   
    
    
    // { bar: 10, fn: [Function: fn] }
    // 10
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这里,this指向调用它的对象,在foo.fn() 语句中,this指向的是foo对象

    1.2 上下文对象调用中的this

    一般通过上下文对象调用函数时,函数体内的 this 会被绑定到该对象上。
    例4:

    const student = {
        name: 'zhangsan',
        fn: function () {
            return this;
        }
    }
    console.log(student.fn() === student); 
    
    
    // true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    this 指向当前的对象student

    在嵌套关系中,this指向最后调用它的对象
    例5:

    const student = {
        name: 'zhangsan',
        son: {
            name: 'zhangxiaosan',
            fn: function () {
                return this.name
            }
        }
    }
    console.log(student.son.fn()); 
    
    // zhangxiaosan
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    高阶-例6:

    const o1 = {
        text: 'o1',
        fn: function () {
            return this.text;
        }
    }
    
    const o2 = {
        text: 'o2',
        fn: function () {
            return o1.fn();
        }
    }
    
    const o3 = {
        text: 'o3',
        fn: function () {
            var fn = o1.fn;
            return fn();
        }
    }
    
    console.log(o1.fn()); 
    console.log(o2.fn());
    console.log(o3.fn());
    
     // o1
     // o1
    // undefined
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    o1.fn() this指向调用它的对象,打印为o1;
    o2.fn() this 指向为o1,打印为o1;
    o3.fn() 这里是将o1.fn 赋值给fn,并return,所以在调用的时候,相当于全局调用function () { return this.text; },并不是以对象的形式调用,this指向window,所以打印为undefined

    1.3 this指向绑定事件的元素

    问题:在一个div节点的时间函数内部,有一个局部的callback方法,我们希望callback方法内部的this指向div节点

    <div id="div1">我是一个div</div>
    
    
    window.id = 'window';
    document.getElementById('div1').onclick = function(){
      console.log('this1',this.id); 
      const callback = function(){
        console.log(this.id); 
      }
      callback();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这里插入图片描述

    由于callback作为普通函数被调用,所以this指向为window

    解决: 通过变量保存的方式

    window.id = 'window';
    document.getElementById('div1').onclick = function(){
      console.log('this1',this.id); // div1
      const that = this; // 保存当前 this 的指向
      const callback = function(){
        console.log(that.id); 
      }
      callback();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述

    1.4 箭头函数的this指向

    箭头函数中的this 指向始终是指向的外层作用域(箭头函数没有自己的this, 它的this是继承而来; 默认指向在定义它时所处的对象(宿主对象),此处指父级作用域)

    var x = 20;
    const obj = {
        x: 10,
        test: () => {
            console.log(this); // {}
            console.log(this.x); // undefined
        }
    }
    obj.test();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述

    箭头函数的 this 指向与普通函数不一样,它的 this 指向始终是指向的外层作用域。所以这里的 this 实际上是指向的全局对象。

    var name = "JavaScript";
    const obj = {
        name: "PHP",
        test: function () {
            const i = function () {
                console.log(this.name);
                // i 是以函数的形式被调用的,所以 this 指向全局
                // 在浏览器环境中打印出 JavaScript,node 里面为 undeifned
            }
            i();
        }
    }
    obj.test(); // JavaScript
    
    // 改为箭头函数:
    
    var name = "JavaScript";
    const obj = {
        name : "PHP",
        test : function(){
            const i = ()=>{
                console.log(this.name);
                // 由于 i 为一个箭头函数,所以 this 是指向外层的
                // 所以 this.name 将会打印出 PHP
            }
            i();
        }
    }
    obj.test();// PHP
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    另外箭头函数 不能作为构造函数

    const Test = (name, age) => {
        this.name = name;
        this.age = age;
    };
    const test = new Test("xiejie", 18);
    // TypeError: Test is not a constructor
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2. 改变this指向

    2.1 call - Function.prototype.call( )

    call方法可以指定this指向(即函数执行时所在的作用域),然后在指定的作用域中执行函数。

    // 函数.call(对象)
    fun.call(thisArg, arg1, arg2, ...)
    
    • 1
    • 2
    2.1.1 call的第一个参数

    thisArg:在 fun函数运行时指定的 this值 。如果参数为空或 null、undefind,则默认传参全局对象,同时值为原始值(数字,字符串,布尔值)的 this会指向该原始值的自动包装对象。
    例1:改变this指向

    var obj = {};
    var f = function(){
    	return this;
    };
    console.log(f() === window);  
    console.log(f.call(obj) === obj) 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    执行f() 时,因为在全局 环境下执行,所以this指向window,通过call 传入第一个参数,call前面的函数执行时,this指向为第一个参数对象

    例2:this指向传入undefined 、null

    var n = 123;
    var obj = { n: 456 };
    
    function a() {
      console.log(this.n);
    }
    
    a.call() // 传入空,指向全局 ,123
    a.call(null) //传入null 指向全局, 123
    a.call(undefined) //传入undefined, 123
    a.call(window) // 123
    a.call(obj) //传入obj,this指向obj 456
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    例3: 传入Number类型,this指向包装对象

    var f = function () {
      return this;
    };
    
    f.call(5); // Number {[[PrimitiveValue]]: 5}
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    2.1.2 call接受多个参数

    第一个参数是 this 指向的对象,之后的是函数回调所需的参数
    例4:

    function add(a, b) {
      return a + b;
    }
    
    add.call(this, 1, 2) // 3
    
    • 1
    • 2
    • 3
    • 4
    • 5
    2.1.3 调用对象的原生方法

    hasOwnProperty 该方法是查看一个对象是否有某一个属性或者方法
    这个属性或者方法必须是自身就有的,而不是继承而来

    var obj = {};
    obj.hasOwnProperty('toString') // false
    
    // 覆盖掉继承的 hasOwnProperty 方法
    obj.hasOwnProperty = function () {
      return true;
    };
    obj.hasOwnProperty('toString') // true
    
    Object.prototype.hasOwnProperty.call(obj, 'toString') // false
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    上面代码中 hasOwnPropertyobj 继承来的方法,用来判断对象是否包含自身特点(非继承)属性,但是 hasOwnProperty 并不是保留字,如果被对象覆盖,会造成结果错误。

    call 方法可以解决这个问题,它将 hasOwnProperty 方法的原始定义放到 obj 对象上执行,这样无论 obj 上有没有同名方法,都不会影响结果。

    2.2 apply - Function.prototype.apply( )
    func.apply(thisValue, [arg1, arg2, ...])
    
    • 1
    2.2.1 apply 与call 的区别

    相同点:

    • apply与call的作用类似,也是改变this指向,然后调用函数
    • 第一个参数也是 this 所要指向的那个对象,如果设为 nullundefined,则等同于指定全局对象。

    不同点:

    • apply接受数组作为 函数执行时的参数,在 call 方法中必须一个个添加,但是在 apply 方法中,必须以数组形式添加,该数组的所有成员依次作为参数,传入原函数。
    function f(x, y){
      console.log(x + y);
    }
    
    f.call(null, 1, 1) // 2
    f.apply(null, [1, 1]) // 2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    2.2.2 利用apply传入数组的特性,实现一些小功能

    1. 输出数组的最大值

    var a = [24,30,2,33,1]
    Math.max.apply(null,a)  //33
    
    • 1
    • 2

    2. 将数组的空元素转化成undefined
    意义:数组的 forEach 方法会跳过空元素,但是不会跳过 undefined。undefined可以通过forEach循环出来

    var a = ['a', , 'b'];
    
    function print(i) {
      console.log(i);
    }
    
    a.forEach(print)
    // a
    // b
    
    Array.apply(null, a).forEach(print)
    // a
    // undefined
    // b
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    3. 配合数组的slice方法,实现类数组转化为真正的数组

    Array.prototype.slice.apply({0: 1, length: 1}) // [1]
    Array.prototype.slice.apply({0: 1}) // []
    Array.prototype.slice.apply({0: 1, length: 2}) // [1, undefined]
    Array.prototype.slice.apply({length: 1}) // [undefined]
    
    • 1
    • 2
    • 3
    • 4

    将类似数组的对象转化为数组

    2.3 bind - Function.prototype.bind( )
    f.bind(obj)
    
    • 1

    bind用于将函数体内的this绑定到某个对象,然后返回一个新函数:
    例:
    问题:

    var d = new Date();
    d.getTime() // 1481869925657
    
    var print = d.getTime;
    print() // Uncaught TypeError: this is not a Date object.
    
    • 1
    • 2
    • 3
    • 4
    • 5

    执行print时,将getTime赋值给print,相当于全局调用,此时this指向window

    使用bind解决:

    var print = d.getTime.bind(d);
    print() // 1481869925657
    
    • 1
    • 2

    通过bind,返回一个新函数,这个新函数this被绑定到了d上

    2.3.1 bind传入一个参数时

    bind接收的参数就是所要绑定的对象
    常规绑定:

    var counter = {
      count: 0,
      inc: function () {
        this.count++;
      }
    };
    
    var func = counter.inc.bind(counter);
    func();
    counter.count // 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    bind中传入对象为counter,所以bind前函数执行时this指向counter

    绑定到其他对象:

    var counter = {
      count: 0,
      inc: function () {
        this.count++;
      }
    };
    
    var obj = {
      count: 100
    };
    var func = counter.inc.bind(obj);
    func();
    obj.count // 101
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    传入的对象为obj,所以执行bind前的函数时,函数指向obj

    2.3.1 bind传入多个参数时

    bind 接受多个参数时,除了第一个参数,其他参数会绑定到原函数的参数

    var add = function (x, y) {
      return x * this.m + y * this.n;
    }
    
    var obj = {
      m: 2,
      n: 2
    };
    
    var newAdd = add.bind(obj, 5);
    newAdd(5) // 20
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    bind的第一个参数为obj,所以将bind前函数的this指向为obj,后面的参数会作为调用add方法时的参数传入

    2.3.2 第一个参数为null或undefined时

    bind 方法的第一个参数是 nullundefined,等于将 this 绑定到全局对象,函数运行时 this 指向顶层对象(浏览器为 window)。

    function add(x, y) {
      return x + y;
    }
    
    var plus5 = add.bind(null, 5);
    plus5(10) // 15
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    函数add内没有使用this,bind方法主要目的是绑定x,y参数,每次运行plus5时,只需要传入另一个参数y就行了。

    2.3.3 bind使用时的注意点(即:使用bind的几个场景)
    2.3.3.1 bind每一次返回一个函数

    事件监听时:
    由于每次运行时,就会返回一个新函数。所以上面的代码click事件绑定bind方法会生成一个匿名函数。导致无法取消绑定

    element.addEventListener('click', o.m.bind(o));
    
    //取消绑定时
    element.removeEventListener('click', o.m.bind(o));
    
    • 1
    • 2
    • 3
    • 4

    解决:

    var listener = o.m.bind(o);
    element.addEventListener('click', listener);
    //  ...
    element.removeEventListener('click', listener);
    
    • 1
    • 2
    • 3
    • 4
    2.3.3.2 将包含 this 的方法直接当作回调函数
    var counter = {
      count: 0,
      inc: function () {
        'use strict';
        this.count++;
      }
    };
    
    function callIt(callback) {
      callback();
    }
    // 写法1: callIt(counter.inc())
    callIt(counter.inc.bind(counter));
    counter.count // 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    使用如上写法1 的方式传入函数,调用时,this会指向window,相当于全局调用。解决方式就是通过bind绑定this

    追问:某些数组方法接收的函数中的this指向

    var obj = {
      name: '张三',
      times: [1, 2, 3],
      print: function () {
        this.times.forEach(function (n) {
          console.log(this.name);
        });
      }
    };
    
    obj.print()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    如上代码没有任何输出,因为this指向为window

    解决:

    obj.print = function () {
      this.times.forEach(function (n) {
        console.log(this.name);
      }.bind(this));
    };
    
    obj.print()
    // 张三
    // 张三
    // 张三
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    *2.3.3.3 结合call的使用

    利用 bind 方法,可以改写一些 JavaScript 原生方法的使用形式,以数组的 slice 方法为例。

    [1, 2, 3].slice(0, 1) // [1]
    // 等同于
    Array.prototype.slice.call([1, 2, 3], 0, 1) // [1]
    
    • 1
    • 2
    • 3

    这样做的本质是在 [1, 2, 3] 上面调用 Array.prototype.slice 方法,因此可以用 call 方法表达这个过程,得到同样的结果。
    call 方法实质上是调用 Function.prototype.call 方法,因此上面的表达式可以用 bind 方法改写。

    var slice = Function.prototype.call.bind(Array.prototype.slice);
    slice([1, 2, 3], 0, 1) // [1]
    
    • 1
    • 2

    上面代码的含义就是,将 Array.prototype.slice 变成 Function.prototype.call 方法所在的对象,调用时就变成了 Array.prototype.slice.call。类似的写法还可以用于其他数组方法。

    var push = Function.prototype.call.bind(Array.prototype.push);
    var pop = Function.prototype.call.bind(Array.prototype.pop);
    
    var a = [1 ,2 ,3];
    push(a, 4)
    a // [1, 2, 3, 4]
    
    pop(a)
    a // [1, 2, 3]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    如果再进一步,将 Function.prototype.call 方法绑定到 Function.prototype.bind 对象,就意味着 bind 的调用形式也可以被改写。

    function f() {
      console.log(this.v);
    }
    
    var o = { v: 123 };
    var bind = Function.prototype.call.bind(Function.prototype.bind);
    bind(f, o)() // 123
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    上面代码的含义就是,将 Function.prototype.bind 方法绑定在 Function.prototype.call 上面,所以 bind 方法就可以直接使用,不需要在函数实例上使用。

    2.4 手写call、bind、apply
    • 原理或者手写类题目,结题思路
      1. 说明原理,写下注释
      1. 根据注释,补齐代码

    手写bind

    // 1. 需求:手写bind => bind位置(挂在那里) => Function.prototype
        Function.prototype.newBind = function() {
            // 2. bind是什么? 
            const _this = this;
            const args = Array.prototype.slice.call(arguments);
            // args特点,第一项是新的this,第二项~最后一项函数传参
            const newThis = args.shift();
    
            // a. 返回一个函数
            return function() {
                // b. 返回原函数执行结果 c. 传参不变
                return _this.apply(newThis, args);
            }
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    手写apply

     Function.prototype.newApply = function(context) {
            // 边缘检测
            // 函数检测
            if (typeof this !== 'function') {
                throw new TypeError('Error');
            }
            // 参数检测
            context = context || window;
    
            // 挂载执行函数
            context.fn = this;
    
            // 执行执行函数
            let result = arguments[1]
                ? context.fn(...arguments[1])
                : context.fn();
    
            // 销毁临时挂载
            delete context.fn;
            return result;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    3.总结

    this 的指向哪几种 ?
    总结起来,this 的指向规律有如下几条:

    • 在函数体中,非显式或隐式地简单调用函数时,在严格模式下,函数内的 this 会被绑定到 undefined 上,在非严格模式下则会被绑定到全局对象 window/global 上。
    • 一般使用 new 方法调用构造函数时,构造函数内的 this 会被绑定到新创建的对象上。
    • 一般通过 call/apply/bind 方法显式调用函数时,函数体内的 this 会被绑定到指定参数的对象上。
    • 一般通过上下文对象调用函数时,函数体内的 this 会被绑定到该对象上。
    • 在箭头函数中,this 的指向是由外层(函数或全局)作用域来决定的。
  • 相关阅读:
    PostgreSQL使用(一)
    携职教育:不容易啊,我的软考高项经验分享
    Python 文件的读写操作
    第十二周总结
    SpringBoot项目启动的时候直接退出了?
    我的算法笔记 | leetCode easy题感受
    IP协议的特性
    总结——》【Redis】
    Java面试宝典.exe程序成功运行,经典 Java 万字笔记,查漏补缺,备战跳槽面试
    「Spring」Boot Docker 认证指南(下)
  • 原文地址:https://blog.csdn.net/weixin_44247866/article/details/127903214