《你不知道的JavaScript》第二部分:this 详解
第1章 关于this
this
是自动定义在所有函数的作用域中的关键字,用于引用合适的上下文对象。
☞ 为什么要使用 this
?
this
提供了一种更优雅的方式来隐式“传递”一个对象引用,因此可以将API设计得更加简洁并且易于复用。
☞ 对 this
的误解
this
不指向函数自身,也不指向函数的词法作用域。- 作用域“对象”无法通过JavaScript代码访问,存在于JavaScript引擎内部
☞ this
到底是什么
this
是在函数被调用时发生的绑定,和函数声明的位置没有关系,它的上下文(指向)取决于函数调用时的各种条件。- 当一个函数被调用时,会创建一个活动记录(执行上下文)。
- 这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。
this
就是记录的其中一个属性,会在函数执行的过程中用到。
- 这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。
第2章 this全面解析
☞ 调用位置(调用方法)
调用栈
:为了到达当前执行位置所调用的所有函数,类似于函数调用链。
调用位置就是在当前执行的函数的前一个调用中。
function baz() {
// 当前调用栈是:baz
// 因此,当前调用位置是全局作用域
console.log('baz');
bar(); // <-- bar 的调用位置
}
function bar() {
// 当前调用栈是:baz -> bar
// 因此,当前调用位置在 baz 中
console.log('bar');
}
baz(); // <-- baz 的调用位置
☞ 绑定规则
① 默认绑定:独立函数调用,this
指向全局对象
function foo() {
console.log(this.a);
}
var a = 2;
foo(); // 2
严格模式下,全局对象将无法使用默认绑定,
this
会绑定到undefined
② 隐式绑定:考虑调用位置是否有上下文对象
function foo() {
console.log(this.a);
}
/**
* 无论是直接在 obj 中定义还是先定义再添加引用属性,foo() 严格来说都不属于 obj 对象
*/
var obj2 = {
a: 42,
foo: foo // 当做 obj 的引用属性添加
};
var obj1 = {
a: 2,
obj2: obj2
}
/**
* 对象属性引用链中只有最顶层或者说最后一层会影响调用位置。
* 调用位置使用 obj2 的上下文来引用函数
*/
obj1.obj2.foo(); // 42
当函数有上下文对象时,
隐式绑定
规则会把函数调用中的this
绑定到这个上下文对象。
※ 隐式丢失:被 隐式绑定
的函数会丢失绑定对象,即会应用 默认绑定
。
[例1:]
function foo() {
console.log(this.a);
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo; // 函数别名!
var a = "oops, global"; // a是全局对象的属性
bar(); // 'oops, global'
[例2:传入回调函数 ]
function foo() {
console.log(this.a);
}
// 参数传递其实就是一种隐式赋值
function doFoo(fn) {
// fn 其实引用的是 foo
fn(); // <-- 调用位置!
}
var obj = {
a: 2,
foo: foo
};
var a = "oops, global"; // a是全局对象属性
doFoo(obj.foo); // 'oops, global'
// 传入语言内置的函数
setTimeout(obj.foo, 2000); // 'oops, global'
JavaScript 环境中内置的 setTimeout() 函数实现和下面的伪代码类似:
function setTimeout(fn, delay) {
// 等待 delay 毫秒
fn(); // <-- 调用位置
}
调用回调函数的函数可能会修改 this。
在一些流行的 JavaScript 库中事件处理器常会把回调函数的
this
强制绑定到触发事件的 DOM 元素上。实际上,你无法控制回调函数的执行方式,因此就没有办法控制会影响绑定的调用位置。
③ 显式绑定:call()、apply()
1) 硬绑定 :显式绑定的一个变种,解决丢失绑定问题
function foo() {
console.log(this.a);
}
var obj = {
a: 2
};
var a = 3;
/**
* 显式绑定
* 仍然存在丢失绑定问题
*/
foo.call(obj); // 2
foo.call(null); // 3
/**
* 硬绑定:显式的强制绑定
* 解决丢失绑定问题
*/
var bar = function() {
foo.call(obj);
};
bar(); // 2
setTimeout(bar, 100); // 2
// 硬绑定的 bar 不可能再修改它的 this
bar.call(window); // 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); // 5
[ 硬绑定的应用场景2 ]:创建一个 i
可重复使用的辅助函数(bind实现及内置函数)
function foo(something) {
console.log(this.a, something);
return this.a + something;
}
/**
* 简单的辅助绑定函数:
* 返回一个硬编码的新函数,把参数设置为 this 的上下文并调用原始函数
*/
function bind(fn, obj) {
return function() {
return fn.apply(obj, arguments);
}
}
var obj = {
a: 2
};
var bar = bind(foo, obj);
var b = bar(3); // 2 3
console.log(b); // 5
/**
* 硬绑定模式内置方法:
* Function.prototype.bind
*/
var bar2 = foo.bind(obj);
var b2 = bar(4); // 2 4
console.log(b2); // 6
2) API调用的“上下文” :提供“上下文”的可选参数,确保回调函数使用指定的 this
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"
④ new
绑定
在 JavaScript 中,构造函数只是有些使用 new
操作符时被调用的函数。它们并不会属于某个类,也不会实例化一个类。实际上并不存在所谓的“构造函数”,只有对于函数的“构造调用”。
使用 new
来调用函数时,会自动执行下面的操作:
1)创建(或者说构造)一个全新的对象;
2)这个新对象会被执行[[原型]]连接;
3)这个新对象会绑定到函数调用的
this
;4)如果函数没有返回其他对象,那么
new
表达式中的函数调用会自动返回这个新对象。
☞ 优先级/判断 this
- 由
new
调用?——> 绑定到新创建的对象。 - 由
call
或apply
(或者bind
)调用?——> 绑定到指定的对象。 - 由上下文对象调用?——> 绑定到那个上下文对象。
- 默认——> 在严格模式下绑定到
undefined
,否则绑定到全局对象。
[例子]:
function foo(p1, p2) {
this.val = p1 + p2;
}
// 是所以使用 null 是因为在本例中我们并不关心硬绑定的 this 是什么
// 反正使用 new 时 this 会被修改
var bar = foo.bind(null, 'p1');
var baz = new bar('p2');
console.log(baz.val); // p1p2
在
new
中使用硬绑定函数,主要目的是预先设置函数的一些参数,这样在使用new
进行初始化时就可以只传入其余的参数。bind(...)的功能之一就是可以把第一个参数(第一个参数用于绑定 this)之外的其他参数都传给下层的函数(这种技术成为“部分应用”,是“柯里化”的一种)。
☞ 绑定例外
① 被忽略的 this
如果你把 null
或者 undefined
作为 this
的绑定对象传入 call
、apply
或者 bind
,这些值在调用时会被忽略,实际应用的是默认绑定规则。
传入
null
的情况:使用
apply(...)
来“展开”一个数组,并当做参数传入一个函数。bind(...)可以对参数进行柯里化(预先设置一些参数)。
function foo(a, b) {
console.log("a:" + a + ", b:" + b);
} // 把数组“展开”成参数
foo.apply(null, [2, 3]); // a:2, b:3 // 在ES6中,可以用...操作符代替apply(...)来“展开”数组
foo(...[1,2]); // a:1, b:2 // 使用 bind(...) 进行柯里化
var bar = foo.bind(null, 3);
bar(4); // a:3, b:4
更安全的
this
:创建一个空的非委托的对象(
Object.create(null)
)function foo(a, b) {
console.log("a:" + a + ", b:" + b);
} // 创建DMZ(demilitarized zone,非军事区)空对象
var dmzObj = Object.create(null); // 把数组“展开”成参数
foo.apply(dmzObj, [2, 3]); // a:2, b:3 // 使用 bind(...) 进行柯里化
var bar = foo.bind(dmzObj, 3);
bar(4); // a:3, b:4
Object.create(null)
和{}
很像,但是并不会创建Object.prototype
这个委托,所以它比 {} “更空”。
② 间接引用 —— 函数会应用默认绑定规则。
[ “间接引用”最容易在赋值时发生 ]:
function foo() {
console.log(this.a);
}
var a = 2;
var o = {a: 3, foo: foo};
var p = {a: 4};
o.foo(); // 3
/**
* 该赋值表达式的返回值是目标函数的引用
* 因此调用位置是 foo() 而不是 p.foo() 或者 o.foo()
*/
(p.foo = o.foo)(); // 2
③ 软绑定
硬绑定的优点:会把 this
强制绑定到指定的对象,防止函数调用应用默认绑定规则。
硬绑定的缺点:会大大降低函数的灵活性,使用之后就无法使用隐式绑定或者显示绑定来修改 this
。
软绑定:可以给默认绑定指定一个全局对象和 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 === (window || global)) ?
obj : this,
curried.concat.apply(curried, arguments)
);
};
bound.prototype = Object.create(fn.prototype);
return bound;
}
}
function foo() {
console.log("name:" + this.name);
}
var obj = { name: 'obj' },
obj2 = { name: 'obj2' },
obj3 = { name: 'obj3' };
/**
* 软绑定
*/
var fooOBJ = foo.softBind(obj);
fooOBJ(); // name: obj
obj2.foo = foo.softBind(obj);
obj2.foo(); // name: obj2 <---- 看!!!
fooOBJ.call(obj3); // name: obj3 <---- 看!
setTimeout(obj2.foo, 10); // name: obj <---- 应用了软绑定
/**
* 硬绑定
*/
obj3.foo = foo.bind(obj3);
obj3.foo(); // name: obj3
setTimeout(obj3.foo, 10); // name: obj3
☞ this
词法 ——> 箭头函数
箭头函数
不使用 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!箭头函数的绑定无法被修改!
[ 箭头函数最常用于回调函数中 ]:
function foo() {
setTimeout(() => {
// 这里的 this 在此法上继承自 foo()
console.log(this.a);
}, 100);
}
var obj = {
a: 2
};
foo.call(obj); // 2
箭头函数可以像
bind(...)
一样确保函数的this
被绑定到指定对象,此外,其重要性还体现在它用更常见的词法作用域取代了传统的this
机制。
《你不知道的JavaScript》第二部分:this 详解的更多相关文章
- JavaScript正则表达式详解(二)JavaScript中正则表达式函数详解
二.JavaScript中正则表达式函数详解(exec, test, match, replace, search, split) 1.使用正则表达式的方法去匹配查找字符串 1.1. exec方法详解 ...
- Javascript常用的设计模式详解
Javascript常用的设计模式详解 阅读目录 一:理解工厂模式 二:理解单体模式 三:理解模块模式 四:理解代理模式 五:理解职责链模式 六:命令模式的理解: 七:模板方法模式 八:理解javas ...
- JavaScript 各种遍历方式详解及总结
JavaScript 各种遍历方式详解 在$.each中想要终止循环,但是它没有continue或者break这样的终止方式,所以尝试使用return来进行终止,但是发现并没有跳出循环.为了搞清楚js ...
- JavaScript进阶内容——DOM详解
JavaScript进阶内容--DOM详解 当我们已经熟练掌握JavaScript的语法之后,我们就该进入更深层次的学习了 首先我们思考一下:JavaScript是用来做什么的? JavaScript ...
- (转)javascript中event对象详解
原文:http://jiajiale.iteye.com/blog/195906 javascript中event对象详解 博客分类: javaScript JavaScriptCS ...
- Javascript 异步加载详解
Javascript 异步加载详解 本文总结一下浏览器在 javascript 的加载方式. 关键词:异步加载(async loading),延迟加载(lazy loading),延迟执行(lazy ...
- 【JavaScript中的this详解】
前言 this用法说难不难,有时候函数调用时,往往会搞不清楚this指向谁?那么,关于this的用法,你知道多少呢? 下面我来给大家整理一下关于this的详细分析,希望对大家有所帮助! this指向的 ...
- JavaScript中的this详解
前言 this用法说难不难,有时候函数调用时,往往会搞不清楚this指向谁?那么,关于this的用法,你知道多少呢? 下面我来给大家整理一下关于this的详细分析,希望对大家有所帮助! this指向的 ...
- Javascript中prototype属性详解 (存)
Javascript中prototype属性详解 在典型的面向对象的语言中,如java,都存在类(class)的概念,类就是对象的模板,对象就是类的实例.但是在Javascript语言体系中,是不 ...
- 学会Git玩转GitHub(第二篇) 入门详解 - 精简归纳
学会Git玩转GitHub(第二篇) 入门详解 - 精简归纳 JERRY_Z. ~ 2020 / 10 / 25 转载请注明出处!️ 目录 学会Git玩转GitHub(第二篇) 入门详解 - 精简归纳 ...
随机推荐
- python——使用readline库实现tab自动补全
Input History readline tracks the input history automatically. There are two different sets of funct ...
- [转]POJO中使用ThreadLocal实现Java嵌套事务
大多嵌套事务都是通过EJB实现的,现在我们尝试实现对POJO的嵌套事务.这里我们使用了ThreadLocal的功能. 理解嵌套事务 事务是可以嵌套的.所以内层事务或外层事务可以在不影响其他事务的条件下 ...
- BZOJ1722 [Usaco2006 Mar] Milk Team Select 产奶比赛
直接树形dp就好了恩 令$f[i][j][t]$表示以$i$为根的子树,选出来的点存在$j$对父子关系,$t$表示$i$这个点选或者没选,的最大产奶值 分类讨论自己和儿子分别有没有选,然后转移一下就好 ...
- 【工具推荐】ELMAH——可插拔错误日志工具
今天看到一篇文章(构建ASP.NET网站十大必备工具(2)),里面介绍了一个ELMAH的错误日志工具,于是研究了一下. ELMAH 是 Error Logging Modules and Handle ...
- js的严谨模式
一.怎么用 <script type="text/javascript"> "use strict"; //放在脚本文件第一行,整个脚本将以 ...
- Js练习题之字符串转驼峰
如border-bottom-color =>borderBottomColor 传传统方法 分析: 1.转大写,需要用到字符串的toUpperCase()方法 2.去掉-,需要用到字符串方法s ...
- JS桌面应用
一.图片预加载 var oImg = new Image(); oImg.onload=function(){ //alert('success'); } oImg.onerror=function( ...
- Jmeter java.lang.OutOfMemoryError: GC overhead limit exceeded
使用这个jmeter工具测试时,遇到这么个gc错误,网上找到了解决方案.原因是jmeter默认分配内存的参数很小,好像是256M吧.故而解决方法,就是增加内存: set HEAP=-Xms4g -Xm ...
- ASP.NET MVC 基于角色的权限控制系统的示例教程
上一次在 .NET MVC 用户权限管理示例教程中讲解了ASP.NET MVC 通过AuthorizeAttribute类的OnAuthorization方法讲解了粗粒度控制权限的方法,接下来讲解基于 ...
- 分布式一致性原理—CAP
背景 随着分布式事务的出现,传统的单机事务模型(ACID)已经无法胜任,尤其是对于一个高访问量.高并发的互联网分布式系统来说. 如果我们要求严格一致性,很可能就需要牺牲掉系统的可用性,反之亦然.但两者 ...