this关键字是函数当中最重要的一个知识点。它在JavaScript中的表现也会有一些细微的不一样,在严格和非严格模式之下也会有一些差异。html
绝大多数状况下,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)中的一个属性,在非严格模式下,它老是指向一个对象,而在严格模式下,它能够被设置成任意值。node
全局上下文即全局对象,例如在浏览器环境当中,this始终指的是window对象,不管是否是严格模式。来看以下一个示例:web
//在浏览器环境中,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
来获取一个全局对象,不管你的代码是否在当前上下文运行。typescript
var obj = {
func:function(){
console.log(this);
console.log(globalThis);
}
}
obj.func();//先打印obj对象,再打印window对象,浏览器环境中
复制代码
在函数内部,this取决于它被调用的方式。例如如下的非严格模式下,没有手动去经过设置调用方式,而且是在全局环境下调用的,因此this指向全局对象。segmentfault
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
方法。如例:markdown
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的表现也会相似,但也有一些区别和注意事项。app
在类当中,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 {};
}
}
复制代码
在非严格模式下,若是调用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]';
}
复制代码
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一样也指向的是调用这个方法的对象,实际上也就至关于该方法在这个对象上同样。如:
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,''等。
在一个对象的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
复制代码
当一个函数被当作构造函数调用时(使用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事件处理函数,它的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,则指向全局对象。如:
<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绑定。如:
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的方法会抛出错误。
打个广告,我在思否上线的课程玩转typescript1,玩转typescript2适用于有必定基础的前端,还望你们多多支持,谢谢。