最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • JS this 指向配合场景实战

    正文概述 掘金(Des bisous)   2021-04-05   498

    前言

    本文将通过一个个小案例,详细的讲解 this 在各种情况下所指向的内容是如何变化的。

    章节一

    /**
     * this 是 JavaScript 的关键字
     * 当前环境执行期上下文对象的一个属性
     * this 在不同的环境、不同作用下,表现是不同的
     * 
     * 获取全局对象
     * web:window、self、frames、this
     * node:global
     * worker:self
     * 通用:globalThis
     */
    
    var a = 'global -> a';
    var obj = {
      a: 'obj -> a',
      test: function () {
        console.log(this.a); // 调用当前方法的对象
        console.log(window.a); // web 环境下全局对象, node 环境下不存在
        // console.log(global.b); // node 环境下全局对象, web 环境下不存在
        console.log(globalThis.a);
      }
    }
    obj.test();
    
    
    function useStrict() {
      'use strict' 
      return this;
    }
    
    /**
     * 使用严格模式的话,会返回 undefined,因为 useStrict() 没说明谁调用它
     * 但是 window.useStrict() 这样调用的话,严格模式也会返回 window,因为明确表明了谁调用了它
     * 
     * 使用非严格模式的话,都会返回 window
     */
    console.log(useStrict())
    console.log(window.useStrict())
    

    章节二

    class People {
      constructor() {
        // 将会定义到实例对象 this 的属性上 -> new -> this -> {}
        this.print = function () {
          console.log('实例属性:', this);
        }
      }
    
      // 类的原型上的方法 -> People.prototype
      // new -> this -> {} -> __proto__ -> People.prototye
      print() {
        console.log('类原型上的方法:', this);
      }
    
      // 类的静态方法
      static print() {
        console.log('静态属性');
      }
    }
    
    /** 
     * Class 其实就是 函数
     */
     
    function People() {
      this.print = function () {
        console.log('实例属性:' + this);
      }
    }
    
    People.prototype.print = function () {
      console.log('类原型上的属性:' + this);
    }
    
    People.print = function () {
      console.log('静态属性:' + this);
    }
    
    
    const man = new People();
    
    /**
     * 输出:实例属性: People {print: ƒ}
     * 这里需要知道的是为什么不是输出“类原型上的方法”,那是因为“类原型上的方法”在 People 在定义的时候就进行的赋予了
     * 而“实例属性”是在 new 的时候,对 constructor 的执行,并且改变 this 为实例对象的时候赋予的一个实例上的方法
     */
    man.print();
    People.print(); // 输出:静态属性
    

    章节三

    const PeopleA = Object.create(null); // 输出的对象,没有 __proto__
    const PeopleB = Object.create({ a: 1 }); // 输出的对象的 __proto__ 指向了传入的 {a: 1}
    
    
    class Father {
      constructor(age) {
        this.age = age;
      }
    
      swim() {
        console.log('Go swimming!!!');
      }
    }
    
    class Son extends Father{
      constructor() {
        /**
         * 类似于 call 的继承:在这里 super 相当于把 A 的 constructor 给执行了,
         * 并且让方法中的 this 是 B 的实例,super 当中传递的实参都是在给 A 的 constructor 传递。
         * super(18) 相当于 Father.prototype.constructor.call(this, 18)
         * super.swim() 相当于 Father.prototype.swim()
         */
        super(18);
        this.hooby = 'traval';
        console.log(this.age);
      }
    
      study() {
        console.log(this);
        this.swim();
      }
    }
    const son = new Son();
    
    son.study();
    
    // 需要注意,由于 super 指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过 super 调用的。
    

    章节四

    var obj = {
      a: 1
    }
    
    var obj2 = {
      a: 100
    }
    
    var a = 2;
    
    function test(b, c, d, e, f) {
      // this 默认 -> 全局对象 window
      console.log(this.a, b, c, d, e, f);
    }
    
    test(); // 2 undefined undefined undefined undefined undefined
    test.call(obj); // 1 undefined undefined undefined undefined undefined
    test.apply(obj); // 1 undefined undefined undefined undefined undefined
    test.call(obj, 3, 4); // 1 3 4 undefined undefined undefined
    test.apply(obj, [3, 4]); // 1 3 4 undefined undefined undefined
    
    var test1 = test.bind(obj, 3, 4);
    
    test1(); // 1 3 4 undefined undefined undefined
    
    var test2 = test1.bind(obj2, 5, 6);
    
    test2(7); // 1 3 4 5 6 7
    
    /**
     * 这里的 test2 输出为 1 3 4 5 6 7 为什么呢?
     * 首先 bind 绑定的时候会返回一个新的函数 test2,
     * 其次,bind 内部也是使用 call 来改变 this 指向。在 test1 的时候执行, 内部 test 指向定义为 obj
     * test1.bind() 的执行的时候,返回 test2,test2 执行的时候,内部 test1 的 this 会指向 obj2,
     * 随后 test1 执行,内部的 test 的 this 指向还是 obj,所以 this.a 为 1,至于参数会不断透传下去
     */
    

    章节五

    'use strict'
    const test = () => {
      console.log(this);
    }
    function test1() {
      console.log(this);
    }
    const test2 = function () {
      console.log(this);
    }
    test(); // window
    test1(); // undefined
    test2(); // undefined
    
    // 严格模式下,箭头函数绑定了 window -> this
    

    章节六

    var obj = {
      a: 1
    }
    
    var a = 2;
    
    const test = () => {
      console.log(this.a);
    }
    
    test(); // 2
    test.call(obj); // 2
    test.apply(obj); // 2
    var test1 = test.bind(obj);
    test1(); // 2
    
    new test(); // test is not constructor
    
    obj.test = () => {
      console.log(obj);
      console.log(this);
    }
    obj.test(); // obj/window
    
    /**
     * 箭头函数是忽略任何形式的 this 指向改变 
     * 箭头函数一定不是一个构造函数
     * 箭头函数不是谁调用 this 就会指向谁
     */
    

    章节七

    obj.test = function () {
      const t1 = () => {
        console.log(this);
      }
      t1();
    }
    obj.test(); // obj
    
    obj.test = () => {
      const t1 = () => {
        console.log(this);
      }
      t1();
    }
    obj.test(); // window
    
    obj.test = function() {
      const t1 = () => {
        // t1 是箭头函数 this -> obj
        const t2 = () => {
          console.log(this);
        }
        t2();
      }
      t1();
    }
    obj.test(); // obj
    
    obj.test = function() {
      const t1 = function() {
        const t2 = () => {
          // t1 是普通函数 this -> window
          console.log(this);
        }
        t2()
      }
      t1();
    }
    obj.test(); // window
    
    /**
     * 总结:
     * 箭头函数 this 总是指向外层非箭头函数的 this 指向
     * 箭头函数是忽略任何形式的 this 指向改变 
     * 箭头函数一定不是一个构造函数
     * 箭头函数不是谁调用 this 就会指向谁
     */
    

    章节八

    // test3 输入变量赋值一个函数,这个在执行的时候才会进行声明和赋值,所以必须写在前面
    const test3 = () => {
        console.log(this);
    }
    
    var obj = {
        a: 1,
        b: 2,
        test: function() {
            console.log(this.a);
        },
        test2: test2,
        test3: test3,
        c: {
            d: 4,
            test4: function () {
                console.log(this.d);
            }
        },
        test5: function () {
            function test6() {
                console.log(this);
            }
            test6();
        }
    }
    
    obj.__proto__ = {
        e: 20
    }
    
    // 预编译的时候函数 test2 就声明了和定义了
    function test2() {
        console.log(this.b);
    }
    
    obj.test(); // 1
    obj.test2(); // 2
    obj.test3(); // window
    obj.c.test4(); // 4 这里按照对象中的 function 的最近谁调用 this 就指向那个宿主的原则即可了解
    obj.test5(); // window, 要知道的是 test6 的调用寄主就是 window·
    console.log(obj.e); // 20
    
    var obj2 = Object.create({
        test0: function () {
            console.log(this.a + this.b);
        }
    });
    
    obj2.a = 1;
    obj2.b = 2;
    
    obj2.test0(); // 1 + 2 = 3 这里的 this 指向就是 obj2
    
    /**
     * 总结:
     * this 的指向的基本原则:谁是调用 this 的寄主,this 就指向谁
     * 另类的就是对于箭头函数不同,箭头函数内部 this 的指向为最近外层非箭头函数的作用域
     */
    

    章节九

    // 使用的是 function Object() {} 构造函数构造的
    var obj1 = {
        a: 1,
        b: 2
    }
    
    // 使用 Object.create() 进行构造,可传入一个 prototype 对象, 传入 null 为一个无 prototype 对象
    var obj2 = Object.create({
        a: 1,
        b: 2
    });
    
    // 使用 Object.defineProperty 进行属性定义
    var obj3 = {};
    Object.defineProperty(obj3, 'a', {
        get() {
            console.log(this)
            return 1;
        }
    })
    console.log(obj3.a); // 这里输出 obj3
    // 这里 this 指向的就是 obj3
    
    function Test() {
        this.a = 1;
        this.b = 2;
        console.log(this);
    
        // return this; // 作为构造函数,默认行为范围 this
        return {
            a: 3,
            b: 4
        }
        // return null; // 作为构造函数,也是返回 this
        // return undefined; // 作为构造函数,也是返回 this
        // return 非 object 也返回 this
    }
    
    var obj4 = new Test(); // obj4 -> { a: 3, b: 4 }、console.log(this) -> Test {a: 1, b: 2}
    console.log(obj4);
    /**
     * new 过程:
     * var _obj = {};
     * _obj.__proto__ == Test.prototype;
     * var res = Test.call(_obj);
     * return typeof res === 'object' && res !== null ? res : obj;
     * 
     * 总结:
     * 构造函数默认隐式返回 this,或者手动返回 this,这个 this 指向的新对象的构造都是成功的
     * 如果手动返回一个新对象,那么这个 this 指向的那个对象将会被忽略,失效掉,因为失去了引用,相当于没有 new,比如如下:
     */
    
     var obj5 = new Test(); // { a: 3, b: 4 }
     var obj6 = Test(); // { a: 3, b: 4 }
    

    章节十

    var oBtn1 = document.getElementById('btn1');
    
    oBtn1.onclick = function () {
      console.log(this); // 输出了:<button id="btn1">click</button>
    }
    
    oBtn1.addEventListener('click', function () {
      console.log(this); // 输出了:<button id="btn1">click</button>
    }, false);
    
    // 事件处理函数内部的 this 总是指向被绑定 DOM 元素
    
    !(function (doc) { 
      var oBtn2 = doc.getElementById('btn2');
    
      function Plus() {
        this.a = 1;
        this.b = 2;
        this.init();
      }
    
      Plus.prototype.init = function () {
        this.bindEvent();
      }
    
      Plus.prototype.bindEvent = function () {
        oBtn2.addEventListener('click', this.handleBtnClick, false);
      }
    
      Plus.prototype.handleBtnClick = function () {
        console.log(this); // <button id="btn2">+</button>
        console.log(this.a + this.b); // NaN
      }
    
      window.Plus = Plus;
    })(document)
    
    var plus = new Plus();
    /**
     * 这里点击 btn2 事件,handleBtnClick 内部的 this 为 dom 元素本身
     * 如果需要内部 this 为 Plus 实例,可有以下方式:
     */
    // 方式一
    Plus.prototype.bindEvent = function () {
      oBtn2.addEventListener('click', this.handleBtnClick.bind(this), false);
    }
    // 方式二
    function Plus() {
      this.a = 1;
      this.b = 2;
      this.handleBtnClick = this.handleBtnClick.bind(this);
      this.init();
    }
    // 方式三
    Plus.prototype.bindEvent = function () {
      oBtn2.addEventListener('click', () => this.handleBtnClick(), false);
    }
    
    /**
     * 总结:
     * DOM 事件处理函数内部的 this 总是指向被绑定 DOM 元素
     */
    

    章节十一

    /**
     * 类中是严格模式
     */
    
     /**
      * 父亲有一个吃水果的方法 还有一个水果
      * 儿子有自己的水果 -> 儿子使用父亲吃水果的方法吃自己的水果
      */
    
    class Father {
      // constructor() {
      //   this.eat = this.eat.bind(this); // bind 会重新返回一个匿名函数
      // }
    
      get fruit() {
        return 'apple';
      }
    
      eat() {
        console.log('I am eating an ' + this.fruit);
      }
    }
    
    class Son {
      get fruit() {
        return 'orange';
      }
    }
    
    var father = new Father();
    var son = new Son();
    
    son.eat = father.eat;
    
    father.eat(); // I am eating an apple
    son.eat(); // I am eating an orange
    
    /** 
     * 如何让 son 也吃父亲的水果内?
     * 其实就是把父亲的 eat 内部绑定住父亲的 this
     * 可以这么做,给 Father 增加一个构造函数,如下:
     * constructor() {
     *  this.eat = this.eat.bind(this); // bind 会重新返回一个匿名函数
     * } 
     */
    

    总结

    总结:

    1. this 是 JavaScript 的关键字,当前环境执行期上下文对象的一个属性,this 在不同的环境、不同作用下,表现是不同的
    2. 获取全局对象
      1. web:window、self、frames、this
      2. node:global
      3. worker:self
      4. 通用:globalThis
    3. 扩展:Class 中由于 super 指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过 super 调用的
    4. 扩展:Object.create(null); // 输出的对象,没有 proto
    5. 扩展:Object.create({ a: 1 }); // 输出的对象的 proto 指向了传入的 {a: 1}
    6. 使用 bing、call、apply 的时候会变更非箭头函数内的 this 指向
    7. 箭头函数 this 总是指向外层非箭头函数的 this 指向
    8. 箭头函数是忽略任何形式的 this 指向改变
    9. 箭头函数一定不是一个构造函数
    10. 箭头函数不是谁调用 this 就会指向谁
    11. 对象中的 function 的最近谁调用 this 就指向那个宿主的原则
    12. this 的指向的基本原则:谁是调用 this 的寄主,this 就指向谁
    13. 对于箭头函数不同,箭头函数内部 this 的指向为最近外层非箭头函数的作用域
    14. 构造函数默认隐式返回 this,或者手动返回 this(返回 null, undefined 也是返回 this),这个 this 指向的新对象的构造都是成功的
    15. DOM 事件处理函数内部的 this 总是指向被绑定 DOM 元素

    起源地下载网 » JS this 指向配合场景实战

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元