JavaScript 中的 this 介绍

发布于 2022-10-11 08:56:17 字数 15615 浏览 108 评论 0

在 JavaScript 中,this 这个上下文总是变化莫测,很多时候出现 bug 总是一头雾水,其实,只要分清楚不同的情况下如何执行就 ok 了。

全局执行

首先,我们在全局环境中看看它的 this 是什么:

first. 浏览器:

console.log(this);

// Window {speechSynthesis: SpeechSynthesis, caches: CacheStorage, localStorage: Storage, sessionStorage: Storage, webkitStorageInfo: DeprecatedStorageInfo…}

可以看到打印出了 window 对象;

second. node:

console.log(this);

// global

可以看到打印出了 global 对象;

总结:在全局作用域中它的 this 执行当前的全局对象(浏览器端是 Window,node 中是 global)。

函数中执行

纯粹的函数调用

这是最普通的函数使用方法了:

function test() {
  console.log(this);
};

test();

// Window {speechSynthesis: SpeechSynthesis, caches: CacheStorage, localStorage: Storage, sessionStorage: Storage, webkitStorageInfo: DeprecatedStorageInfo…}

我们可以看到,一个函数被直接调用的时候,属于全局调用,这时候它的 this 指向 全局对象;

严格模式 ‘use strict’;

如果在严格模式的情况下执行纯粹的函数调用,那么这里的的 this 并不会指向全局,而是 undefined,这样的做法是为了消除 js 中一些不严谨的行为:

'use strict';
function test() {
  console.log(this);
};

test();

// undefined

当然,把它放在一个立即执行函数中会更好,避免了污染全局:

(function (){
  "use strict";
 console.log(this);
})();

// undefined

作为对象的方法调用

当一个函数被当作一个对象的方法调用的时候:

var obj = {
  name: 'qiutc',
  foo: function() {
    console.log(this.name);
  }
}

obj.foo();

// 'qiutc'

这时候,this 指向当前的这个对象;

当然,我们还可以这么做:

function test() {
  console.log(this.name);
}
var obj = {
  name: 'qiutc',
  foo: test
}

obj.foo();

// 'qiutc'

同样不变,因为在 JavaScript 中一切都是对象,函数也是一个对象,对于 test ,它只是一个函数名,函数的引用,它指向这个函数,当 foo = test,foo 同样也指向了这个函数。

如果把对象的方法赋值给一个变量,然后直接调用这个变量呢?

var obj = {
  name: 'qiutc',
  foo: function() {
    console.log(this);
  }
}

var test = obj.foo;
test();

// Window

可以看到,这时候 this 执行了全局,当我们把 test = obj.foo ,test 直接指向了一个函数的引用,这时候,其实和 obj 这个对象没有关系了,所以,它是被当作一个普通函数来直接调用,因此,this 指向全局对象。

一些坑

我们经常在回调函数里面会遇到一些坑:

var obj = {
  name: 'qiutc',
  foo: function() {
    console.log(this);
  },
  foo2: function() {
    console.log(this);   //Object {name: "qiutc"...}
    setTimeout(this.foo, 1000);   // window 对象
  }
}

obj.foo2();

执行这段代码我们会发现两次打印出来的 this 是不一样的:

关于 setTimeout 的 this 指向:https://www.talkingcoder.com/article/6356947525374513523

  第一次是 foo2 中直接打印 this,这里指向 obj 这个对象,我们毋庸置疑;

但是在 setTimeout 中执行的 this.foo,却指向了全局对象,这里不是把它当作函数的方法使用吗?这一点经常让很多初学者疑惑;

其实 setTimeout 也只是一个函数而已,函数必然有可能需要参数,我们把 this.foo 当作一个参数传给 setTimeout 这个函数,就像它需要一个 fun 参数,在传入参数的时候,其实做了个这样的操作 fun = this.foo,看到没有,这里我们直接把 fun 指向 this.foo 的引用;执行的时候其实是执行了 fun() 所以已经和 obj 无关了,它是被当作普通函数直接调用的,因此 this 指向全局对象。

