javascript中this关键字详解


Posted in Javascript onDecember 12, 2016

不管学习什么知识,习惯于把自己所学习的知识列成一个list,会有助于我们理清思路,是一个很好的学习方法。强烈推荐。

以下篇幅有点长,希望读者耐心阅读。

以下内容会分为如下部分:

1.涵义

1.1:this涵义

1.2:this指向的可变性

2.使用场合

2.1:全局环境

2.2:构造函数

2.3:对象的方法

3.使用注意点

3.1:避免多层嵌套this

3.2:避免数组处理方法中的this

3.3:避免回调函数中的this

1.涵义

1.1:this涵义

在我写的一篇关于 构造函数与new关键字 的关系的博文中谈及,new关键字总是会返回一个对象。这个对象可以是new调用构造函数时返回的空对象,也可以是在构造函数中使用return语句返回的复杂数据类型(包括对象,数组等)。

同样,与new关键字相同,this关键字总是返回一个对象。再说的详细一些,就是属性或方法“当前”所在的对象。

var Keith = {
  firstName: 'Chou',
  describe: function() {
   return this.firstName;
  }
 };
console.log(Keith.describe()); //'Chou'

上面代码中,this.firstName表示describe方法当前所在对象的firstName属性。也就是说,在全局作用域下调用describe方法时,describe方法所在的当前对象是Keith,所以就是调用Keith.firstName。

1.2:this指向的可变性

由于对象的属性可以赋给另外一个对象,所以属性所在的当前对象是可变的。也就是说,this的指向是可变的。

var Keith = {
  firstName: 'Chou',
  describe: function() {
   return this.firstName;
  }
 };
 var Rascal={
  firstName: 'King'
 }
 Rascal.describe=Keith.describe;
 console.log(Rascal.describe()); //'King'

上面代码中,Keith对象中的describe属性赋给Rascal,于是describe方法中当前所在的对象就是Rascal,所以this.firstName就指向Rascal。因为是传址传递,所以修改firstName会对原对象有影响。这个例子可能不便于理解,再看看下面这个例子。

function f(){
  return this.firstName;
 }
 var Keith = {
  firstName: 'Chou',
  describe:f
 };
 var Rascal={
  firstName: 'King',
  describe:f
 }
 console.log(Keith.describe()); //'Chou'
 console.log(Rascal.describe()); //'King'

上面代码中,把方法移到全局作用域下,函数f内部使用了this关键字。随着f所在的对象不同,this指向也就不同。

在全局作用域下this关键字会指向顶层对象(也就是window对象)。

var name='keith';
 function person(){
  var name='rascal';
  return this.name;
 }
console.log(person()); //'keith'

上面代码中,返回的是keith而不是rascal。原因在于this指向的是全局作用域。在全局作用域中定义一个函数,默认是指向window对象,而不是函数本身。但是,如果在函数内部不使用var来声明一个局部变量,那结果也会不同。

var name='keith';
 function person(){
  name='rascal';
  return this.name;
 }
console.log(person()); //'rascal'

上面代码中,在函数内部没有使用var来声明一个局部变量,那么此时函数内部的name属性不是局部变量,而是全局变量。所以会覆盖掉前面的name属性。如果对于局部变量和全局变量不了解,可以访问 这篇文章 。 

只要函数被赋给另外一个变量,this的指向会发生改变。

var Keith={
  name:'keith',
  describe:function(){
   return this.name;
  }
 }
var name='rascal';
var f=Keith.describe;
console.log(f()) //'rascal'

上面代码中,返回的是rascal,而不是keith。因为Keith.describe被赋值给了f变量,而全局作用域下有一个name变量,所以Keith内部的函数的this指向就会指向f运行时所在的对象(顶层对象,也就是window对象)

总结一下:

1.javascript语言中,一切皆为对象(除了 undefined 和 null 之外),运行环境也是对象,所以函数都是在某个对象之中运行,this就是这个对象(环境)。

2.this的指向是动态的。如果函数在全局作用域中,那么this就会指向全局环境;如果函数位于某个对象中,那么this就会指向该对象。

2.使用场合

this的使用场合可以分为以下几个场合。

2.1:全局环境(全局作用域)

在全局作用域中使用this对象,它指向的就是顶层对象,也就是window对象。

function keith() {
  return (this === window)
  }
console.log(keith()) //true

上面代码中,不管是不是在函数内部,只要在全局作用域下运行,this就是指向顶层对象window。

2.2:构造函数

构造函数中的this,指向的是将要创建的对象实例。

function Keith() {
  this.sex = 'boy';
 }
 var person = new Keith();
 console.log(person.sex); //'boy'

上面代码中,在全局作用域下定义了Keith构造函数,然后调用构造函数并赋值给person对象实例。

