关于javascript:深入JavaScript中的this对象

4次阅读

共计 9821 个字符,预计需要花费 25 分钟才能阅读完成。

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!"

笔记: 能够始终应用 globalThis 来获取一个全局对象, 无论你的代码是否在以后上下文运行。

    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

tips: 上例中,因为 fn 是间接被调用的,也就是并不是作为对象的属性来调用(window.fn),所以 this 应是 undefined,有些浏览器在最后反对严格模式的时候并没有正确的实现这个性能,所以谬误的返回了 window 对象。

如果想要扭转 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"]

tips: 静态方法不是 this 的属性,它们只是类本身的属性。

比方,咱们要调用以上的 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

留神: 无论应用 call,apply 还是 bind 其中的哪一种办法,都不能扭转箭头函数的 this 指向,因为都将被疏忽,然而依然能够传递参数,实践上第一个参数设置为 null 或者 undefined 为最佳实际。

如:

    // 在浏览器环境下 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 的原型对象,这也是原型链当中的一个十分重要的特点。

留神:Object.create()办法示意创立一个新对象,会以第一个参数作为新对象的原型对象,第一个参数只能为 null 或者新对象,不能为其它根本类型的值,如 undefined,1,” 等。

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 指向的就是实例化的那个对象。

留神: 只管构造函数返回的默认值就是 this 指向的那个对象,然而也能够手动设置成返回其它的对象,如果手动设置的值不是一个对象,则返回 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

留神: 在类的外部总是应用的严格模式,所以调用一个 this 值为 undefined 的办法会抛出谬误。

打个广告,我在思否上线的课程玩转 typescript,实用于有肯定根底的前端,还望大家多多反对,谢谢。

正文完
 0