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

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

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

 function foo(num) {
console.log("foo" + num);
this.count++;
}
foo.count = 0;
for(var i = 0; i < 5; i++) {
foo(i);
}
console.log(foo.count);

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

foo0
foo1
foo2
foo3
foo4

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

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

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

 function foo() {
var a = 2;
this.bar();
}
function bar() {
console.log(this.a);
}
foo();

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

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

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

 function baz() {
//当前调用栈:baz
//调用位置是全局作用域
console.log("baz");
bar();//bar的调用位置
}
function bar() {
//当前调用栈:baz -> bar
//调用位置是baz中
console.log("bar");
foo();//foo的调用位置
}
function foo() {
//当前调用栈:baz -> bar -> foo
//调用位置是bar中
console.log("foo");
}
baz();//baz的调用位置

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

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

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

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

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

 var a = 3;
function foo() {
console.log(this.a);
}
var obj = {
a: 2,
foo: foo
};
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就会绑定在这个对象上,看一段代码

 function foo() {
console.log(this.a);
}
var obj = {
a: 2
};
foo.call(obj);

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

3.new 绑定

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

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

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

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

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

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

 function foo() {
console.log(this.a);
}
var a = 2;
foo();

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

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

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

1.

 function foo() {
console.log(this.a);
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo;
var a = "Global";
bar();

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

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

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

2.

 function foo() {
console.log(this.a);
}
function toDo (fn) {
fn();
}
var obj = {
a: 2,
foo: foo
};
var a = "Global";
toDo(obj.foo);

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

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

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

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

 function foo() {
console.log(this.a);
}
var obj1 = {
a: 2,
foo: foo
};
var obj2 = {
a: 3,
foo: foo
};
obj1.foo();
obj2.foo(); obj1.foo.call(obj2);
obj2.foo.call(obj1);

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

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

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

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

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

  1. 深入浅出 JavaScript 中的 this

    在 Java 等面向对象的语言中,this 关键字的含义是明确且具体的,即指代当前对象.一般在编译期确定下来,或称为编译期绑定.而在 JavaScript 中,this 是动态绑定,或称为运行期绑定的 ...

  2. 【转】深入浅出 JavaScript 中的 this

    Java 等面向对象的语言中,this 关键字的含义是明确且具体的,即指代当前对象.一般在编译期确定下来,或称为编译期绑定.而在 JavaScript 中,this 是动态绑定,或称为运行期绑定的,这 ...

  3. JavaScript 中的 this

    JavaScript 语言中的 this 由于其运行期绑定的特性,JavaScript 中的 this 含义要丰富得多,它可以是全局对象.当前对象或者任意对象,这完全取决于函数的调用方式.JavaSc ...

  4. 一个简单的例子让你很轻松地明白JavaScript中apply、call、bind三者的用法及区别

    JavaScript中apply.call.bind三者的用法及区别 引言 正文 一.apply.call.bind的共同用法 二. apply 三. call 四. bind 五.其他应用场景 六. ...

  5. JavaScript 中的数据类型

    Javascript中的数据类型有以下几种情况: 基本类型:string,number,boolean 特殊类型:undefined,null 引用类型:Object,Function,Date,Ar ...

  6. 关于javascript中的this关键字

    this是非常强大的一个关键字,但是如果你不了解它,可能很难正确的使用它. 下面我解释一下如果在事件处理中使用this. 首先我们讨论一下下面这个函数中的this关联到什么. function doS ...

  7. javascript中的变量作用域以及变量提升

    在javascript中, 理解变量的作用域以及变量提升是非常有必要的.这个看起来是否很简单,但其实并不是你想的那样,还要一些重要的细节你需要理解. 变量作用域 “一个变量的作用域表示这个变量存在的上 ...

  8. 理解JavaScript中的“this”

    对于javascript的初学者来说,一般对“this”关键字都感到非常迷惑.本文的目的旨在让你全面的了解“this”,理解在每一个情景下如何使用“this”,希望通过本文,可以帮助同学们不在害怕“t ...

  9. javascript中的Function和Object

    写的很好,理解了很多,特此转发记录 转自:http://blog.csdn.net/tom_221x/archive/2010/02/22/5316675.aspx 在JavaScript中所有的对象 ...

随机推荐

  1. AutoIt3(AU3)开发的装机小工具,实现快速检测以及一些重用快捷操作功能

    项目相关地址 源码:https://github.com/easonjim/Installed_Tools bug提交:https://github.com/easonjim/Installed_To ...

  2. glibc resolv/res_send.c getaddrinfo() buffer stack smash when dealing malformation big DNS Response Package

    catalogue . 漏洞简述 . 调试环境搭建 . 漏洞利用 . 漏洞分析 . 缓解修复方案 1. 漏洞简述 0x1: 函数调用顺序 getaddrinfo (getaddrinfo.c) -&g ...

  3. 屠蛟之路_乘风破浪_FifthDay

    乘风破浪 一艘大船 (我们写的几千行代码) 一片汪洋 (软工耗费的时间) 一群骚年 (一不小心就火了) 为救一位公主 (吃自助餐) 选择去跋山涉水,乘风破浪(解决bug,解决冲突) 毫不畏惧 (韧性) ...

  4. 【Alpha版本】冲刺-Day2

    队伍:606notconnected 会议时间:11月10日 会议总结 张斯巍(433) 今天安排:把昨天没完成的做好,主界面图标的修改,侧边栏背景设计 完成度:85% 明天计划:个人信息界面设计.优 ...

  5. WPF元素可视化效果

    在WPF中设置元素的可视化效果主要用到BlurEffect类和DropShadowEffect类.(目前只学到这两个,哈哈) 1.BlurEffect类 命名空间: System.Windows.Me ...

  6. hdu 2031 杨辉三角

    题意:显示杨辉三角形. 解法: 组合数学公式:combi(n,m)=combi(n-1,m-1)+combi(n-1,m); 至于为什么有这个公式呢?那就是高中数学的内容啦 1: #include&l ...

  7. bootstrap学习总结-04 常用标签2

    1 表格 Bootstrap为表格设计了漂亮的样式. 1)表格基本实例 任意 <table> 标签添加 .table. <table class="table"& ...

  8. uC/OS-II互斥信号(OS_mutex)块

    /*************************************************************************************************** ...

  9. C中的字符串实例

    1.#include <stdio.h>#include <assert.h>size_t strlen(const char* s){    return ( assert( ...

  10. 仿照jquery封装一个自己的js库(二)

    本篇为完结篇.主要讲述如何造出轮子的高级特性. 一. css方法的高级操作 先看本文第一部分所讲的dQuery css方法 //css方法 dQuery.prototype.css=function( ...