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值,须要应用call
或apply
办法。如例:
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));//10console.log(sum.call(count,'3',4))//'334'console.log(sum.call(null,3,4));//14console.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]));//10console.log(sum.apply(count,['3',4]))//'334'console.log(sum.apply(null,[3,4]));//14console.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)());//objNameconsole.log(newFn());//objNamevar bindObj = { value:"bind", f:fn, g:newFn, h:fn.bind(bindObj)}var newBind = { a:fn.bind(bindObj)}console.log(bindObj.f());//bindconsole.log(bindObj.g());//objNameconsole.log(bindObj.h());//undefinedconsole.log(newBind.a());//bind
箭头函数
在箭头函数中,this与关闭环境当中的上下文的this绑定统一,在全局环境中,那它的this就是全局对象。如:
var obj = { a:() => { return this; }, b:function(){ var x = () => { return this;}; return x(); }}console.log(obj.a());//globalconsole.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,实用于有肯定根底的前端,还望大家多多反对,谢谢。