跟我学习javascript的this关键字


Posted in Javascript onMay 28, 2020

本文仅就这一问题展开讨论,阅罢本文,读者若能正确回答 JavaScript 中的 What 's this 问题,作为作者,我就会觉得花费这么多功夫,撰写这样一篇文章是值得的。

我们要记住一句话:this永远指向函数运行时所在的对象!而不是函数被创建时所在的对象。也即:谁调用,指向谁。切记…

本文将分三种情况来分析this对象到底身处何方。

1、普通函数中的this

无论this身处何处,第一要务就是要找到函数运行时的位置。

var name="全局";
 function getName(){
  var name="局部";
  return this.name;
 };
 alert(getName());

当this出现在全局环境的函数getName中时,此时函数getName运行时的位置在

alert(getName());

显然,函数getName所在的对象是全局对象,即window,因此this的安身之处定然在window。此时的this指向window对象,则getName返回的this.name其实是window.name,因此alert出来的是“全局”!

那么,当this不是出现在全局环境的函数中,而是出现在局部环境的函数中时,又会身陷何方呢?

var name="全局";
 var xpg={
  name:"局部",
  getName:function(){
   return this.name;
  }
 };
 alert(xpg.getName());

其中this身处的函数getName不是在全局环境中,而是处在xpg环境中。无论this身处何处,一定要找到函数运行时的位置。此时函数getName运行时的位置

alert(xpg.getName());

显然,函数getName所在的对象是xpg,因此this的安身之处定然在xpg,即指向xpg对象,则getName返回的this.name其实是xpg.name,因此alert出来的是“局部”!

举个例子巩固一下:

var someone = {
 name: "Bob",
 showName: function(){
  alert(this.name);
 }
};

var other = {
 name: "Tom",
 showName: someone.showName
}

other.showName();//Tom

this关键字虽然是在someone.showName中声明的,但运行的时候是other.showName,所以this指向other.showName函数的当前对象,即other,故最后alert出来的是other.name。

2、闭包中的this

闭包也是个不安分子,本文暂且不对其过于赘述,简而言之:所谓闭包就是在一个函数内部创建另一个函数,且内部函数访问了外部的变量。
浪子this与痞子闭包混在一起,可见将永无宁日啊!

var name="全局";
 var xpg={
  name:"局部",
  getName:function(){
   return function(){
    return this.name;
   };
  }
 };
 alert(xpg.getName()());

此时的this明显身处困境,竟然处在getName函数中的匿名函数里面,而该匿名函数又调用了变量name,因此构成了闭包,即this身处闭包中。
无论this身处何处,一定要找到函数运行时的位置。此时不能根据函数getName运行时的位置来判断,而是根据匿名函数的运行时位置来判断。

function (){
 return this.name;
};

显然,匿名函数所在的对象是window,因此this的安身之处定然在window,则匿名函数返回的this.name其实是window.name,因此alert出来的就是“全局”!

那么,如何在闭包中使得this身处在xpg中呢?—缓存this

var name="全局";
 var xpg={
  name:"局部",
  getName:function(){
   var that=this;
   return function(){
    return that.name;
   };
  }
 };
 alert(xpg.getName()());

在getName函数中定义that=this,此时getName函数运行时位置在

alert(xpg.getName());

则this指向xpg对象,因此that也指向xpg对象。在闭包的匿名函数中返回that.name,则此时返回的that.name其实是xpg.name,因此就可以alert出来 “局部”!

3、new关键字创建新对象

new关键字后的构造函数中的this指向用该构造函数构造出来的新对象:

function Person(__name){
 this.name = __name;  //这个this指向用该构造函数构造的新对象,这个例子是Bob对象
}
Person.prototype.show = function(){
 alert(this.name); //this 指向Person,this.name = Person.name;
}

var Bob = new Person("Bob");
Bob.show();  //Bob

4、call与apply中的this

在JavaScript中能管的住this的估计也就非call与apply莫属了。
call与apply就像this的父母一般,让this住哪它就得住哪,不得不听话!当无参数时,当前对象为window