这个问题是很多异步回调函数中普遍会碰到的,为了解决这个问题,我们可以利用 闭包 的特性来处理:

var obj = {
  name: 'qiutc',
  foo: function() {
    console.log(this);
  },
  foo2: function() {
    console.log(this);
    var _this = this;
    setTimeout(function() {
      console.log(this);  // Window

      console.log(_this);  // Object {name: "qiutc"}
    }, 1000);
  }
}

obj.foo2();

可以看到直接用 this 仍然是 Window;因为 foo2 中的 this 是指向 obj,我们可以先用一个变量 _this 来储存,然后在回调函数中使用 _this,就可以指向当前的这个对象了;

setTimeout 的另一个坑

之前啊说过,如果直接执行回调函数而没有绑定作用域,那么它的 this 是指向全局对象(window),在严格模式下会指向 undefined,然而在 setTimeout 中的回调函数在严格模式下却表现出不同:

'use strict';

function foo() {
  console.log(this);
}

setTimeout(foo, 1);

// window

按理说我们加了严格模式,foo 调用也没有指定 this,应该是出来 undefined,但是这里仍然出现了全局对象,难道是严格模式失效了吗?

并不,即使在严格模式下,setTimeout 方法在调用传入函数的时候,如果这个函数没有指定了的 this,那么它会做一个隐式的操作—-自动地注入全局上下文,等同于调用 foo.apply(window) 而非 foo();

当然,如果我们在传入函数的时候已经指定 this,那么就不会被注入全局对象,比如: setTimeout(foo.bind(obj), 1);;

http://stackoverflow.com/questions/21957030/why-is-window-still-defined-in-this-strict-mode-code

作为一个构造函数使用

在 JavaScript 中,为了实现类,我们需要定义一些构造函数,在调用一个构造函数的时候需要加上 new 这个关键字:

function Person(name) {
  this.name = name;
  console.log(this);
}

var p = new Person('qiutc');

// Person {name: "qiutc"}

我们可以看到当作构造函数调用时,this 指向了这个构造函数调用时候实例化出来的对象;

当然,构造函数其实也是一个函数,如果我们把它当作一个普通函数执行,这个 this 仍然执行全局:

function Person(name) {
  this.name = name;
  console.log(this);
}

var p = Person('qiutc');

// Window

其区别在于,如何调用函数(new)。

箭头函数

在 ES6 的新规范中,加入了箭头函数,它和普通函数最不一样的一点就是 this 的指向了,还记得在上文中(作为对象的方法调用-一些坑-解决)我们使用闭包来解决 this 的指向问题吗,如果用上了箭头函数就可以更完美的解决了:

var obj = {
  name: 'qiutc',
  foo: function() {
    console.log(this);
  },
  foo2: function() {
    console.log(this);
    setTimeout(() => {
      console.log(this);  // Object {name: "qiutc"}
    }, 1000);
  }
}

obj.foo2();

可以看到,在 setTimeout 执行的函数中,本应该打印出在 Window,但是在这里 this 却指向了 obj,原因就在于,给 setTimeout 传入的函数(参数)是一个箭头函数:

函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象。

根据例子我们理解一下这句话:

在 obj.foo2() 执行的时候,当前的 this 指向 obj;在执行 setTimeout 时候,我们先是定义了一个匿名的箭头函数,关键点就在这,箭头函数内的 this 执行定义时所在的对象,就是指向定义这个箭头函数时作用域内的 this,也就是 obj.foo2 中的 this,即 obj;所以在执行箭头函数的时候,它的 this -> obj.foo2 中的 this -> obj;

简单来说, 箭头函数中的 this 只和定义它时候的作用域的 this 有关,而与在哪里以及如何调用它无关,同时它的 this 指向是不可改变的。

call, apply, bind

