最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 深入JavaScript中的this对象

    正文概述 掘金(夕水)   2021-04-01   476

    this 对象详解

    this关键字是函数当中最重要的一个知识点。它在JavaScript中的表现也会有一些细微的不同,在严格和非严格模式之下也会有一些差别。

    绝大多数情况下,this的指向由函数的调用方式决定。它不能被赋值,并且每次函数调用,它也有可能会不同。ES5引入了bind方法来设置函数的this值,而不需要考虑函数的调用方式,ES6的箭头函数不提供自身的this绑定,它的this由当前上下文决定。

        const obj = {
            name:"hello,world!",
            getName(){
                return this.name;
            }
        }
        console.log(obj.getName());//"hello,world!"
    

    语法:

      this
    

    它的值是当前上下文(global,function,eval)中的一个属性,在非严格模式下,它总是指向一个对象,而在严格模式下,它可以被设置成任意值。

    描述

    全局上下文

    全局上下文即全局对象,例如在浏览器环境当中,this始终指的是window对象,不论是否是严格模式。来看如下一个示例:

        //在浏览器环境中,window对象就是全局对象
        console.log(this === window);//true
    
        //不用标识符定义一个变量,也会自动将该变量添加到window对象中,作为window对象的一个属性
        a = 250;
        console.log(this.a);//250
    
        this.message = "hello,world!";
        console.log(message);
        console.log(window.message);
        //都是打印的"hello,world!"
    
        var obj = {
            func:function(){
                console.log(this);
                console.log(globalThis);
            }
        }
        obj.func();//先打印obj对象,再打印window对象,浏览器环境中
    

    函数上下文

    在函数内部,this取决于它被调用的方式。例如以下的非严格模式下,没有手动去通过设置调用方式,并且是在全局环境下调用的,所以this指向全局对象。

      function fn(){
          return this;
      }
      //在浏览器环境中
      console.log(fn() === window);//true
      //在node.js环境中
      console.log(fn() === globalThis);//true
    

    然而,在严格模式下,如果没有为this设置值,那么this会保持为undefined。如:

        function fn(){
            'use strict';
            return this;
        }
        console.log(fn() === undefined) //true
    

    如果想要改变this值,需要使用callapply方法。如例:

        var obj = { value:"this is custom object!"};
        var value = "this is global object!";
        var getThis = function(){
            return this.value;
        }
        console.log(getThis());//"this is global object!"
        console.log(getThis.apply(obj))//"this is custom object!"
        console.log(getThis.call(obj))//"this is custom object!"
    

    类上下文

    尽管ES6的类和函数有些相似,this的表现也会类似,但也有一些区别和注意事项。

    在类当中,this就是一个常规的类对象,类里面定义的非静态的方法都会被添加到this对象的原型当中。例:

        class Test {
            constructor(){
                const p = Object.getPrototypeOf(this);
                console.log(Object.getOwnPropertyNames(p));
            }
            getName(){}
            getValue(){}
            static getNameAndValue(){}
        }
        new Test();//["constructor","getName","getValue"]
    

    比如,我们要调用以上的getNameAndValue方法,我们可以像如下这样调用:

        Test.getNameAndValue();
        //或者
        const test = new Test();
        test.constructor.getNameAndValue();
    

    派生类

    在派生类当中,不会像基类那样,有初始的绑定。什么是派生类?也就是继承基类的类。例如:

    class Base {
        constructor(){
            this.key = "base";
        }
    }
    class Test extends Base {}
    //这里的test就是一个派生类
    

    在派生类的构造函数当中,如果不使用super绑定this,则在使用this的过程中会报错Must call super constructor in derived class before accessing 'this' or returning from derived constructor。大致意思就是要有一个super绑定。如:

    class Base {
        constructor(){
            this.key = "base";
        }
    }
    class Test extends Base {
        constructor(){
            console.log(this);
        }
    }
    //ReferenceError
    

    但是如果我们稍微改一下,如下:

    class Base {
        constructor(){
            this.key = "base";
        }
    }
    class Test extends Base {
        constructor(){
            super();//这时候会生成一个this绑定
            console.log(this);
        }
    }
    //Test,继承了基类的属性和方法,相当于执行this = new Base()
    

    派生类不能在没有super方法的构造函数中返回一个除对象以外的值,或者说是有super方法的前面直接返回一个对象以外的值也是不行的,除非根本就没有构造函数。如:

    class Base {
        constructor(){
            this.key = "base";
        }
    }
    class Test extends Base {
        constructor(){
            return 1;
            super();
        }
    }
    //TypeError
    

    但是下面的示例不会出错:

    class Base {
        constructor(){
            this.key = "base";
        }
    }
    class Test extends Base {
        constructor(){
            return {};
            super();
        }
    }
    

    下面示例会报错:

    class Base {
        constructor(){
            this.key = "base";
        }
    }
    class Test extends Base {
        constructor(){
            return 1;
        }
    }
    //TypeError
    

    下面示例不会报错:

    class Base {
        constructor(){
            this.key = "base";
        }
    }
    class Test extends Base {
        constructor(){
            return {};
        }
    }
    

    this和对象之间的转换

    在非严格模式下,如果调用call或apply方法,传入的第一个参数,也就是被用作this的值不是一个对象,则会尝试被转换为对象。基本类型值,如null何undefined会被转换成全局对象,而像其他的基本类型值则会使用对应的构造函数来转换成对象。例如number类型数字1就会调用new Number(1),string类型'test'就会调用new String('test')。

    例如:

    function sum(c,d){
        return this.a + this.b + c + d;
    }
    var a = 3,b = 4;
    var count = {
        a:1,
        b:2
    }
    //call方法后面的参数直接被用作函数的参数
    console.log(sum.call(count,3,4));//10
    console.log(sum.call(count,'3',4))//'334'
    console.log(sum.call(null,3,4));//14
    console.log(sum.call(undefined,'3',4));//'734'
    console.log(sum.call(1,3,4));//new Number(1)上没有a和b属性,所以是this.a + this.b就是NaN,即两个undefined相加
    console.log(sum.call('',1,'2'))//'NaN2'
    //apply方法参数只能传数组参数
    //TypeError
    // console.log(sum.apply(count,3,4));
    // console.log(sum.apply(count,'3',4))
    // console.log(sum.apply(null,3,4));
    // console.log(sum.apply(undefined,'3',4));
    // console.log(sum.apply(1,3,4));
    // console.log(sum.apply('',1,'2'))
    //必须这样传
    console.log(sum.apply(count,[3,4]));//10
    console.log(sum.apply(count,['3',4]))//'334'
    console.log(sum.apply(null,[3,4]));//14
    console.log(sum.apply(undefined,['3',4]));//'734'
    console.log(sum.apply(1,[3,4]));//new Number(1)上没有a和b属性,所以是this.a + this.b就是NaN,即两个undefined相加
    console.log(sum.apply('',[1,'2']))//'NaN2'
    

    再来看一个示例如下:

    function test(){
        console.log(Object.prototype.toString.call(this))
    }
    console.log(test.call(7));//[object Number]
    console.log(test.call(undefined));//[object global],在浏览器环境下指向为[Object window]
    console.log(test.apply('123'));//[object String]
    

    根据以上示例,我们就可以知道了利用Object.prototype.toString方法来判断一个对象的类型。如可以封装一个函数如下:

    function isObject(value){
        return Object.prototype.toString.call(value) === '[object Object]';
    }
    //等价于
    function isObject(value){
        return Object.prototype.toString.apply(value) === '[object Object]';
    }
    //等价于
    function isObject(value){
        return {}.toString.call(value) === '[object Object]';
    }
    //等价于
    function isObject(value){
        return {}.toString.apply(value) === '[object Object]';
    }
    

    bind方法

    ES5引入了bind方法,该方法为Function的原型对象上的一个属性,在一个函数fn中调用fn.bind(object)将会创建一个和该函数相同作用域以及相同函数体的函数,但是它的this值将被绑定到bind方法的第一个参数,无论这个新创建的函数以什么方式调用。如:

    function fn(){
        var value = "test";
        return this.value;
    }
    var obj = {
        value:"objName"
    }
    var newFn = fn.bind(obj);
    console.log(fn.bind(obj)());//objName
    console.log(newFn());//objName
    var bindObj = {
        value:"bind",
        f:fn,
        g:newFn,
        h:fn.bind(bindObj)
    }
    var newBind = {
        a:fn.bind(bindObj)
    }
    console.log(bindObj.f());//bind
    console.log(bindObj.g());//objName
    console.log(bindObj.h());//undefined
    console.log(newBind.a());//bind
    

    箭头函数

    在箭头函数中,this与封闭环境当中的上下文的this绑定一致,在全局环境中,那它的this就是全局对象。如:

    var obj = {
        a:() => {
            return this;
        },
        b:function(){
            var x = () => { return this;};
            return x();
        }
    }
    console.log(obj.a());//global
    console.log(obj.b());//obj
    

    如:

        //在浏览器环境下globalObject是window对象
        let globalObject = this;
        let getThis = () => this;
        console.log(getThis() === globalObject);//true
        let obj = {
            getThis:getThis
        }
        console.log(obj.getThis() === globalObject);//true
        console.log(obj.getThis.call(obj) === globalObject);//true
        console.log(obj.getThis.apply(obj) === globalObject);//true
        // 使用bind并未改变this指向
        console.log(obj.getThis.bind(obj)() === globalObject);//true
    

    也就是说,无论如何,箭头函数的this都指向它的封闭环境中的this。如下:

    var obj = {
        a:() => {
            return this;
        },
        b:function(){
            var x = () => { return this;};
            return x();
        }
    }
    console.log(obj.a());//global在浏览器环境下是window对象
    console.log(obj.b());//obj
    

    作为某个对象

    当调用某个对象中的函数中的方法时,在访问该函数中的this对象,将会指向这个对象。例如:

        var value = "this is a global value!";
        var obj = {
            value:"this is a custom object value!",
            getValue:function(){
                return this.value;
            }
        }
        console.log(obj.getValue());//"this is a custom object value!"
    

    这样的行为方式完全不会受函数定义的方式和位置影响,例如:

        var value = "this is a global value!";
        var obj = {
            value:"this is a custom object value!",
            getValue:getValue
        }
        function getValue(){
            return this.value;
        }
        console.log(obj.getValue());//"this is a custom object value!"
    

    此外,它只受最接近的引用对象的影响。如:

        var value = "this is a global value!";
        var obj = {
            value:"this is a custom object value!",
            getValue:getValue
        }
        obj.b = {
            value:"this is b object value!",
            getValue:getValue
        }
        function getValue(){
            return this.value;
        }
        console.log(obj.b.getValue());//"this is b object value!"
    

    对象原型链中的this

    在对象的原型链中,this同样也指向的是调用这个方法的对象,实际上也就相当于该方法在这个对象上一样。如:

       var obj = {
           sum:function(){
               return this.a + this.b;
           }
       }
       var newObj = Object.create(obj);
       newObj.a = 1;
       newObj.b = 2;
       console.log(newObj.sum());//3
       console.log(obj.sum());//NaN
    

    上例中,newObj对象继承了obj的sum方法,并且我们未newObj添加了a和b属性,如果我们调用newObj的sum方法,this实际上指向的就是newObj这个对象,所以我们可以得到结果为3,但是我们调用obj.sum方法的时候,this指向的是obj,obj对象并没有a和b属性,所以也就是两个undefined相加,就会是NaN。obj就作为了newObj的原型对象,这也是原型链当中的一个非常重要的特点。

    getter或setter中的this

    在一个对象的setter或者getter中同样的this指向设置或者获取这个属性的对象。如:

       function average(){
           return (this.a + this.b + this.c) / 3;
       }
       var obj = {
           a:1,
           b:2,
           c:3
           get sum:function(){
               return this.a + this.b + this.c;
           }
       }
       Object.defineProperty(obj,'average',{
           get:average,
           enumerable:true,
           configurable:true
       });
       console.log(obj.average,obj.sum);//2,6
    

    构造函数中的this对象

    当一个函数被当做构造函数调用时(使用new关键字),this指向的就是实例化的那个对象。

    如:

        function C(){
            this.a = 1;
        }
        var c1 = new C();
        console.log(c1.a);//1
        function C2(){
            var obj = {
                a:2
            }
            this.a = 3;
            return obj;
        }
        var c2 = new C2();
        console.log(c2.a);//2
    

    在上例中实例化的c2的构造函数C2中,由于手动的设置了返回的对象obj,所以导致this.a = 3这条语句被忽略,从而得到结果为2,就好像"僵尸"代码。当然也不能算是"僵尸"代码,因为实际上它是被执行了的,只不过对外部没有造成影响,所以可以被忽略。

    作为一个DOM事件处理函数

    当函数是一个DOM事件处理函数,它的this就指向触发事件的元素(有一些浏览器在使用非addEventListener动态添加函数时不遵守这个约定)。如:

    function changeStyle(e){
        console.log(this === e.currentTarget);//true
        console.log(this === e.target);//true
        //将背景色更改为红色
        this.style.setProperty('background',"#f00");
    }
    
    // 获取文档中所有的DOM元素
    var elements = document.getElementsByTagName('*');
    
    for(let i = 0,len = elements.length;i < len;i++){
        //为每个获取到的元素添加事件
        elements[i].addEventListener('click',changeStyle,false);
    }
    

    内联事件中的this

    当在内联事件中调用函数时,this指向的就是这个元素。但只有最外层的代码才指向这个元素,如果是内部嵌套函数中没有指定this,则指向全局对象。如:

        <button type="button" onclick="document.writeln(this.tagName.toLowerCase())">clicked me</button> 
        <!-- 点击按钮会在页面中出现button -->
    
    <button type="button" onclick="document.writeln((function(){return this})())">clicked me</button>
    <!-- 在浏览器环境下页面会写入[object Window] -->
    

    在类中更改this绑定

    类中的this取决于如何调用,但实际上在开发当中,我们手动的去绑定this为该类实例是一个很有用的方式,我们可以在构造函数中去更改this绑定。如:

    class Animal {
        constructor(){
            //利用bind方法让this指向实例化的类对象
            this.getAnimalName = this.getAnimalName.bind(this);
        }
        getAnimalName(){
            console.log("The animal name is ",this.animalName);
        }
        getAnimalNameAgain(){
            console.log("The animal name is ",this.animalName);
        }
        get animalName(){
            return "dog";
        }
    }
    class Bird {
        get animalName(){
            return "bird";
        }
    }
    
    let animal = new Animal();
    console.log(animal.getAnimalName());//The animal name is dog;
    let bird = new Bird();
    bird.getAnimalName = animal.getAnimalName;
    console.log(bird.getAnimalName());//The animal name is dog;
    
    bird.getAnimalNameAgain = animal.getAnimalNameAgain;
    console.log(bird.getAnimalNameAgain());//The animal name is bird;
    

    在这个示例中,我们始终将getAnimalName方法的this绑定到实例化的Animal类对象上,所以尽管在Bird类中定义了一个animalName属性,我们在调用getAnimalName方法的时候,始终得到的就是Animal中的animalName属性。所以第二个打印仍然是dog

    打个广告,我在思否上线的课程玩转typescript,适用于有一定基础的前端,还望大家多多支持,谢谢。


    起源地下载网 » 深入JavaScript中的this对象

    常见问题FAQ

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

    发表评论

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

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

    联系作者

    请选择支付方式

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