var name="全局";
var xpg={
 name:"局部"
};
function getName(){
 alert(this.name);
}
getName(xpg);
getName.call(xpg);
getName.call();

其中this身处函数getName中。无论this身处何处,一定要找到函数运行时的位置。此时函数getName运行时的位置

getName(xpg);

显然,函数getName所在的对象是window,因此this的安身之处定然在window,即指向window对象,则getName返回的this.name其实是window.name,因此alert出来的是“全局”!

那么,该call与apply登场了,因为this必须听他们的指挥!
getName.call(xpg);
其中,call指定this的安身之处就是在xpg对象,因为this被迫只能在xpg那安家,则此时this指向xpg对象, this.name其实是xpg.name,因此alert出来的是“局部”!

5、eval中的this

对于eval函数,其执行时候似乎没有指定当前对象,但实际上其this并非指向window,因为该函数执行时的作用域是当前作用域,即等同于在该行将里面的代码填进去。下面的例子说明了这个问题:

var name = "window";

var Bob = {
 name: "Bob",
 showName: function(){
  eval("alert(this.name)");
 }
};

Bob.showName(); //Bob

6、没有明确的当前对象时的this

当没有明确的执行时的当前对象时,this指向全局对象window。
例如对于全局变量引用的函数上我们有:

var name = "Tom";

var Bob = {
 name: "Bob",
 show: function(){
  alert(this.name);
 }
}

var show = Bob.show;
show();//Tom

你可能也能理解成show是window对象下的方法,所以执行时的当前对象时window。但局部变量引用的函数上,却无法这么解释:

var name = "window";

var Bob = {
 name: "Bob",
 showName: function(){
  alert(this.name);
 }
};

var Tom = {
 name: "Tom",
 showName: function(){
  var fun = Bob.showName;
  fun();
 }
};

Tom.showName();//window

在浏览器中setTimeout、setInterval和匿名函数执行时的当前对象是全局对象window,这条我们可以看成是上一条的一个特殊情况。

var name = "Bob"; 
var nameObj ={ 
  name : "Tom", 
  showName : function(){ 
   alert(this.name); 
  }, 
  waitShowName : function(){ 
   setTimeout(this.showName, 1000); 
  } 
 }; 

 nameObj.waitShowName();

所以在运行this.showName的时候,this指向了window,所以最后显示了window.name。

7、dom事件中的this

(1)你可以直接在dom元素中使用

<input id="btnTest" type="button" value="提交" onclick="alert(this.value))" />

分析:对于dom元素的一个onclick(或其他如onblur等)属性,它为所属的html元素所拥有,直接在它触发的函数里写this,this应该指向该html元素。

(2)给dom元素注册js函数
a、不正确的方式

<script type="text/javascript">
 function thisTest(){
 alert(this.value); // 弹出undefined, this在这里指向??
}
</script>
<input id="btnTest" type="button" value="提交" onclick="thisTest()" />

分析:onclick事件直接调用thisTest函数,程序就会弹出undefined。因为thisTest函数是在window对象中定义的,
所以thisTest的拥有者(作用域)是window,thisTest的this也是window。而window是没有value属性的,所以就报错了。
b、正确的方式

<input id="btnTest" type="button" value="提交" />

<script type="text/javascript">
 function thisTest(){
 alert(this.value); 
}
document.getElementById("btnTest").onclick=thisTest; //给button的onclick事件注册一个函数
</script>

分析:在前面的示例中,thisTest函数定义在全局作用域(这里就是window对象),所以this指代的是当前的window对象。而通过document.getElementById(“btnTest”).onclick=thisTest;这样的形式,其实是将btnTest的onclick属性设置为thisTest函数的一个副本,在btnTest的onclick属性的函数作用域内,this归btnTest所有,this也就指向了btnTest。其实如果有多个dom元素要注册该事件,我们可以利用不同的dom元素id,用下面的方式实现:

document.getElementById("domID").onclick=thisTest; //给button的onclick事件注册一个函数。