在 js 中,函数也是对象,同样也有一些方法,这里我们介绍三个方法,他们可以更改函数中的 this 指向:

call

fun.call(thisArg[, arg1[, arg2[, ...]]])

它会立即执行函数,第一个参数是指定执行函数中 this 的上下文,后面的参数是执行函数需要传入的参数;

apply

fun.apply(thisArg[, [arg1, arg2, ...]])

它会立即执行函数,第一个参数是指定执行函数中 this 的上下文,第二个参数是一个数组,是传给执行函数的参数(与 call 的区别);

bind

var foo = fun.bind(thisArg[, arg1[, arg2[, ...]]]);

它不会执行函数,而是返回一个新的函数,这个新的函数被指定了 this 的上下文,后面的参数是执行函数需要传入的参数,这三个函数其实大同小异,总的目的就是去指定一个函数的上下文(this),我们以 call 函数为例;

为一个普通函数指定 this

var obj = {
  name: 'qiutc'
};

function foo() {
  console.log(this);
}

foo.call(obj);

// Object {name: "qiutc"}

可以看到,在执行 foo.call(obj) 的时候,函数内的 this 指向了 obj 这个对象,成功;

为对象中的方法指定一个 this

var obj = {
  name: 'qiutc',
  foo: function () {
    console.log(this);
  }
}

var obj2 = {
  name: 'tcqiu222222'
};

obj.foo.call(obj2);

// Object {name: "tcqiu222222"}

可以看到,执行函数的时候这里的 this 指向了 obj2,成功;

为构造函数指定 this

function Person(name) {
  this.name = name;
  console.log(this);
}

var obj = {
  name: 'qiutc2222222'
};

var p = new Person.call(obj, 'qiutc');

// Uncaught TypeError: Person.call is not a constructor(…)

这里报了个错,原因是我们去 new 了 Person.call 函数,而非 Person ,这里的函数不是一个构造函数;

换成 bind 试试:

function Person(name) {
  this.name = name;
  console.log(this);
}

var obj = {
  name: 'qiutc2222222'
};

var Person2 = Person.bind(obj);

var p = new Person2('qiutc');

// Person {name: "qiutc"}

console.log(obj);

// Object {name: "qiutc2222222"}

打印出来的是 Person 实例化出来的对象,而和 obj 没有关系,而 obj 也没有发生变化,说明,我们给 Person 指定 this 上下文并没有生效;

因此可以得出: 使用 bind 给一个构造函数指定 this,在 new 这个构造函数的时候,bind 函数所指定的 this 并不会生效;

当然 bind 不仅可以指定 this ,还能传入参数,我们来试试这个操作:

function Person(name) {
  this.name = name;
  console.log(this);
}

var obj = {
  name: 'qiutc2222222'
};

var Person2 = Person.bind(obj, 'qiutc111111');

var p = new Person2('qiutc');

// Person {name: "qiutc111111"}

可以看到,虽然指定 this 不起作用,但是传入参数还是起作用了;

为箭头函数指定 this

我们来定义一个全局下的箭头函数,因此这个箭头函数中的 this 必然会指向全局对象,如果用 call 方法改变 this 呢:

var afoo = (a) => {
  console.log(a);
  console.log(this);
}

afoo(1);

// 1
// Window

var obj = {
  name: 'qiutc'
};

afoo.call(obj, 2);

// 2
// Window

可以看到,这里的 call 指向 this 的操作并没有成功,所以可以得出: 箭头函数中的 this 在定义它的时候已经决定了(执行定义它的作用域中的 this),与如何调用以及在哪里调用它无关,包括 (call, apply, bind) 等操作都无法改变它的 this。

只要记住箭头函数大法好,不变的 this。

Function.prototype.call

格式:fx.call( thisArg [,arg1,arg2,… ] );

call 的传参个数不限,第一个数表示调用函数(fx)函数体内this的指向.从第二个参数开始依次按序传入函数.

