JavaScript奥秘之让人捉摸不定的this

开发 前端
俗话说的好,半灌水响叮当,我就那种一直认为自己js功底好的人,但真的拿出手来说,其实真的有点水了......此时再不好好学习一番,岂不是坐井观天,所以让我们开动吧!

前言

之所以会有此篇文章当然还要从最近的一次面试说起,很抱歉居然又扯到面试上去看,其实不要说,平时不注意的东西,往往在面试时便会立马给你揪出来哪里有问题。

比如我当时就小小的栽了个跟头,栽跟头不要紧,要紧的是我确实对js的一些问题没有了解透彻。

俗话说的好,半灌水响叮当,我就那种一直认为自己js功底好的人,但真的拿出手来说,其实真的有点水了......此时再不好好学习一番,岂不是坐井观天,所以让我们开动吧!

小弟最近的文章基本都是边写边发,若是各位发现什么问题,或者感觉废话太多,请包涵。

闲扯作用域

你了解javascript的作用域吗?真的了解吗?那来试试这道题吧:

  1. if (!("a" in window)) {  
  2.     var a = 1;  
  3. }   
  4. alert(a); 

好吧,拿出你的答案吧,吾已经露出了邪恶的笑容了,因为多数人看着这道题脑壳就有点昏(我会说我也有点昏吗???)
让我们一起来剥离她性感的外衣吧:

第一步:"a" in window这是什么意思?

意思是a是window的属性吗?那我们来做个试验:

我那个去,你会发现不管注释var a 还是不注释,a都是window的属性......于是上面答案呼之欲出!!!但我一团浆糊在我们脑袋中膨胀扩散......

在js的变量作用域中有个原则:所有变量声明都在范围作用域的顶部!

所以,之前我犯了一个愚蠢的错误,错的连我也吃惊,所以我把我自己喝大家都忽悠了,注意陷阱:

其实刚刚上面的完整代码是这样的:

  1. <script type="text/javascript">  
  2.       var a;  
  3.     var in_window = 'a' in window;  
  4.     alert(in_window);  
  5.     if (!("a" in window)) {  
  6.         var a = 1;  
  7.     }   
  8.     alert(a);  
  9. </script> 

这样的话:in_window自然是true,这才是隐藏的真相!!!

若是注释下面这些代码的话:

终于正确了,刚刚因为自己的一个错误差点颠覆我最近学习的东西,太可怕了!

现在我们来看看“所有变量申明都会在范围作用域的顶部”是什么意思。

意思是在最下面定义的变量会自动提到上面去啦!!!所以我们定义变量时不如直接全部定义上去算啦。

回到本题:

  1. if (!("a" in window)) {  
  2.     var a = 1;  
  3. }   
  4. alert(a); 

其实他该是这个样子的。。。。

  1. var a;  
  2. if (!("a" in window)) {  
  3.     a = 1;  
  4. }   
  5. alert(a); 

