JS中的this用法详解
随着对js的深入学习和使用,你会发现它里面包含了很多令人困惑的机制,比如对象、闭包、原型链继承等等,而这其中肯定包含令你现在或者曾经费解的this,如果你不把心一横,花点时间还真不明白这个this的用法,下面这篇文章来详细探讨下:
1、this是啥?
简言之,this是JavaScript语言中定义的众多关键字之一,它的特殊在于它自动定义于每一个函数域内,但是this到底指啥东西却让很多人张二摸不着头脑。这里我们留个小悬念,希望看完这篇文章了你能回答出来this到底指什么。
2、this有啥用?
那边观众又该为了,既然this这么难以理解,那么为个甚还要用它呢?我们来看个例子:
function identify() { return this .name.toUpperCase(); } function sayHello() { var greeting = "Hello, I'm " + identify.call( this ); console.log( greeting ); } var person1= { name: "Kyle" }; var person2= { name: "Reader" }; identify.call( person1); // KYLE identify.call( person2); // READER sayHello.call( person1); // Hello, I'm KYLE sayHello.call( person2); // Hello, I'm READER |
这段代码很简单,我们定义了两个函数,分别为identify和sayHello。并且在不同的对象环境下执行了它们,达到了复用的效果,而不用为了在不同的对象环境下执行而必须针对不同的对象环境写对应的函数了。简言之,this给函数带来了复用。那边客官又问了,我不用this一样可以实现,如:
function identify(context) { return context.name.toUpperCase(); } function sayHello(context) { var greeting = "Hello, I'm " + identify( context); console.log( greeting ); } var person1= { name: "Kyle" }; var person2= { name: "Reader" }; identify( person1); // KYLE identify( person2); // READER sayHello( person1); // Hello, I'm KYLE sayHello( person2); // Hello, I'm READER |
仔细一看,这位客官给出的解决方法的确也达到了类似的效果。赞一个!我想说的是,随着代码的增加,函数嵌套、各级调用等变得越来越复杂,那么传递一个对象的引用将变得越来越不明智,它会把你的代码弄得非常乱,甚至你自己都无法理解清楚。而this机制提供了一个更加优雅而灵便的方案,传递一个隐式的对象引用让代码变得更加简洁和复用。好了,知道了this的用处,那么再看看我们对它的误解。
3、关于this的误解
相信很多童鞋是学过其它语言的,在很多编程语言中都有this的机制,惯性思维把其它语言里对它的理解带到了JavaScript中。同时,由于this这个单词的理解导致了我们产生了对它各种各样的误解。所以,开始前,我们先澄清下对它的误解。
3.1 误解一:this引用function本身
我们都知道,在函数里引用函数可以达到递归和给函数属性赋值的效果。而这在很多应用场景下显得非常有用。所以,很多人都误以为this就是指引function本身。例如:
function fn(num) { console.log( "fn: " + num ); // count用于记录fn的被调用次数 this .count++; } fn.count = 0; var i; for (i=0; i<10; i++) { if (i > 5) { fn( i ); } } // fn: 6 // fn: 7 // fn: 8 // fn: 9 console.log( fn.count ); // 0 -- 耶?咋不是4捏? |
上面我们想要记录fn被调用的次数,可是明显fn被调用了四次但count仍然为0。咋回事捏?这里简单解释下,fn里第4行的自增隐式的创建了一个全局变量count,由于初始值为undefined,所以每一次自增其实依然不是一个数字,你在全局环境下打印count(window.count)输出的应该是NaN。而第6行定义的函数熟悉变量count依然没变,还是0。如果对这个执行结果不清楚的,欢迎去看我前些天的那篇博文(聊一下JS中的作用域scope和闭包closure scope和closure),在这里你只需要知道,this引用的是function这种理解是错误的就行。
这边就会又有人问了,既然this不是引用function,那么我要实现递归函数,该咋引用呢?这里简单回答下介个问题,两种方法:①函数体内用函数名来引用函数本身②函数体内使用arguments.callee来引用函数(不推荐)。那么既然第二种方法不推荐,匿名函数咋引用呢?用第一种,并且给匿名函数一个函数名即可(推荐)。
3.2 误解二:this引用的是function的词法作用域
这种误解欺骗的人可能更多一些。首先,澄清一下,this并没有引用function的词法作用域。的确JS的引擎内对词法作用域的实现的确像是一个对象,拥有属性和函数,但是这仅仅是JS引擎的一种实现,对代码来说是不可见的,也就是说词法作用域“对象”在JS代码中取不到。(关于词法作用域,如果不理解,可以参考之前的一篇博文《聊一下JS中的作用域scope和闭包closure scope和closure》)。看个错误的例子:
function fn1() { var a = 2; this .fn2(); //以为this引用的是fn1的词法作用域 } function fn2() { console.log( this .a ); } fn1(); //ReferenceError |
上面的代码明显没有执行出想要的结果,从而可以看到this并没有引用函数的词法作用域。甚至,可以肯定的说,这个例子里fn2可以在fn1里正确执行都是偶然的(理解了词法作用域你就知道为什么这里执行不报错了)。
4、this到底跟啥有关?
好了,扯了那么多都没上干货,有的观众都开始关闭当前页开始离席了。这里,我们郑重声明:this跟函数在哪里定义没有半毛钱关系,函数在哪里调用才决定了this到底引用的是啥。也就是说this跟函数的定义没关系,跟函数的执行有大大的关系。所以,记住,“函数在哪里调用才决定了this到底引用的是啥”。
5、this机制的四种规则
this到底绑定或者引用的是哪个对象环境决定于函数被调用的地方。而函数的调用有不同的方式,在不同的方式中调用决定this引用的是哪个对象是由四种规则确定的。我们一个个来看。
5.1 默认绑定全局变量
这条规则是最常见的,也是默认的。当函数被单独定义和调用的时候,应用的规则就是绑定全局变量。如下:
function fn() { console.log( this .a ); } var a = 2; fn(); // 2 -- fn单独调用,this引用window |
5.2 隐式绑定
隐式调用的意思是,函数调用时拥有一个上下文对象,就好像这个函数是属于该对象的一样。例如:
function fn() { console.log( this .a ); } var obj = { a: 2, fn: fn }; obj.fn(); // 2 -- this引用obj。 |
需要说明的一点是,最后一个调用该函数的对象是传到函数的上下文对象(绕懵了)。如:
function fn() { console.log( this .a ); } var obj2 = { a: 42, fn: fn }; var obj1 = { a: 2, obj2: obj2 }; obj1.obj2.fn(); // 42 -- this引用的是obj2. |
还有一点要说明的是,失去隐式绑定的情况,如下:
function fn() { console.log( this .a ); } var obj = { a: 2, fn: fn }; var bar = obj.fn; // 函数引用传递 var a = "全局" ; // 定义全局变量 bar(); // "全局" |
如上,第8行虽然有隐式绑定,但是它执行的效果明显是把fn赋给bar。这样bar执行的时候,依然是默认绑定全局变量,所以输出结果如上。
5.3 显示绑定
学过bind()\apply()\call()函数的都应该知道,它接收的第一个参数即是上下文对象并将其赋给this。看下面的例子:
function fn() { console.log( this .a ); } var obj = { a: 2 }; fn.call( obj ); // 2 |
如果我们传递第一个值为简单值,那么后台会自动转换为对应的封装对象。如果传递为null,那么结果就是在绑定默认全局变量,如:
function fn() { console.log( this .a ); } var obj = { a: 2 }; var a = 10; fn.call( null ); // 10 |
5.4 new新对象绑定
如果是一个构造函数,那么用new来调用,那么绑定的将是新创建的对象。如:
function fn(a) { this .a = a; } var bar = new fn( 2 ); console.log( bar.a ); // 2 |
注意,一般构造函数名首字母大写,这里没有大写的原因是想提醒读者,构造函数也是一般的函数而已。
6、结束语
读到现在,1中问的问题你该自己能回答上来了。上面介绍的四种关于this绑定的4中情况和规则,现实写代码的过程中肯定比这要多和复杂,但是无论多复杂多乱,它们都是混合应用上面的几个规则和情况而已。只要你的思路和理解是清晰的,那肯定没问题的。
转载自w3cmark (http://www.w3cmark.com/2015/410.html)。第一次写博客不知道转载说明的格式,请作者见谅。有好的文章分享下来,方便自己,方便他人。
JS中的this用法详解的更多相关文章
- JS中setTimeout()的用法详解
1. SetTimeOut() 1.1 SetTimeOut()语法例子 1.2 用SetTimeOut()执行Function 1.3 SetTimeOut()语法例子 1.4 设定条件使SetTi ...
- js中this关键字用法详解
1.全局环境中的this 在全局环境中,this 指向全局对象Global,即 window 对象 如: alert(this); // 显示 [object Window] alert(this = ...
- JavaScript中return的用法详解
JavaScript中return的用法详解 最近,跟身边学前端的朋友了解,有很多人对函数中的this的用法和指向问题比较模糊,这里写一篇博客跟大家一起探讨一下this的用法和指向性问题. 1定义 t ...
- JavaScript中this的用法详解
JavaScript中this的用法详解 最近,跟身边学前端的朋友了解,有很多人对函数中的this的用法和指向问题比较模糊,这里写一篇博客跟大家一起探讨一下this的用法和指向性问题. 1定义 thi ...
- C#中string.format用法详解
C#中string.format用法详解 本文实例总结了C#中string.format用法.分享给大家供大家参考.具体分析如下: String.Format 方法的几种定义: String.Form ...
- c++中vector的用法详解
c++中vector的用法详解 vector(向量): C++中的一种数据结构,确切的说是一个类.它相当于一个动态的数组,当程序员无法知道自己需要的数组的规模多大时,用其来解决问题可以达到最大节约空间 ...
- JS中的event 对象详解
JS中的event 对象详解 JS的event对象 Event属性和方法:1. type:事件的类型,如onlick中的click:2. srcElement/target:事件源,就是发生事件的 ...
- js中鼠标滚轮事件详解
js中鼠标滚轮事件详解 (以下内容部分内容参考了http://adomas.org/javascript-mouse-wheel/ ) 之前js 仿Photoshop鼠标滚轮控制输入框取值中已使用 ...
- php中setcookie函数用法详解(转)
php中setcookie函数用法详解: php手册中对setcookie函数讲解的不是很清楚,下面是我做的一些整理,欢迎提出意见. 语法: bool set ...
随机推荐
- TFS 2010 使用手册(三)权限管理
本文参考了 蔚蓝的宁静 http://www.cnblogs.com/tymo/archive/2011/03/21/1990550.html 等文章. 1.权限说明 1.1 权限关联 TFS的权限与 ...
- 【JEMTER】后置处理器JSON Path Extractor获取server端返回的json中某项值
需求1:点击所有报表模板时,server端返回所有报表模板的ID(templateId),测试时需要下载某个模板生成的报表 需求2:点击单个报表模板时,server端返回这个报表模板下的所有报表ID( ...
- 《算法导论》习题解答 Chapter 22.1-4(去除重边)
思路:重开一个新图,按着邻接列表的顺序从上到下遍历,每遍历一行链表前,清空visited数组,如果没有访问过这个元素,则加入新图,如果已经访问过了(重边),则不动. 伪代码: 复杂度:O(V+E) f ...
- [课程相关]附加题——stack的理解
一.stack的三种解释 stack有三种解释,我个人理解如下. 1.用户自定义的stack 用户自定义的stack就是一般意义上的后进先出队列,从名字上就能理解了,stack由下向上增长,有一个顶指 ...
- jQuery阻止事件冒泡的例子
下面给给各位朋友稍加整理了一jquery中阻止事件冒泡的一些例子,我们知道JQuery 提供了两种方式来阻止事件冒泡,但我们简单的利用它来做一些应用可能不深入或不理解,下面整理了更详细的方法,有兴趣的 ...
- shell配置环境变量
我们在使用Xshell开发机时往往会遇到一些问题,比如使用某些命令他会报command not found,,出现这种情况的原因是我们必须要配置一下环境变量,这样就可以在任何path中访问了. 这就需 ...
- Umbraco(5)-Creating Master Template Part 1(翻译文档)
原文地址:http://www.ncloud.hk/%E6%8A%80%E6%9C%AF%E5%88%86%E4%BA%AB/umbraco5-creating-master-template-par ...
- CF Destroying Roads (最短路)
Destroying Roads time limit per test 2 seconds memory limit per test 256 megabytes input standard in ...
- HDU 2084 数塔 (DP)
数塔 Time Limit:1000MS Memory Limit:32768KB 64bit IO Format:%I64d & %I64u Submit Status Pr ...
- LeetCode 287
Find the Duplicate Number Given an array nums containing n + 1 integers where each integer is betwee ...