var age = 40;
var xiaoMing = {
age:30
};
var xiaoLi = {
age: 20
};
var getAge = function(){
console.log(this.age);
};
getAge.call( xiaoMing ); //30 表示函数this指向xiaoMing
getAge.call(xiaoLi); //20 表示函数this指向xiaoLi
getAge.call(undefined);//40 getAge.call(undefined)==getAge.call(null)
getAge.call(null);//40
getAge(); //40

如果我们传入 fx.call() 的第一个参数数为 null,那么表示函数 fx 体内 this 指向宿主对象,在浏览器是 Window 对象,这也解释了 getAge.call(undefined); //40。

在此基础我们可以理解为 getAge() 相当于 getAge.call(null/undefined),扩展到所有函数,fx()==fx.call(null) == fx.call(undefined)

值得注意的是严格模式下有点区别:this 指向 null

var getAge = function(){
'use strict'
console.log(this.age);
};
getAge(null);//报错 age未定义

再来理解 this 的使用

this 的常用场景:

this 位于一个对象的方法内,此时this指向该对象

var name = 'window';
var Student = {
name : 'kobe',
getName: function () {
console.log(this == Student); //true
console.log(this.name); //kobe
}
}
Student.getName();
var name = 'window';
var Student = {
name : 'kobe',
getName: function () {
var name=100;
console.log(this == Student); //true
console.log(this.name); //kobe
}
}
Student.getName(); // getName 取得是 Student 的 name

this 位于一个普通的函数内,表示 this 指向全局对象,浏览器是 window

var name = 'window';
var getName = function () {
var name = 'kobe'; //迷惑性而已
return this.name;
}
console.log( getName() ); //window

this 使用在构造函数(构造器)里面,表示 this 指向的是那个返回的对象。

var name = 'window';
//构造器
var Student = function () {
this.name = 'student';
}
var s1 = new Student();
console.log(s1.name); //student

注意:如果构造器返回的也是一个 Object 的对象(其他类型 this 指向不变遵循之前那个规律),这时候this指的是返回的这个 Object。

var name = 'window';
//构造器
var Student = function () {
	this.name = 'student';
	return {
		name: 'boyStudent'
	}
}

var s1 = new Student();
console.log(s1.name);  //boyStudent

this 指向失效问题

var name = 'window';
var Student = {
name : 'kobe',
getName: function () {
console.log(this.name);
}
}
Student.getName(); // kobe
var s1 = Student.getName;
s1(); // window

原因:此时 s1 是一个函数

function () {		
  console.log(this.name);  
}

对一个基本的函数,前面提过 this 在基本函数中指的是 window。

在开发中我们经常使用的 this 缓存法,缓存当前作用域下 this 到另外一个环境域下使用

最后理解 apply 的用法 Function.prototype.apply

格式:fx.apply(thisArg [,argArray] ); // 参数数组,argArray

apply 与 call 的作用是一样的,只是传参方式不同,

apply 接受两个参数,第一个也是 fx 函数体内 this 的指向,用法与 call 第一个参数一致,第二个参数是数组或者类数组,apply 就是把这个数组元素传入函数 fx。

var add = function (a ,b ,c) {
console.log(a +b +c);
}
add.apply(null , [1,2,3]); // 6

再吃透这个题目就 ok

var a=10;
var foo={
  a:20,
  bar:function(){
      var a=30;
      return this.a;
    }
}
foo.bar()
//20
(foo.bar)()
//20
(foo.bar=foo.bar)()
//10
(foo.bar,foo.bar)()
//10

上题注解:

时刻牢记:作用域链查找遵循 就近原则,this 谁调用就指向谁。

var a=10;
var foo={
a:20,
bar:function(){
var a=30; // this 指向 foo :console.log( this == foo) //true
return this.a;
}
}
foo.bar()
//20
// foo.bar() // foo调用,this 指向 foo , 此时的 this 指的是 foo,所以是 20
(foo.bar)()
// 20
// 第一步:
(function(){
var a=30;
return this.a;
})() // 作用域链向上查找,this 指向外一层的对象 foo
(foo.bar=foo.bar)()
//10
foo.bar=foo.bar