因为多个不同的HTML元素虽然创建了不同的函数副本,但每个副本的拥有者都是相对应的HTML元素,各自的this也都指向它们的拥有者,不会造成混乱。
为了验证上述说法,我们改进一下代码,让button直接弹出它们对应的触发函数:

<input id="btnTest1" type="button" value="提交1" onclick="thisTest()" />
<input id="btnTest2" type="button" value="提交2" />

<script type="text/javascript">
function thisTest(){
this.value="提交中";
}
var btn=document.getElementById("btnTest1");
alert(btn.onclick); //第一个按钮函数

var btnOther=document.getElementById("btnTest2");
btnOther.onclick=thisTest;
alert(btnOther.onclick); //第二个按钮函数
</script>
其弹出的结果是:

//第一个按钮
function onclick(){
 thisTest()
}

//第二个按钮
function thisTest(){
 this.value="提交中";
}

从上面的结果你一定理解的更透彻了。
By the way,每新建一个函数的副本,程序就会为这个函数副本分配一定的内存。而实际应用中,大多数函数并不一定会被调用,于是这部分内存就被白白浪费了。所以我们通常都这么写:

<input id="btnTest1" type="button" value="提交1" onclick="thisTest(this)" />
<input id="btnTest2" type="button" value="提交2" onclick="thisTest(this)" />
<input id="btnTest3" type="button" value="提交3" onclick="thisTest(this)" />
<input id="btnTest4" type="button" value="提交4" onclick="thisTest(this)" />

<script type="text/javascript">
 function thisTest(obj){
 alert(obj.value); 
}
</script>

这是因为我们使用了函数引用的方式,程序就只会给函数的本体分配内存,而引用只分配指针。这样写一个函数,调用的地方给它分配一个(指针)引用,这样效率就高很多。当然,如果你觉得这样注册事件不能兼容多种浏览器,可以写下面的注册事件的通用脚本:

//js事件 添加 EventUtil.addEvent(dom元素,事件名称,事件触发的函数名) 移除EventUtil.removeEvent(dom元素,事件名称,事件触发的函数名)
var EventUtil = new eventManager();

//js事件通用管理器 dom元素 添加或者移除事件
function eventManager() {
 //添加事件
 //oDomElement:dom元素,如按钮,文本,document等; ****** oEventType:事件名称(如:click,如果是ie浏览器,自动将click转换为onclick);****** oFunc:事件触发的函数名
 this.addEvent = function(oDomElement, oEventType, oFunc) {
  //ie
  if (oDomElement.attachEvent) {
   oDomElement.attachEvent("on" + oEventType, oFunc);
  }
  //ff,opera,safari等
  else if (oDomElement.addEventListener) {
   oDomElement.addEventListener(oEventType, oFunc, false);
  }
  //其他
  else {
   oDomElement["on" + oEventType] = oFunc;
  }
 }

 this.removeEvent = function(oDomElement, oEventType, oFunc) {
  //ie
  if (oDomElement.detachEvent) {
   oDomElement.detachEvent("on" + oEventType, oFunc);
  }
  //ff,opera,safari等
  else if (oDomElement.removeEventListener) {
   oDomElement.removeEventListener(oEventType, oFunc, false);
  }
  //其他
  else {
   oDomElement["on" + oEventType] = null;
  }
 }
}

正像注释写的那样,要注册dom元素事件,用EventUtil.addEvent(dom元素,事件名称,事件触发的函数名)即可,移除时可以这样写:EventUtil.removeEvent(dom元素,事件名称,事件触发的函数名),这是题外话,不说了。

以上就是本文的全部内容,希望通过这篇文章大家更加了解javascript的this关键字,大家共同进步。

