你不知道的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上卷(第一章 作用域是什么)
在写这篇博客时这本书我已经是看过一遍了,为了加深印象和深入学习于是打算做这系列的前端经典书籍导读博文,大家如果觉得这本书讲的好可以自己买来看看,我是比较喜欢看纸质版书的,因为这样才有读书的那种感觉. ...
随机推荐
- WPF设计の画刷(Brush)
一.什么是画刷 画刷是是一种渲染方式,用于填充图形形状,如矩形.椭圆.扇形.多边形和封闭路径.在GDI+中,画刷分为以下几种:SolidBrush,TextureBrush,HatchBrush,Li ...
- Nginx 反向代理 -- 一路上的坑转载
个人学习之用转子https://www.cnblogs.com/xjbBill/p/7477825.html 前些天刚过来新公司上班,公司的项目都挺多的,只不过项目都是第三方公司团队开发的,现在本公司 ...
- python 那些我记不清的函数
eval 函数:用来计算在字符串中的有效Python表达式,并返回一个对象......将字符串变回数据类型 enumerate函数:加上序号 isinstance函数:判断数据类型 isinstanc ...
- IDEA+JUnit
1.入门 https://blog.csdn.net/smxjant/article/details/78206279 2.比较好的JUnit例子:https://github.com/aws/aws ...
- AI R-CNN目标检测算法
Region-CNN,简称R-CNN,是首次将深度学习应用于目标检测的算法. bounding box IOU 非极大值抑制 selective search 参考链接: https://blog.c ...
- Java关键字(五)——this
this 也是Java中的一个关键字,在<Java编程思想>第四版第五章5.4小节对 this 关键字是这样介绍的: this 关键字只能在方法内部使用,表示对“调用方法的那个对象”的引用 ...
- 在ASP.NET Core MVC中子类Controller拦截器要先于父类Controller拦截器执行
我们知道在ASP.NET Core MVC中Controller上的Filter拦截器是有执行顺序的,那么如果我们在有继承关系的两个Controller类上,声明同一种类型的Filter拦截器,那么是 ...
- vi十六进制编辑
指定行:n 光标行之前或之后的n个字符nl 之后 2l 光标位置两个字符后nh 之前 2h 光标位置两个字符前 光标行之上或之下的n个字符nk 之上 1k 光标位置1个字符之上nj 之下 1j 光标位 ...
- 【转】MySQL中的行级锁,表级锁,页级锁
在计算机科学中,锁是在执行多线程时用于强行限制资源访问的同步机制,即用于在并发控制中保证对互斥要求的满足. 在数据库的锁机制中介绍过,在DBMS中,可以按照锁的粒度把数据库锁分为行级锁(INNODB引 ...
- 深入理解[Master-Worker模式]原理与技术
Master-Worker模式是常用的并行模式之一.它的核心思想是,系统由两类进程协作工作:Master进程和Worker进程.Master进程负责接收和分配任务,Worker进程负责处理子任务.当各 ...