睁大眼睛,是单等号赋值,就是普通的复制,一个匿名函数赋值给一个全局变量,你可以把右边的 foo.bar 换成 b,即 (b = foo.bar)(),博客里面【this 指向失效问题】说过普通的函数里面的 this 指向 window,自然 this.a == 10。

(foo.bar,foo.bar)() // 逗号表达式
//10
//(foo.bar,foo.bar) 是一个小括号表达式,小括号表达式会依次创建两个匿名函数,并返回最后一个的匿名函数值,
(foo.bar,foo.bar) 得到的是这个函数
function(){
var a=30;
console.log( this == foo); // 如果不是很了解 this 的指向就加这个代码进行检测
return this.a;
}

这个是匿名函数,匿名函数的 this 指的是 widnow,那么 this.a = 10

this 可谓是 JavaScript 中的开发神器,使用得当的话不仅有事半功倍的效果,而且代码的逼格也更高。但是既然是神器,如果你没有足够的功力的话,那么就不要使用它,否则就有可能自毁身亡。曾几何时,我偶然得到这个神器,之后,自残,自残,再自残...,再自残了那么多次后,终于可以拥有强大功力持此神器行走江湖了。接下来,我就为大家来传授传说中神器的使用秘诀。

咳咳,入正题,this 是什么?this 表示当前运行方法的主体。

注意:函数中的this指向和当前函数在哪定义的话或者在哪执行都没有任何的关系。为啥这样说,请仔细阅读下面的秘籍大全。

神器秘籍大全

秘籍一:自制行函数里面的 this 永远都是 window

var inner = "window";
var obj = {inner : "obj",
fn : (function () {console.log(this.inner)})()
}

上面浏览器在运行该程序时,会自动运行 obj.fn 里面的方法,因为 obj.fn 是一个自制行函数,当执行该函数时,程序会输出 window。

为什么输出不是obj?

因为人家规定自制行函数里面的 this 是 window,所以其实 this.inner 就是 window.inner,因此这个 inner 是定义在全局变量的,它的值是 window。

秘籍二:元素绑定事件驱动方法运行,方法里的 this 表示当前绑定的元素

var oDiv = document.getElementsByTagName("div")[0];
oDiv.onclick=function(){
console.log(this); // 当用鼠标点击该元素,则输出 oDiv 元素的集合
};

这个好理解,元素绑定某个行为执行的方法,就相当于把这个方法也绑定在这个元素上,所以 this 也就指向元素本身。

秘籍三:方法执行,看方法名前面是否有 . 点,有的话前面是谁this就是谁,没有的话 this 就是 window

var obj={fn:fn};
function fn(){console.log(this)}
fn.prototype.aa = function(){console.log(this)};
var f=new fn;
fn(); // window..
obj.fn(); // Object..
fn.prototype.aa(); // fn.prototype
f.aa(); // f

记住此秘籍!!!

秘籍四:在构造函数模式中,函数体中的 this 是当前类的一个实例

function Fn(){
this.x = 100;
console.log(this); // 实例 f
}
var f = new Fn;

构造函数生成的实例,故构造函数里的 this 当然是指向当前这个实例了。

秘籍五(大招):call/apply 来改变 this 的指向

var oDiv = document.getElementsByTagName("div")[0];
function fn() {
console.log(this);
}
fn.call(oDiv);
fn.call(oDiv); // 执行这个语句后,fn 里面的 this 指向 oDiv 元素,applay 用法与 call 类似。

此大招一出来,上面四个秘籍都无效了。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

肩上的翅膀

暂无简介

文章
评论
26 人气
更多

推荐作者

櫻之舞

文章 0 评论 0

弥枳

文章 0 评论 0

m2429

文章 0 评论 0

野却迷人

文章 0 评论 0

我怀念的。

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文