Javascript 相关文章推荐
jQuery 鼠标经过(hover)事件的延时处理示例
Apr 14 Javascript
原生js获取iframe中dom元素--父子页面相互获取对方dom元素的方法
Aug 05 Javascript
webpack+vue.js实现组件化详解
Oct 12 Javascript
ES6中Iterator与for..of..遍历用法分析
Mar 31 Javascript
浅谈js-FCC算法Friendly Date Ranges(详解)
Apr 10 Javascript
简单谈谈React中的路由系统
Jul 25 Javascript
php main 与 iframe 相互通讯类(js+php同域/跨域)
Sep 14 Javascript
JavaScript数据结构之双向链表和双向循环链表的实现
Nov 28 Javascript
仿淘宝JSsearch搜索下拉深度用法
Jan 15 Javascript
vue项目使用$router.go(-1)返回时刷新原来的界面操作
Jul 26 Javascript
详解webpack的文件监听实现(热更新)
Sep 11 Javascript
36个正则表达式(开发效率提高80%)
Nov 17 Javascript
jQuery 1.9.1源码分析系列(十)事件系统之绑定事件
Nov 19 #Javascript
基于Jquery代码实现手风琴菜单
Nov 19 #Javascript
跟我学习javascript的作用域与作用域链
Nov 19 #Javascript
每天一篇javascript学习小结(属性定义方法)
Nov 19 #Javascript
理解 JavaScript Scoping &amp; Hoisting(二)
Nov 18 #Javascript
js立即执行函数: (function ( ){})( ) 与 (function ( ){}( )) 有什么区别?
Nov 18 #Javascript
z-blog SyntaxHighlighter 长代码无法换行解决办法(基于jquery)
Nov 18 #Javascript
You might like
php var_export与var_dump 输出的不同
2013/08/09 PHP
php分页代码学习示例分享
2014/02/20 PHP
PHP中isset与array_key_exists的区别实例分析
2015/06/02 PHP
ThinkPHP路由详解
2015/07/27 PHP
YII2框架中actions的作用与使用方法示例
2020/03/13 PHP
Prototype 学习 工具函数学习($w,$F方法)
2009/07/12 Javascript
js中将String转换为number以便比较
2014/07/08 Javascript
一个不错的字符串转码解码函数(自写)
2014/07/31 Javascript
jquery模拟进度条实现方法
2015/08/03 Javascript
jQuery实现鼠标经过购物车出现下拉框代码(推荐)
2016/07/21 Javascript
JS中如何实现复选框全选功能
2016/12/19 Javascript
javascript滚轮事件基础实例讲解(37)
2017/02/14 Javascript
详解vue2.0 transition 多个元素嵌套使用过渡
2017/06/19 Javascript
Mac下安装vue
2018/04/11 Javascript
搭建vue开发环境
2018/07/19 Javascript
vue项目实现表单登录页保存账号和密码到cookie功能
2018/08/31 Javascript
《javascript设计模式》学习笔记五:Javascript面向对象程序设计工厂模式实例分析
2020/04/08 Javascript
[02:20]DOTA2亚洲邀请赛 EHOME战队出场宣传片
2015/02/07 DOTA
python二叉树遍历的实现方法
2013/11/21 Python
python实现dict版图遍历示例
2014/02/19 Python
python非递归全排列实现方法
2017/04/10 Python
利用Python查看目录中的文件示例详解
2017/08/28 Python
python pandas 对series和dataframe的重置索引reindex方法
2018/06/07 Python
Python DataFrame.groupby()聚合函数,分组级运算
2018/09/18 Python
详解用Python练习画个美队盾牌
2019/03/23 Python
Python使用MyQR制作专属动态彩色二维码功能
2019/06/04 Python
Python sqlalchemy时间戳及密码管理实现代码详解
2020/08/01 Python
Python基于pillow库实现生成图片水印
2020/09/14 Python
Python更改pip镜像源的方法示例
2020/12/01 Python
ANINE BING官方网站:奢华的衣橱基本款和时尚永恒的单品
2019/11/26 全球购物
Java编程面试题
2016/04/04 面试题
在校学生职业规划范文
2014/01/08 职场文书
车队安全员岗位职责
2015/02/15 职场文书
暑假打工感想
2015/08/07 职场文书
帮你提高开发效率的JavaScript20个技巧
2021/06/18 Javascript
django 认证类配置实现
2021/11/11 Python