Javascript 中我很想说说的 this
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 来调用函数,即发生构造函数调用时,会执行下面操作
- 创建一个全新的对象
- 这个对象会被执行[__proto__]的链接
- 这个新对象会绑定到函数调用的this上
- 如果函数没有返回其他对象,那么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的更多相关文章
- 深入浅出 JavaScript 中的 this
在 Java 等面向对象的语言中,this 关键字的含义是明确且具体的,即指代当前对象.一般在编译期确定下来,或称为编译期绑定.而在 JavaScript 中,this 是动态绑定,或称为运行期绑定的 ...
- 【转】深入浅出 JavaScript 中的 this
Java 等面向对象的语言中,this 关键字的含义是明确且具体的,即指代当前对象.一般在编译期确定下来,或称为编译期绑定.而在 JavaScript 中,this 是动态绑定,或称为运行期绑定的,这 ...
- JavaScript 中的 this
JavaScript 语言中的 this 由于其运行期绑定的特性,JavaScript 中的 this 含义要丰富得多,它可以是全局对象.当前对象或者任意对象,这完全取决于函数的调用方式.JavaSc ...
- 一个简单的例子让你很轻松地明白JavaScript中apply、call、bind三者的用法及区别
JavaScript中apply.call.bind三者的用法及区别 引言 正文 一.apply.call.bind的共同用法 二. apply 三. call 四. bind 五.其他应用场景 六. ...
- JavaScript 中的数据类型
Javascript中的数据类型有以下几种情况: 基本类型:string,number,boolean 特殊类型:undefined,null 引用类型:Object,Function,Date,Ar ...
- 关于javascript中的this关键字
this是非常强大的一个关键字,但是如果你不了解它,可能很难正确的使用它. 下面我解释一下如果在事件处理中使用this. 首先我们讨论一下下面这个函数中的this关联到什么. function doS ...
- javascript中的变量作用域以及变量提升
在javascript中, 理解变量的作用域以及变量提升是非常有必要的.这个看起来是否很简单,但其实并不是你想的那样,还要一些重要的细节你需要理解. 变量作用域 “一个变量的作用域表示这个变量存在的上 ...
- 理解JavaScript中的“this”
对于javascript的初学者来说,一般对“this”关键字都感到非常迷惑.本文的目的旨在让你全面的了解“this”,理解在每一个情景下如何使用“this”,希望通过本文,可以帮助同学们不在害怕“t ...
- javascript中的Function和Object
写的很好,理解了很多,特此转发记录 转自:http://blog.csdn.net/tom_221x/archive/2010/02/22/5316675.aspx 在JavaScript中所有的对象 ...
随机推荐
- HA模式强制手动切换:IPC's epoch [X] is less than the last promised epoch [X+1]
-- ::, WARN org.apache.hadoop.hdfs.qjournal.client.QuorumJournalManager: Remote journal failed to wr ...
- [NOIP2012] 提高组 洛谷P1079 Vigenère 密码
题目描述 16 世纪法国外交家 Blaise de Vigenère 设计了一种多表密码加密算法――Vigenère 密 码.Vigenère 密码的加密解密算法简单易用,且破译难度比较高,曾在美国南 ...
- Windows装机必备软件列表
经常装系统,列个List,以后装完之后安装软件直接参照使用!windows版: 输入法: 搜狗输入法(由于长期使用导致此输入法十分熟悉我的输入习惯,以无法自拔).支持Linux.Windows(太穷还 ...
- ffmpeg]ffmpeg使用参数的中文说明
基本选项: -formats 输出所有可用格式 -f fmt 指定格式(音频或视频格式) -i filename 指定输入文件名,在linux下当然也能指定:0.0(屏幕录制)或摄像头 -y 覆盖已有 ...
- Distance Between Points
I need some help. I have to create a function that will calculate the distance between points (x1,y1 ...
- HDU1698Just a Hook(线段树 + 区间修改 + 求和)
题目链接 分析:1-N区间内初始都是1,然后q个询问,每个询问修改区间[a,b]的值为2或3或者1,统计最后整个区间的和 本来想刷刷手速,结果还是写了一个小时,第一个超时,因为输出的时候去每个区间查找 ...
- Docker change directory
https://forums.docker.com/t/how-do-i-change-the-docker-image-installation-directory/1169/2 How do I ...
- Yocto开发笔记之《Tip-设置程序开机启动》(QQ交流群:519230208)
QQ群:519230208,为避免广告骚扰,申请时请注明 “开发者” 字样 IMX6UL,转载请注明出处 ============================================== ...
- HTML1
1. 前端:html:做网页的内容 CSS: 做网页的外观 JS:执行网页的动作 前端是静态网页,内容写死.要有变化的内容,需要数据库 浏览器去解析生成漂亮的界面 后台技术:.Net C# 数据库: ...
- System.currentTimeMillis()计算方式与时间的单位转换
目录[-] 一.时间的单位转换 二.System.currentTimeMillis()计算方式 一.时间的单位转换 1秒=1000毫秒(ms) 1毫秒=1/1,000秒(s)1秒=1,000,000 ...