构造函数创建的三个基本要求:函数名首字母大写;构造函数内部使用this关键字来指向即将生成的对象实例;使用new关键字来调用构造函数并返回对象实例。

如果想更进一步深入了解构造函数与new关键字的关系,请移步至 这篇文章 。

2.3:对象的方法

当A对象的方法被赋予B对象,该方法中的this就从指向A对象变成指向B对象。所以要特别小心,将某个对象的方法赋值个另外一个对象时,会改变this的指向。

var keith = {
  sex: 'boy',
  foo: function() {
   return this.sex;
  }
 };
 var rascal = {
  sex: 'girl'
 };
 rascal.foo = keith.foo;
 console.log(keith.foo()); //'boy'
 console.log(rascal.foo()); //'girl'

上面代码中,把keith的foo函数赋值给了rascal,那么this的指向就从keith变成了rascal。

如果某个方法位于多层对象的内部,这时为了简化书写,把该方法赋值给一个变量,往往会得到不一样的结果。

var a = {
  b: {
   p: 'keith',
   c: function() {
    return this.p;
   }
  }
 };
 var person = a.b.c;
 console.log(person()); //undefined

上面代码中,c是两层对象里面的一个方法。为求简便,将其赋值给全局变量person,结果调用时,this指向了顶层对象window。而在window中变量p默认值为undefined。

要解决这个问题,可以只将c所在的对象赋值给person变量,或者是直接调用。

var person = a.b;
console.log(person.c()); //'keith'
console.log(a.b.c()); //'keith'

3.使用注意点

3.1:避免多层嵌套this

当在闭包中使用多层this,则this都会指向window。

function keith() {
  console.log(this);
  return function() {
   return this;
  }
 }
 keith(); //window
 keith()(); //window

上面代码中,在一个函数中返回另外一个匿名函数是闭包的特点之一,可以看出,当在闭包中使用this对象都会指向全局作用域中的window对象。

如果在函数外包含一个对象,则内部this指向全局作用域,而外部this对象指向当前作用域。

var o = {
  f1: function() {
   console.log(this);
   (function() {
    console.log(this)
   })();
  }
 };
 o.f1(); //Object , Window

上面代码包含两层this,结果运行后,第一层指向当前对象,第二层指向全局对象。

实际执行的是如下代码。

function keith() {
  console.log(this);
 }
 var o = {
  f1: function() {
   console.log(this);
   var f2 = keith();
  }
 };
 o.f1(); //Object , Window

要实现多层this嵌套,有两种解决方法:

一是在第二层中改用一个指向外层this的变量。

var o = {
  f1: function() {
   console.log(this);
   var that = this;
   (function() {
    console.log(that);
   })();
  }
 };
 o.f1(); //Object , Object

上面代码中,定义了局部变量that,固定指向了外层的this,然后在内层中使用that,就不会发生this指向的改变。但是如果函数外部内有嵌套一个对象,this还是会指向全局。

二是Javascript中的严格模式。在严格模式下,如果内部函数的this指向了window对象,就会报错。

var a = {
  count: 0,
  fun: function() {
   'use strict';
   return this.count++;
  }
 }
 var f = a.fun;
 console.log(f()) //'TypeError: this is undefined'

上面代码中,fun方法使用严格模式声明。把a对象中的fun方法赋值给全局变量f,那么this此时指向window对象,在严格模式下,就会报错。如果函数外部没有嵌套一个对象,那么不会报错,而是会返回undefined。

3.2:避免数组处理方法中的this

  数组的map和foreach方法,允许提供一个函数作为参数。这个函数内部不应该使用this

var keith = {
  a: 'Hello',
  b: ['b1', 'b2'],
  c: function() {
   this.b.forEach(function(item) {
    console.log(this.a + ' ' + item);
   })
  }
 };
 keith.c();
 //undefined b1
 //undefined b2

上面代码中,forEach方法的回调函数中的this,其实指向的是window对象,因此取不到keith.a的值,同上也属于避免多层嵌套this。也就是说,内层的this不指向外部函数,而是指向顶层对象。

要解决这个方法,可以使用that变量来代替回调函数中的this。

var keith = {
  a: 'Hello',
  b: ['b1', 'b2'],
  c: function() {
   var that = this;
   this.b.forEach(function(item) {
    console.log(that.a + ' ' + item);
   })
  }
 };
 keith.c();
 //Hello b1
 //Hello b2

另外一种方法,就是让this做为forEach方法的第二个参数,来固定它的运行环境。

var keith = {
  a: 'Hello',
  b: ['b1', 'b2'],
  c: function() {
   this.b.forEach(function(item) {
    console.log(this.a + ' ' + item);
   }, this)
  }
 };
 keith.c();
 //Hello b1
 //Hello b2

3.3:避免回调函数中的this

回调函数中的this往往会改变指向。

var o = {
  f: function() {
   console.log(this === o);
  }
 };
 o.f(); // true;

