Javascript 中我很想说说的 this

this是每一个想要深入学习Javascript的人必过的一关,我为this看过很多书查过很多资料,虽然对this有了一定的了解并且也经常使用this,但是如果有人问我  this是什么呀? 我依旧不能给别人一个完美的解释。最近一个小的机缘,让我重新对this有了认识,终于觉得自己可以把我认识到的this将给别人听了,所以现在迫不及待的来分享一下我的认识

说到this,最重要的就是this的指向了(这样说并不准确,因为this只是函数被调用时所创建的活动对象中的一个属性而已)。

有些人可能认为this指向的是自身,因为this这个单词的含义就是如此嘛,不过这种认识应该是错误的,不信?来看代码~

1 function foo(num) {
2     console.log("foo" + num);
3     this.count++;
4 }
5 foo.count = 0;
6 for(var i = 0; i < 5; i++) {
7     foo(i);
8 }
9 console.log(foo.count);

对于第二行的输出肯定大家都是知道答案的

foo0
foo1
foo2
foo3
foo4

那么~第九行的输出是什么?(如果this指向的是自身的话foo被调用5次 foo.count应该是5才对,但是!)

事实上this.count并没有 ++ 由此可以说明,this并不是指向自身。

又会有人说,我知道他不是指向自身呀,他明明是指向作用域的嘛(好吧,很长一段时间我也是这么以为的)。不过事实上这也是不正确的,依旧,我们用一段代码来证明

1 function foo() {
2     var a = 2;
3     this.bar();
4 }
5 function bar() {
6     console.log(this.a);
7 }
8 foo();

这段代码第六行的输出会是什么呢?是2吗?哈哈,不要太天真呦~

哈哈,this并没有指向自身作用域呢~看到这里的你现在一定暴虐的想要知道那this到底是个什么鬼,那现在我们就开始一层层解析this是什么

想要知道函数在执行过程中是如何绑定this的,首先要知道函数的调用位置。可能很多人已经可以肯轻松的寻找出函数调用位置,那就轻轻松的看一下我的代码和你想出的答案是不是一样的吧

 1 function baz() {
 2     //当前调用栈:baz
 3    //调用位置是全局作用域
 4     console.log("baz");
 5     bar();//bar的调用位置
 6 }
 7 function bar() {
 8     //当前调用栈:baz -> bar
 9    //调用位置是baz中
10     console.log("bar");
11     foo();//foo的调用位置
12 }
13 function foo() {
14     //当前调用栈:baz -> bar -> foo
15    //调用位置是bar中
16     console.log("foo");
17 }
18 baz();//baz的调用位置

看一下上面的代码,从 baz(); 开始,baz在全局之中,之后baz调用 bar(); 所以bar的调用位置在baz之中,再然后bar调用 foo(); foo的调用位置在baz之中。执行foo函数是的调用栈为baz -> bar -> foo 。

通过函数的互相调用找出调用栈,便可以找出函数的真正调用位置,可是如果代码量过大可能这样人工查找很容易出错,所以~~我们有更好的方法。。。

上图右侧向上的箭头所滑过的就是foo函数执行的调用栈,栈中的第二个元素就是真正的调用位置。

找到调用位置之后我们来看一下this的绑定符合哪种绑定规则

1.隐式绑定: 看调用位置是否有上下文对象,即是否被某个对象拥有或包含(这里说的包含可不是用花括号包起来呦~)。

1 var a = 3;
2 function foo() {
3     console.log(this.a);
4 }
5 var obj = {
6     a: 2,
7     foo: foo
8 };
9 obj.foo();

上面又是一个函数调用里面输出this指向的一个值呢,先不说答案,先看一下,这段代码和上面指出this不是指向作用域的那段代码的区别是什么,我想大家都发现了,这次foo函数的调用是 obj.foo() 。foo函数被用作obj对象的一个方法来调用。很明显根据foo函数的声明位置来看他并不属于obj对象,但是~~哈哈,讲了好多回但是~~foo函数的调用位置是obj的上下文来引用函数,也就是说他的落脚点是obj对象,那~~他的this自然也就指向他的上下文对象。这样一分析,答案就显而易见了,this.a等同于obj.a就是2。

(ps:如果对象属性的引用是多层的,只有最后一层会影响调用位置)。

2.显式绑定:

上面的隐式绑定是通过调用一个对象中绑定了函数的属性来把this隐式的绑定在这个对象上的。所以显示绑定就是使用一些方法强制将函数绑定在某个对象上,这些方法就是 call(...)  apply(...) 这两种方法的工作方式是类似的,所以我们就以call()来作为例子分析一下他们的工作过程

这两个方法第一个参数是一个对象,而被绑定的函数的this就会绑定在这个对象上,看一段代码