他将if里面的申明也提前了,怎么样不服气吧,其实我也是有点不服气,我想再试试:

  1. var s = '';  
  2. if (false) {  
  3.     var a = 1;  

请注意,其中s没有任何意义,就是为了我方便设置断点:

至此真相出现,无论如何a的申明都会提前包括以下几种情况:

  1. var s = '';while (false) {    var a = 1;} 

变形一

学而不思则罔,我们将题目做个简单变形看看:

  1. if (!("a" in window)) {  
  2.     a = 1;  
  3. }  
  4. alert(a); 

在if里面去掉了申明,这道题就该是“1”了,但是若是if里面的代码不被执行的话就会报错了哟;

变形2:碰上了函数

刚刚那个现在看来就相对简单了,现在我们看看如此如此这般这般又会如何(我承认我闲的蛋疼好了)?

  1. if (!("a" in window)) {  
  2.     var a = function () { window.a = 1; }  
  3. }  
  4. alert(a); 

这样一改真的很蛋疼啦,这里不管a被定义为什么,但他是函数表达式,函数表达式就和原来一样,所以不变,if里面不会被执行!

那若是这个样子呢?

  1. if (!("a" in window)) {  
  2.     function a() { window.a = 1; }  
  3. }  
  4. alert(a); 

这个场景其实我也傻了,那么设置个断点看看:

看来a并不在window中,所以会执行if中的代码;

这里却又引出了另一个问题:到底变量提前或者函数提前?

  1. var a = '1';  
  2. function a(){}  
  3. alert(a); 
  1. function a() { }  
  2. var a = '1';  
  3. alert(a); 

这两种写法会导致最后输出有所不同吗???

答案是不会,他们的的结果都是1,原因就是函数式申明更加被优先啦,所以无论怎么写函数式什么都在最前面!!

  1. function a() {return false; }  
  2. if (a()) {  
  3.     var a = '1';  
  4. }  
  5. s = '';  
  6. alert(a); 
  1. function a() {return  true; }  
  2. if (a()) {  
  3.     var a = '1';  
  4. }  
  5. s = '';  
  6. alert(a); 

我们前面说过,无论如何,if里面的申明会提前,那么我们这两道题可以改写一下

  1. var a = function () { return true };  
  2. var a;  
  3. if (a()) {  
  4.     a = '1';  
  5. }  
  6. s = '';  
  7. alert(a); 

注意来:这里的10行,虽说申明了变量a却没有给其赋值,所以a还是函数,这从这里也可以看出来:

所以上面两个答案就没问题了,一个打印函数,一个打印数字1;

变形三

  1. var a = 1,  
  2. b = function a(x) { x && b(--x); };   
  3. alert(a); 

现在我不运行代码试试是否可以解析,答案是不可以。。。我解析不出来,还是运行算了吧,我太水了!

这里涉及几个重要概念:

  1. 1 变量声明在进入执行上下文就完成了  
  2. 2 函数声明也是提前的,所有的函数声明都在执行代码之前都已经完成了声明,和变量声明一样  
  3. 3 函数声明会覆盖变量声明,但不会覆盖变量赋值,如我们上面看到的 

想要理清问题,我还是老老实实一步步做工作吧:

根据规则三,这个结果是没有问题的,再看看下面的

从这里可以看出,若是注释了var a,这里function a()压根与它没什么事情,我们可以直接将之忽略(可能有误)

所以该题可以理解为:

  1. var a = 1,  
  2. b = function (x) { x && b(--x); };   
  3. alert(a); 

坑爹的我本来是想对js中的this做次研究的,没想到在作用域相关的东西上转了这么久,但是经过这次折腾我相信在这块地方我应该不会出问题了吧???

#p#

进入正题

通常情况下, this代表的是前面提到的Globle Object,也就是Browser环境时的window Object.

当function作为某一对象的 method 时, this 代表这个 function 所属的 object

其实这里有个原则“javascript中的this永远指向其函数拥有者”或者“this指针代表的是执行当前代码的对象的所有者”

说到javascript中的this就不能不提javascript中的函数,说到javascript中的函数就会涉及到闭包,我今天就以以下几个方面研究下javascript中的this:

1 一般情况下的this

2 对象调用中的this

3 函数调用中的this

4 页面dom事件对象中的this

5 大杂烩

正常情况下的this

正常情况下的this就是windows,我会乱说?

  1. var a = 1;  
  2. alert(window.a); 

申明a其实就在window上申明的,其实我各位说这些干嘛呢。。。。大家都知道呀,所以跳过算啦。

对象中的this

对象中的this情况相对复杂一点可能和我们后面的原型扯上关系,至于原因我们后面再说,先看看下面这个例子:

  1. var person = {  
  2.     name: '叶小钗',  
  3.     getName: function () {  
  4.         var scope = this;  
  5.         alert(scope.name);  
  6.         var s = ''//无实际意义,用于设置断点  
  7.     }  
  8. };  
  9. person.getName(); 

这个例子亦很平常,仿佛一切理所当然,当我拿出来说是想证明一件事情“this指向其函数拥有者”,这次拥有者为person所以打印叶小钗理所当然

我们简单做下变形:

  1. var name = '素还真';  
  2. function global_getName() {  
  3.     var scope = this;  
  4.     return scope.name;  
  5.     var s = ''//无实际意义,用于设置断点  
  6. }  
  7. var person = {  
  8.     name: '叶小钗',  
  9.     getName: function () {  
  10.         var scope = this;  
  11.         return scope.name;  
  12.         var s = ''//无实际意义,用于设置断点  
  13.     }  
  14. };  
  15. var s1 = global_getName();  
  16. var s2 = person.getName();  
  17.  
  18. var s = ''

也没任何问题,好的我们再做一次变形:

  1. var name = '素还真';  
  2. function global_getName() {  
  3.     var scope = this;  
  4.     return scope.name;  
  5.     var s = ''//无实际意义,用于设置断点  
  6. }  
  7. var person = {  
  8.     name: '叶小钗',  
  9.     getName: global_getName  
  10. };  
  11. var s1 = global_getName();  
  12. var s2 = person.getName();  
  13.  
  14. var s = ''

我们看到getName被指向了global_getName,那我们再调用person.getName会不会有所不同呢?

当然不会了!我发现我自己被自己感动了,完全在说一些没意义的东西,所以下面这种情况是不可能骗到大家的,我就不绕弯子了:

  1. var name = '素还真';  
  2. function global_getName() {  
  3.     var scope = this;  
  4.     return scope.name;  
  5.     var s = ''//无实际意义,用于设置断点  
  6. }  
  7. var person = {  
  8.     name: '叶小钗',  
  9.     getName: global_getName  
  10. };  
  11. var hehe = {  
  12.     name: '笑吧',  
  13.     getName: person.getName  
  14. };  
  15.  
  16. var s1 = global_getName();//素还真  
  17. var s2 = person.getName();//叶小钗  
  18. var s3 = hehe.getName(); //笑吧 

函数中的this

要说函数的话,我先说说javascript中函数调用有四种方式和函数中this的相关东东:

  1. 1 方法调用模式:作为对象属性调用obj.func()  
  2. 2 函数调用模式:指向全局,直接调用 func()  
  3. 3 构造器调用模式:new方式调用,会隐式创建一个新对象其隐藏链接至函数prototype成员  
  4. 4 call/apply调用模式:动态改变this指向的怪咖 
  1. 函数代码中this值的第一个特性(同时也是最主要的特性)就是:它并非静态的绑定在函数上。  
  2.  
  3. 正如此前提到的,this的值是在进入执行上下文(Excution context)的阶段确定的,并且在函数代码中的话,其值每次都不尽相同。  
  4.  
  5. 然而,一旦进入执行代码阶段,其值就不能改变了。如果要想给this赋一个新的值是不可能的,因为在那时this根本就不是变量了。 

对于函数中this的设定被认为是一个失误,但谁管他那么多呢。。。 

现在回到正文,请看以下例子:

  1. function func() {  
  2.     var scope = this;  
  3.     function s_func() {  
  4.         var s_scope = this;  
  5.         var s1 = scope;  
  6.         var s2 = s_scope;  
  7.         var s = '';  
  8.     }  
  9.     s_func();  
  10. }  
  11. this.func(); 

这里s1,s2都指向全局,这是为什么呢?我也不知道了,这里就去查询了下资料:

“ 函数上下文中this的值是函数调用者提供并且由当前调用表达式的形式而定的。如果在调用括号()的左边有引用类型的值,那么this的值就会设置为该引用类型值的base对象。 所有其他情况下(非引用类型),this的值总是null。然而,由于null对于this来说没有任何意义,因此会隐式转换为全局对象。“——Javascript this 的一些学习总结”当调用括号的左边不是引用类型而是其它类型,这个值自动设置为null,结果为全局对象。“——深入理解JavaScript系列(13):This? Yes,this!

我们随便看一个变形:

  1. var person = {  
  2.     name: '叶小钗',  
  3.     func: func  
  4. };  
  5. function func() {  
  6.     var scope = this;  
  7.     function s_func() {  
  8.         var s_scope = this;  
  9.         var obj = {};  
  10.         obj.s1 = scope;  
  11.         obj.s2 = s_scope;  
  12.         return obj;  
  13.     }  
  14.     return s_func ();  
  15. }  
  16.  
  17. var o = this.func();  
  18. var o2 = person.func();  
  19. var s = ''

这个应该比较好理解这边就不说了。

#p#

构造函数

以上说那么多绕过去绕过来的大家一定累了吧,好吧,开心点,我们看看下面这个东西,我想你心情会好起来的!!!

  1. var name = 'window_叶小钗';  
  2.  var Person = function (name) {  
  3.      this.name = name;  
  4.  };  
  5.    
  6.  Person('叶小钗');  
  7.  var s1 = Person.name;  
  8.  var o = new Person();  
  9.    
  10.  var s = ''

这是一段可怕的代码,Person('叶小钗')事实上改变不了任何事情,但从某种意义上来说Person也是对象,

所以他确确实实是存在name这个值的:

因为this.name是属于该对象的子对象的属性,若不实例化便休想染指

这个样子写又会有所不同,而且又产生了诡异的一幕:

  1. var name = 'window_叶小钗';  
  2. var Person = function (name) {  
  3.     this.name = name;  
  4. };  
  5. Person.name = name;  
  6. Person.name1 = name;  
  7. var o = new Person('叶小钗');  
  8. var s1 = Person.name;  
  9. var s2 = Person.name1;  
  10. var s3 = o.name;  
  11. var s = ''

我们发现,由于Person中定义了this.name,外面无论怎么赋值都无效啦!

关于动态指定this的call/apply

每个函数都有一个方法call可以动态指定this指向,第一个参数便是this指向,不传或者null更具前面说的便是window,第二个参数为参数:

  1. var name = 'window_叶小钗';  
  2.  function getName() {  
  3.      return this.name;  
  4.  }  
  5.  var person = { name: '叶小钗' };  
  6.  var s1 = getName();  
  7.  var s2 = getName.call();  
  8.  var s3 = getName.call(null);  
  9.  var s4 = getName.call(undefined);  
  10.  var s5 = getName.call(person);  
  11.  var s = ''

dom事件中的this

  1. <div id="wl" onclick="test_this(this)">  
  2.     点击我试试</div>  
  3. <script type="text/javascript">  
  4.     function test_this(el) {  
  5.         var scope = this;  
  6.         var s = '';  
  7.     }  
  8. </script> 

我们看到el是dom对象,this指向的是window,那么换个写法看看:

  1. <div id="wl">  
  2.     点击我试试</div>  
  3. <script type="text/javascript">  
  4.     function test_this(e) {  
  5.         var scope = this;  
  6.         var s = '';  
  7.     }  
  8.     document.getElementById('wl').onclick = test_this;  
  9. </script> 

这个样子,this指向的是dom对象,我们再试试:

  1. <div id="wl">  
  2.     点击我试试</div>  
  3. <script type="text/javascript">  
  4.     function test_this(e) {  
  5.         var scope = this;  
  6.         var s = '';  
  7.     }  
  8.     document.getElementById('wl').addEventListener('click', test_this, false);  
  9. </script> 

这样结果与上面相同。

检测学习成果的时间

现在,我感觉我对javascript中的this好像比较清楚了,是吗??来大家一起来做两道题吧,大家记得独立做下哦:

作用域:

  1. a = 2;  
  2. var a = 1;  
  3. alert(a); 
  1. (function () {  
  2.     baz = 5;  
  3.     var bar = 10;  
  4. })();  
  5. alert(baz); 

this:

确实想不到合适的了,各位园友给推荐两个呗。

结语

通过此次的研究学习,我感觉我对this的了解相对深入一点了,在后面工作中再注意总结一番,我相信我可以完全掌握this的特性!

小弟现在还很水,又是边写边发文,若是有不对或是理解有误的地方还请各位大侠提出来哟。

原文链接:http://www.cnblogs.com/yexiaochai/archive/2013/04/22/3034949.html

责任编辑:张伟 来源: 博客园
相关推荐

2024-05-09 07:58:59

PostgreSQL数据库统计信息

2013-04-25 09:12:36

2018-06-29 14:37:01

Intel服务器路线图

2015-06-30 19:21:22

免费OA

2023-01-06 07:37:08

JavaScript技巧t性能

2010-09-29 09:41:55

WiFi

2012-03-23 14:38:31

JavaScript

2021-12-09 11:59:49

JavaScript前端提案

2015-07-17 11:52:14

2012-06-21 08:36:06

LinuxWindows

2023-10-11 13:13:46

​PostmanJavaScrip

2011-07-08 15:41:43

oracle job

2009-08-14 09:19:15

Windows 7XP模式优缺点

2021-06-15 15:33:36

存储选型系统

2018-11-26 07:04:59

神经网络优化函数

2018-05-11 09:40:10

服务器运维运营商

2016-07-14 09:59:28

超融合''桌面云'融合架构''

2015-09-21 14:44:54

物联网

2011-09-15 09:19:30

2011-09-16 14:34:20

点赞
收藏

51CTO技术栈公众号