上面代码中,调用o对象的f方法,返回true。

但是,如果将f方法指定给某个按钮的click事件,this的指向就变了。

$('button').on('click',o.f);

上面代码中,使用了jquery方法来获取button元素,并绑定click事件。点击按钮后控制台会显示false。原因是此时this不再指向o对象了,而是指向按钮的DOM对象,因为f方法是在按钮对象的环境中被调用的。

总结一下:

a:如果想要多层嵌套this关键字,最常用的解决方法就是使用that变量,固定指向外层的this,然后在内层中使用that变量。就不会发生内层this指向全局的问题。

b:如果在回调函数中使用this关键字,注意this的指向问题。

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持三水点靠木!

Javascript 相关文章推荐
JavaScript禁止复制与粘贴的实现代码
May 16 Javascript
Javascript缓存API
Jun 14 Javascript
jQuery插件 Jqplot图表实例
Jun 18 Javascript
JavaScript每天必学之事件
Sep 18 Javascript
JS实现用户注册时获取短信验证码和倒计时功能
Oct 27 Javascript
js 性能优化之快速响应的用户界面
Feb 15 Javascript
详解在Vue中如何使用axios跨域访问数据
Jul 07 Javascript
vue页面使用阿里oss上传功能的实例(二)
Aug 09 Javascript
JS实现自定义弹窗功能
Aug 08 Javascript
对angularjs框架下controller间的传值方法详解
Oct 08 Javascript
js canvas实现俄罗斯方块
Oct 11 Javascript
Node.js 中如何收集和解析命令行参数
Jan 08 Javascript
深入理解javascript函数参数与闭包
Dec 12 #Javascript
smartupload实现文件上传时获取表单数据(推荐)
Dec 12 #Javascript
Javascript中call,apply,bind方法的详解与总结
Dec 12 #Javascript
微信小程序开发之圆形菜单 仿建行圆形菜单实例
Dec 12 #Javascript
深入理解javascript中concat方法
Dec 12 #Javascript
js仿微信语音播放实现思路
Dec 12 #Javascript
解析JavaScript数组方法reduce
Dec 12 #Javascript
You might like
使用PHPMYADMIN操作mysql数据库添加新用户和数据库的方法
2010/04/02 PHP
php中字符集转换iconv函数使用总结
2014/10/11 PHP
微信小程序 消息推送php服务器验证实例详解
2017/03/30 PHP
PHP ADODB生成HTML表格函数rs2html功能【附错误处理函数用法】
2018/05/29 PHP
IE FF OPERA都可用的弹出层实现代码
2009/09/29 Javascript
你必须知道的JavaScript 中字符串连接的性能的一些问题
2013/05/07 Javascript
JavaScript给url网址进行encode编码的方法
2015/03/18 Javascript
RequireJS使用注意细节
2016/05/15 Javascript
javascript实现日期三级联动下拉框选择菜单
2020/12/03 Javascript
使用vue的v-for生成table并给table加上序号的实例代码
2017/10/27 Javascript
详解PHP后期静态绑定分析与应用
2018/03/21 Javascript
layui 表格的属性的显示转换方法
2018/08/14 Javascript
解析JS在获取当前月的最后一天遇到的坑
2019/08/30 Javascript
Python random模块用法解析及简单示例
2017/12/18 Python
Python-Seaborn热图绘制的实现方法
2019/07/15 Python
对于Python深浅拷贝的理解
2019/07/29 Python
pandas DataFrame行或列的删除方法的实现示例
2019/08/02 Python
python中通过selenium简单操作及元素定位知识点总结
2019/09/10 Python
wxPython实现画图板
2020/08/27 Python
python3.6环境下安装freetype库和基本使用方法(推荐)
2020/05/10 Python
简单介绍一下pyinstaller打包以及安全性的实现
2020/06/02 Python
24个canvas基础知识小结
2014/12/17 HTML / CSS
美国花布包包品牌:Vera Bradley
2017/08/11 全球购物
REISS美国官网:伦敦最受欢迎的时尚品牌
2019/08/16 全球购物
意大利一家专营包包和配饰的网上商店:Borse Last Minute
2019/08/26 全球购物
Linux管理员面试经常问道的相关命令
2013/04/29 面试题
儿子婚宴答谢词
2014/01/09 职场文书
法学函授自我鉴定
2014/02/06 职场文书
小学生竞选班干部演讲稿
2014/04/24 职场文书
知识就是力量演讲稿
2014/09/13 职场文书
2014年幼儿园保育工作总结
2014/12/02 职场文书
捐款感谢信
2015/01/20 职场文书
邀请函样本
2015/02/02 职场文书
2015年销售人员工作总结
2015/04/07 职场文书
初中英语教学随笔
2015/08/15 职场文书
Python实现科学占卜 让视频自动打码
2022/04/09 Python