1 function foo() {
2     console.log(this.a);
3 }
4 var obj = {
5     a: 2
6 };
7 foo.call(obj);

foo函数执行时使用了call函数,call函数的第一个参数是obj对象,则foo函数活动对象的this属性就被绑定在了obj对象上,所以,输出2.

3.new 绑定

使用 new 来调用函数,即发生构造函数调用时,会执行下面操作

  1. 创建一个全新的对象
  2. 这个对象会被执行[__proto__]的链接
  3. 这个新对象会绑定到函数调用的this上
  4. 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象

用一段代码来解释上面的操作( 原型的问题暂且不考虑 )

1 function Foo() {
2     this.a = a;
3 }
4 var bar = new Foo(2);
5 console.log(bar.a);

当使用new操作符执行Foo函数时,就创建了一个全新的对象并且赋值给了变量bar。进行原型链接之后(这里不是我们今天讨论的范围)Foo函数活动对象的this属性被绑定在新创建的对象bar上,Foo函数并没有返回值,所以自动返回new创建的bar对象。

4.默认绑定    默认绑定就是不符合上面任何一种规则是的默认规则

1 function foo() {
2     console.log(this.a);
3 }
4 var a = 2;
5 foo();

上面这段代码foo函数不带任何修饰的直接使用,不符合1-3中的任何一种绑定规则,所以只能使用默认绑定规则,而默认绑定规则就是在非严格模式下被绑定在全局对象上(严格模式下与foo函数的调用位置无关为undefined)。

好了,关于this的四种绑定规则都已经解释清楚。那有没有意外情况呢?

是不是意外情况就看你对this的理解有没有到位,有没有认真的分析了,下面放两种特殊情况让大家转动一下脑筋

1.

 1 function foo() {
 2     console.log(this.a);
 3 }
 4 var obj = {
 5     a: 2,
 6     foo: foo
 7 };
 8 var bar = obj.foo;
 9 var a = "Global";
10 bar();

这段代码唯一的特殊点就是bar引用了obj的foo函数。而答案就变成了Global。这是为什么呢?

首先我们直接来输出一下bar

可以看到bar就是foo()函数的一个别名而已,虽然他是通过obj引用的foo()函数,但他只是引用到了foo函数,并没有引用到obj对象的执行上下文,所以他符合的是上面this规则的第四条默认绑定,所以this指向全局输出Global。

2.

 1 function foo() {
 2     console.log(this.a);
 3 }
 4 function toDo (fn) {
 5     fn();
 6 }
 7 var obj = {
 8     a: 2,
 9     foo: foo
10 };
11 var a = "Global";
12 toDo(obj.foo);

这段代码是传入回掉函数。而toDO函数中fn的传递依旧只是传递了foo这个函数,并没有传递obj对象的执行上下文,所以在fn()调用的位置没有任何特殊绑定,符合this规则的默认绑定,this指向全局。不信?我们在toDO函数内输出一下fn看是不是foo函数喽~

嗯哼,看来我说的是对的。

一切都解决之后你肯定又会问,那万一我遇见的函数一下子符合了两条规则怎么办?呃。。。我们来测一下绑定规则的优先级吧~

不用说默认绑定的优先级是最低的,那先不用理他,先看一下隐式绑定和显示绑定谁的优先级更高

 1 function foo() {
 2     console.log(this.a);
 3 }
 4 var obj1 = {
 5     a: 2,
 6     foo: foo
 7 };
 8 var obj2 = {
 9     a: 3,
10     foo: foo
11 };
12 obj1.foo();
13 obj2.foo();
14
15 obj1.foo.call(obj2);
16 obj2.foo.call(obj1);

看来显式绑定轻松战胜隐式绑定,那显式绑定和new谁的优先级更高呢?好吧,由于new和call(),apply()无法同时使用,所以我没有写出他们之间的测试代码,不过查了些资料之后的结论是new的优先级高于显式绑定(那位大神可以举一个new优先级高于现实绑定的例子呢~如果有的话请贴进评论,小女子在此谢过~~)。

那总结以上经验就是想要判断this绑定规则,先看new,再看显式绑定,再看隐式绑定,都没有则使用默认绑定。

不过凡事都有例外,不能百分百下定论,比如说显式绑定是对象传入null或者undefined的话,则会忽略call或apply应用默认绑定。

还有很多种例外等着大家去发觉。不过还是一般情况占据多数,所以,只要正确寻找分析函数调用栈,找到函数调用位置,在函数调用位置上判断使用那种绑定规则,基本可以正确判断出this的指向。

时间: 2024-10-14 01:40:38

Javascript 中我很想说说的 this的相关文章

【转】深入浅出 JavaScript 中的 this

