你不知道的JavaScript——第二章:this全面解析
1调用位置
调用栈:为了到达当前执行位置所调用的所有函数。
- 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的调用位置
2绑定规则:
2.1默认绑定:
- function foo(){
- console.log(this.a);
- }
- var a=2;
- foo();//
函数调用时应用了this的默认绑定,因此this指定全局对象。
如果使用严格模式(strict mode),则不能将全局对象用于默认绑定,因此this会绑定到undefined。
- function foo(){ //严格模式下声明
- "use strict";
- console.log(this.a);
- }
- var a=2;
- foo();//TypeError:this is undefined
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1
虽然this的绑定规则完全取决于调用的位置,但是只有foo()运行在非strict mode下时,默认绑定才能绑定到全局对象。
在严格模式下调用foo()则不影响默认绑定。!!!!
- function foo(){
- console.log(this.a);
- }
- var a=2;
- (function(){ //严格模式下调用
- "use strict";
- foo();//
- })();
2.2隐式绑定
需要考虑的规则是调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含,!!不过这种说法可能会造成一些误导。
- funtion foo(){
- console.log(this.a);
- }
- var obj={
- a:2,
- foo:foo
- }
- obj.foo();//
无论是直接在obj中定义还是先定义再添加为引用属性,这个函数严格来说都不属于obj对象。
然而,调用位置会使用obj上下文来引用函数,因此可以说函数被调用时obj对象“拥有”或者“包含”函数引用。
当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。
!!!!!!!!!!!!!!!!
对象属性引用链中只有上一层或者说最后一层在调用位置中起作用。(函数直接关联层起作用)
- function foo(){
- console.log(this.a);
- }
- var obj2={
- a:42,
- foo:foo
- }
- var obj1={
- a:2,
- obj2:obj2
- }
- obj1.obj2.foo();//
隐式丢失:
被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,从而把this绑定到全局对象或者undefined上,取决于是否在严格模式。
- function foo(){
- console.log(this.a);
- }
- var obj={
- a:2,
- foo:foo
- }
- var bar=obj.foo;
- var a='oops,global';
- bar();//'oops,global'
虽然bar是obj.foo的一个引用,但实际上,它引用的是foo函数本身,因此此时bar()其实是一个不带任何修饰的函数调用,因此应用了默认绑定。
- function foo(){
- console.log(this.a);
- }
- function doFoo(fn){
- fn();
- }
- var obj={
- a:2,
- foo:foo
- }
- var a='oops,global';
- doFoo(obj.foo);//'oops,global';
参数传递其实就是一种隐式赋值。
- function foo(){
- console.log(this.a);
- }
- var obj={
- a:2,
- foo:foo
- }
- var a="oops,global";
- setTimeout(obj.foo,100);//"oops, global";
JavaScript环境中内置的setTimeout()函数实现和下面的伪代码类似:
- function setTimeout(fn,delay){
- //等待delay毫秒
- fn();//调用位置
- }
2.3显式绑定 (call,apply,bind)
- function foo(){
- console.log(this.a);
- }
- var obj={
- a:2
- }
- foo.call(obj);//
通过foo.call(...),我们可以在调用foo时强制把它的this绑定到obj上。
如果你传入了一个原始值(字符串类型、布尔类型或者数字类型)来当作this的绑定对象,这个原始值会被转换成它的对象形式(也就是new String(…)、new Boolean(…)或者new Number(…))。这通常被称为“装箱”。
1.硬绑定: 显式的强制绑定
- function foo(){
- console.log(this.a);
- }
- var obj={
- a:2
- };
- var bar=function(){
- foo.call(obj);
- };
- bar(); //
- setTimeout(bar,100); //
- //硬绑定的bar不可能再修改它的this
- bar.call(windwo);//2
硬绑定的典型应用场景就是创建一个包裹函数,负责接收参数并返回值。
- function foo(something){
- console.log(this.a,something);
- return this.a+something;
- }
- var obj={
- a:2
- };
- var bar=function(){
- return foo.apply(obj,arguments);
- };
- var b=bar(3); //2 3
- console.log(b);//
另一种使用方法是创建一个可以重复使用的辅助函数:
- function foo(something){
- console.log(this.a,something);
- return this.a+something;
- }
- //简单的辅助绑定函数
- function bind(fn,obj){
- return function(){
- fn.appl(obj,arguments);
- };
- }
- var obj={
- a:2
- };
- var bar=bind(foo,obj);
- var b=bar(3);//2 3
- console.log(b);//
ES5提供了内置的方法Function.prototype.bind
- function foo(something){
- console.log(this.a, something);
- return this.a+something;
- }
- var obj={
- a:2
- };
- var bar=foo.bind(obj);
- var b=bar(3); //2 3
- console.log(b); //
bind(…)会返回一个硬编码的新函数,它会把你指定的参数设置为this的上下文并调用原始函数。
2.API调用的“上下文”
- function foo(el){
- console.log(el, this.id);
- }
- var obj={
- id:"awesome"
- };
- //调用foo(…)时把this绑定到obj
- [1,2,3].forEach(foo,obj);//1 awesome 2 awesome 3 awesome
2.4new绑定
包括内置对象函数在内的所有函数都可以用new来调用,这种函数调用被称为构造函数调用。注:实际上并不存在所谓的“”构造函数“”,只有对于函数的“”构造调用“”。
使用new来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。
1.创建(或者说构造)一个全新的对象。
2.这个新对象会被执行 [[Prototype]]连接
3.这个新对象会绑定到函数调用的this。
4.如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。
- function foo(a){
- this.a=a;
- }
- var bar=new foo(2);//函数没有返回其他对象,new表达式中的函数调用会自动返回新对象。
- console.log(bar.a)//
使用new来调用foo(…)时,我们会构造一个新对象并把它绑定到foo(…)调用中的this上。
3优先级:
判断this:
1.函数是否在new调用(new绑定)?如果是的话this绑定的是新创建的对象。 var bar=new foo();
2.函数是否通过call、apply(显式绑定)或者硬绑定调用?如果是的话,this绑定的是指定的对象。 var bar=foo.call(obj2);
3.函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this绑定的是那个上下文对象。 var bar=obj1.foo();
4.如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到全局对象。 varbar=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绑定比隐式绑定的优先级高。例:
- function foo(something){
- this.a=something;
- }
- var obj1={
- foo:foo
- }
- var obj2={}
- obj1.foo(2);
- console.log(obj1.a);//
- obj1.foo.call(obj2,3);
- console.log(obj2.a);//
- var bar=new obj1.foo(4);
- console.log(obj1.a);//
- console.log(bar.a);//
new和bind比较:
- function foo(something){
- this.a=something;
- }
- var obj1={};
- var bar=foo.bind(obj1);
- bar(2);
- console.log(obj1.a);//
- var baz=new bar(3);
- console.log(obj1.a);//
- console.log(baz.a);//
4绑定例外;
1被忽略的this:
如果把null或者undefined作为this的绑定对象传入call、apply或者bind,这些值在调用时会被忽略,实际应用的是默认绑定规则。
- function foo(){
- console.log(this.a);
- }
- var a=2;
- foo.call(null);//
应用:
apply(…)来“展开”一个数组,并当做参数传入一个函数。
bind(…)可以对参数进行柯里化(预先设置一些参数)。
- function foo(a,b){
- console.log("a: "+a+",b: "+b);
- }
- //把数组“展开”成参数
- foo.apply(null,[2,3]);//a:2,b:3
- //使用bind(…)进行柯里化
- var bar=foo.bind(null,2);
- bar(3);//a:2,b:3
更安全的this:
一种“更安全”的做法是传入一个特殊的对象,把this绑定到这个对象不会对你的程序产生任何副作用。
在JavaScript中创建一个空对象最简单的方法就是Object.create(null)。Object.create(null)和{}很像,但是并不会创建Object.prototype这个委托,所以它比{}“更空”:
- function foo(a,b){
- console.log("a:"+a+",b:"+b);
- }
- //我们的DMZ空对象
- var ♓=Object.create(null);
- //把数组展开成参数
- foo.apply(♓,[2,3]);
- //使用bind(…)进行柯里化
- var bar=foo.bind(♓,2);
- bar(3);//a:2,b:3
2间接引用:
- function foo(){
- console.log(this.a);
- }
- var a=2;
- var o={a:3,foo:foo};
- var p={a:4};
- o.foo();//
- (p.foo=o.foo)();//
赋值表达式p.foo=o.foo的返回值是目标函数的引用,因此调用位置是foo()而不是p.foo()或者o.foo()。根据我们之前所说的,这里会应用默认绑定。
注意:
对于默认绑定来说,决定this绑定对象的并不是调用位置是否处于严格模式,而是函数体是否处于严格模式。如果函数体处于严格模式,this会被绑定到undefined,否则this会被绑定到全局对象。
3软绑定
可以给默认绑定指定一个全局对象和undefined以外的值。可以实现和硬绑定相同的效果,同时保留隐式绑定或者显示绑定修改this的能力。
- if(!Function.prototype.softBind){
- Function.prototype.softBind=function(obj){
- var fn=this;
- //捕获所有的curried参数
- var curried=[].slice.call(arguments,1);
- var bound=function(){
- return fn.apply(
- (!this||this===(windwo || global))?obj:this,
- curried.concat.apply(curried,arguments)
- );
- };
- bound.prototype=Object.create(fn.prototype);
- return bound;
- };
- }
softBind实现软绑定功能:
- function foo(){
- console.log("name: "+this.name);
- }
- var obj={name:"obj"},
- obj2={name:"obj2"},
- obj3={name:"obj3"};
- var fooOBJ=foo.softBind(obj);
- fooOBJ();//obj
- obj2.foo=foo.softBind(obj);
- obj2.foo();//obj2
- fooOBJ.call(obj3);//obj3
- setTimeout(obj2.foo,100);//obj
5this词法:
箭头函数
箭头函数并不是使用function关键字定义的,而是使用被称为“胖箭头”的操作符=>定义的。箭头函数不使用this的四种规则,而是根据外层(函数或者全局)作用域来决定this(定义函数时)。
- function foo(){
- //返回一个箭头函数
- return (a)=>{
- //this继承自foo()
- console.log(this.a);
- };
- }
- var obj1={
- a:2
- };
- var obj2={
- a:3
- };
- var bar=foo.call(obj1);
- bar.call(obj2);//2 不是3 绑定到创建箭头函数时的this上,绑定无法被修改
foo()内部创建的箭头函数会捕获调用是foo()的this。由于foo()的this绑定到obj1,bar(引用箭头函数)的this也会绑定到obj1,箭头函数的绑定无法被修改。(new 也不行!)
箭头函数最常用于回调函数中,例如事件处理器或者定时器。
- function foo(){
- setTimeout(()=>{
- //这里的this在词法上继承自foo()
- console.log(this.a);
- },100);
- }
- var obj={
- a:2
- };
- foo.call(obj);//
你不知道的JavaScript——第二章:this全面解析的更多相关文章
- JavaScript 数据访问(通译自High Performance Javascript 第二章) [转]
JavaScript 数据访问(通译自High Performance Javascript 第二章) JavaScript 数据访问(翻译自High Performance Javascript ...
- 你不知道的JavaScript——第一章:作用域是什么?
编译原理 JavaScript事实上是一门编译语言,但与传统的编译语言不同,它不是提前编译的,编译结果也不能在分布式系统中进行移植. 任何JavaScript代码片段在执行前都要进行编译(通常就在执行 ...
- R语言分析(二)——薛毅R语言第二章后面习题解析
包括2.2—2.6中间的习题,2.2的习题中第三问和第四问,应该有其他的解答方法,但我看他的题目,似乎是在A和B的基础上进行,所以就选择了使用for循环的方法 做着习题,又不断查着书,这样,书籍也熟悉 ...
- JavaScript 第二章总结
Writing real code 设计程序的步骤 First, a high-level design and a flowchart more details Working through th ...
- javascript第二章--变量、作用域和内存问题
① 基本类型和引用类型的值 ② 执行环境及作用域 ③ 垃圾收集
- 你不知道的JavaScript(上)this和对象原型(一)
第一章 关于this 1.this 既不指向函数自身也不指向函数的词法作用域 2.this 实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用(调用位置). 第二章 this全面 ...
- Javascript权威指南——第二章词法结构,第三章类型、值和变量,第四章表达式和运算符,第五章语句
第二章 词法结构 一.HTML并不区分大小写(尽管XHTML区分大小写),而javascript区分大小写:在HTML中,这些标签和属性名可以使用大写也可以使用小写,而在javascript中必须小写 ...
- Javascript高级程序设计读书笔记(第二章)
第二章 在HTML中使用Javascript 2.1<script>元素 延迟脚本(defer = "defer")表明脚本在执行时不会影响页面的构造,脚本会被延迟到 ...
- 你不知道的javaScript上卷(第一章 作用域是什么)
在写这篇博客时这本书我已经是看过一遍了,为了加深印象和深入学习于是打算做这系列的前端经典书籍导读博文,大家如果觉得这本书讲的好可以自己买来看看,我是比较喜欢看纸质版书的,因为这样才有读书的那种感觉. ...
随机推荐
- Python:Day52 urls
Django的2.0版本之后使用新的规则,之前的url变成了path,如果要使用正则还需要引入re_path from django.urls import path,re_path urlpatte ...
- CONTRO4 系列
软件下载 https://getcomposer.org/download/ 0技术手册 https://wenku.baidu.com/view/4b511ead376baf1ffd4fad36.h ...
- 开源的许可证GPL、LGPL、BSD、Apache 2.0
软件开发者要开源软件,不单单是开放源代码就可以了,选择一种许可证很重要,一个许可证之于软件就相当于价值观之于普通人,代表了这个软件的基本品性.一个错误的许可证选择可能会直接导致整个项目的失败.各种开源 ...
- Spring Security(三):1、Getting Started
The later parts of this guide provide an in-depth discussion of the framework architecture and imple ...
- Spring(1)_Bean初始化
源码: 执行的代码 public static void main(String[] args) { ApplicationContext context=new ClassPathXmlApplic ...
- jmeter(二十二)内存溢出原因及解决方法
jmeter是一个java开发的开源性能测试工具,在性能测试中可支持模拟并发压测,但有时候当模拟并发请求较大或者脚本运行时间较长时,压力机会出现卡顿甚至报异常————内存溢出, 这里就介绍下如何解决内 ...
- SkylineGlobe for web开发是否支持IE11?
之前有客户反馈,说在IE11里浏览skyline开发的系统页面,会提示错误,怀疑是不是skyline不支持IE11了,其实不是. 主要是因为IE11更加遵循W3C规范,所以IE11与低版本IE在加载a ...
- ESP8266开发综合篇第十四节(LUA)-8266作为TCP服务器,Android客户端连接,显示温湿度,控制继电器
前几节先略过,我先补充上大部分人迫切的需求 编写Android TCP客户端 用Android Studio 先做一下界面 然后放一个输入对话框,因为没有显示出来这个控件.所以就手写 剩下的自己研究 ...
- CF741 D Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths
题目意思很清楚了吧,那么我们从重排回文串的性质入手. 很容易得出,只要所有字符出现的次数都为偶数,或者有且只有一个字符出现为奇数就满足要求了. 然后想到什么,Hash?大可不必,可以发现字符\(\in ...
- 【教程】switch上的Human Fall Flat如何设置本地双人?
1. 保证两个手柄已插入主机上 2. 进入游戏至游戏开始界面 3. 将主机插入拓展坞,等待电视显示 4. 稍等数秒,电视上会提示使用手柄方式 5. 此时按照多人游戏的手柄操作方法即可