《你不知道的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(第二篇) 入门详解 - 精简归纳 ...
随机推荐
- 分形树Fractal tree介绍——具体如何结合TokuDB还没有太懂,先记住其和LSM都是一样的适合写密集
在目前的Mysql数据库中,使用最广泛的是innodb存储引擎.innodb确实是个很不错的存储引擎,就连高性能Mysql里都说了,如果不是有什么很特别的要求,innodb就是最好的选择.当然,这偏文 ...
- JS对象的写法
写法1: <script> var database = function () { function add(){ console.info("add"); } fu ...
- UVALive 6680 Join the Conversation
题意:conversion的定义是下一句提到上一句的人的名字.请你输出最长的对话的长度,及组成对话的序列号. 思路:动态规划的思想很容易想到,当前句子,根据所有提到的人的名字为结尾组成的对话长度来判断 ...
- Redis的简单介绍及在Windows下环境搭建
简单介绍 1,Redis是什么 最直接的还是看官方的定义吧. Redis is an open source (BSD licensed), in-memory data structure stor ...
- FusionCharts ajax 调用方式
方式一:setJSONUrl function initChart() { var myChart = new FusionCharts("Fusion ...
- 在ASP.NET MVC中使用Area
前言: 这段时间小猪花了不少功夫在研究ASP.NET MVC的源码上面,可谓思想是了解了不少,用的上用不上却是另外一回事了.! 应用场景: ASP.NET MVC中,是依靠某些文件夹以及类的固定命名规 ...
- jquery 字数限制
$("#TextArea1").keydown(function(){ 10 var curLength=$("#TextArea1").val().lengt ...
- 带你揭开ATM的神秘面纱
相信大家都用过ATM取过money吧,但是有多少人真正是了解ATM的呢?相信除了ATM从业者外了解的人寥寥无几吧,鄙人作为一个从事ATM软件开发的伪专业人士就站在我的角度为大家揭开ATM的神秘面纱吧. ...
- 12.Object-C--浅谈OC的内存管理机制
昨天学习了OC的内存管理机制,今天想总结一下,所以接下来我要在这里bibi一下:OC的内存管理. 首先我要说的是,内存管理的作用范围. 内存管理的作用范围: 任何继承了NSObject的对象,对其他基 ...
- Codeforces Round #257 (Div. 1) (Codeforces 449B)
题意:给力一张无向图,有一些边是正常道路,有一些边是铁路,问最多能删除几条铁路使得所有点到首都(编号为1)的最短路长度不变. 思路:求不能删除的铁路数,总数减掉就是答案.先求出首都到所有点的最短路,求 ...