Java 等面向对象的语言中,this 关键字的含义是明确且具体的,即指代当前对象.一般在编译期确定下来,或称为编译期绑定.而在 JavaScript 中,this 是动态绑定,或称为运行期绑定的,这就导致 JavaScript 中的 this 关键字有能力具备多重含义,带来灵活性的同时,也为初学者带来不少困惑.本文仅就这一问题展开讨论,阅罢本文,读者若能正确回答 JavaScript 中的 What ’s this 问题,作为作者,我就会觉得花费这么多功夫,撰写这样一篇文章是值得的. Java

深入浅出 JavaScript 中的 this

本文来自:http://www.ibm.com/developerworks/cn/web/1207_wangqf_jsthis/ JavaScript 语言中的 this 由于其运行期绑定的特性,JavaScript 中的 this 含义要丰富得多,它可以是全局对象.当前对象或者任意对象,这完全取决于函数的调用方式.JavaScript 中函数的调用有以下几种方式:作为对象方法调用,作为函数调用,作为构造函数调用,和使用 apply 或 call 调用.下面我们将按照调用方式的不同,分别讨论

javascript中的location.href有很多种用法

javascript中的location.href有很多种用法,主要如下. self.location.href="/url" 当前页面打开URL页面 location.href="/url" 当前页面打开URL页面 windows.location.href="/url" 当前页面打开URL页面,前面三个用法相同. this.location.href="/url" 当前页面打开URL页面 parent.location.h

javascript中的原始值和复杂值

前面的话 javascript的数据类型可以分为两种:原始类型(基本类型或者简单类型)和引用类型. 原始类型:Undefined,Null,Boolean,Number,String五种: 引用类型:Object,Array,Function: 与此相对应的,它们的值分别被称为原始值和复杂值. 特性 原始值 原始值是表示javascript中可用的数据或信息的最底层的形式或者最简单的形式.原始类型的值被称为原始值,因为它们的值是不可被细化的.也就是说,数字是数字,字符串是字符串,布尔值是true

【转】十个JavaScript中易犯的小错误,你中了几枪?

在今天,JavaScript已经成为了网页编辑的核心.尤其是过去的几年,互联网见证了在SPA开发.图形处理.交互等方面大量JS库的出现. 如果初次打交道,很多人会觉得js很简单.确实,对于很多有经验的工程师,或者甚至是初学者而言,实现基本的js功能几乎毫无障碍.但是JS的真实功能却比很多人想象的要更加多样.复杂.JavaScript的许多细节规定会让你的网页出现很多意想不到的bug,搞懂这些bug,对于成为一位有经验的JS开发者很重要. 常见错误一:对于this关键词的不正确引用 我曾经听一位喜

转---深入浅出妙用 Javascript 中 apply、call、bind

作者:伯乐在线专栏作者 - chokcoco 如有好文章投稿,请点击 → 这里了解详情 如需转载,发送「转载」二字查看说明 这篇文章实在是很难下笔,因为网上相关文章不胜枚举. 巧合的是前些天看到阮老师的一篇文章的一句话: "对我来说,博客首先是一种知识管理工具,其次才是传播工具.我的技术文章,主要用来整理我还不懂的知识.我只写那些我还没有完全掌握的东西,那些我精通的东西,往往没有动力写.炫耀从来不是我的动机,好奇才是." 对于这句话,不能赞同更多,也让我下决心好好写这篇,网上文章虽多,

JavaScript中的回调函数

在学习JavaScript的过程中遇到了很多,使用到回调函数的例子,出现了许多疑问,就由一个栗子开始吧: 在JavaScript中接触的第一个回调函数是在setInterval()和setTimeout()中出现的: 1 var num = 10; 2 3 var interValId = setInterval(function (){ 4 console.log(num); 5 num--; 6 if(num==0){ 7 clearInterval(interValId); 8 } 9 }

深入理解JavaScript中创建对象模式的演变(原型)

创建对象的模式多种多样,但是各种模式又有怎样的利弊呢?有没有一种最为完美的模式呢?下面我将就以下几个方面来分析创建对象的几种模式: Object构造函数和对象字面量方法 工厂模式 自定义构造函数模式 原型模式 组合使用自定义构造函数模式和原型模式 动态原型模式.寄生构造函数模式.稳妥构造函数模式 第一部分:Object构造函数和对象字面量方法 我之前在博文<javascript中对象字面量的理解>中讲到过这两种方法,如何大家不熟悉,可以点进去看一看回顾一下.它们的优点是用来创建单个的对象非常方

javascript中的Function和Object

写的很好,理解了很多,特此转发记录 转自:http://blog.csdn.net/tom_221x/archive/2010/02/22/5316675.aspx 在JavaScript中所有的对象都继承自Object原型,而Function又充当了对象的构造器,那么Funtion和Object到底有着什麽样的关系呢 ? 首先,一切都是对象. 1 alert(Object instanceof Object); // true 2 alert(Function instanceof Objec