《你不知道的JavaScript》整理(二)——this
最近在读一本进阶的JavaScript的书《你不知道的JavaScript(上卷)》,这次研究了一下“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 的调用位置
二、绑定规则
你必须找到调用位置,然后判断需要应用下面四条规则中的哪一条。
1)默认绑定
最常用的函数调用类型:独立函数调用。可以把这条规则看作是无法应用其他规则时的默认规则。
function foo() {
console.log(this.a);
}
var a = 2;
foo(); //
2)隐式绑定
隐式绑定的规则是调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含。
function foo() {
console.log(this.a);
}
var obj = {
a: 2,
foo: foo
};
obj.foo(); //
但有时候会出现隐式丢失。
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()其实是一个不带任何修饰的函数调用,应用了默认绑定。
3)显式绑定
使用函数的call(..)和apply(..)方法。
function foo() {
console.log(this.a);
}
var obj = {
a: 2
};
foo.call(obj); //
在很多库中经常能看到bind方法,这是一种硬绑定,一种显式的强制绑定,下面是一种bind实现。
function foo(something) {
console.log(this.a, something);
return this.a + something;
}
// 简单的辅助绑定函数
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); //
4)new绑定
使用new来调用函数,或者说发生构造函数调用时,会自动执行下面的操作:
1. 创建(或者说构造)一个全新的对象。
2. 这个新对象会被执行[[原型]]连接。
3. 这个新对象会绑定到函数调用的this。
4. 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。
function foo(a) {
this.a = a;
}
var bar = new foo(2);
console.log(bar.a); //
三、优先级
默认绑定的优先级是四条规则中最低的。
1)显式绑定优先级比隐式绑定要更高
function foo() {
console.log(this.a);
}
var obj1 = {
a: 2,
foo: foo
};
var obj2 = {
a: 3,
foo: foo
};
obj1.foo(); // 2
obj2.foo(); //
obj1.foo.call(obj2); // 3
obj2.foo.call(obj1); //
2)new绑定比隐式绑定优先级高
function foo(something) {
this.a = something;
}
var obj1 = {
foo: foo
};
obj1.foo(2);
console.log(obj1.a); //
var bar = new obj1.foo(4);
console.log(obj1.a); // 2
console.log(bar.a); //
3)new绑定会修改显示绑定中this
function bind(fn, obj) {
return function() {
return fn.apply(obj, arguments);
};
}
function foo(something) {
this.a = something;
}
var obj1 = {};
var bar = foo.bind(obj1); //bar被硬绑定到obj1上
bar(2);
console.log( obj1.a ); //
var baz = new bar(3); //
console.log( obj1.a ); // 没有把obj1.a 修改为3,还是为2
console.log( baz.a ); //
4)判断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,否则绑定到全局对象。
var bar = foo()
四、绑定例外
1)被忽略的this
如果你把null或者undefined作为this的绑定对象传入call、apply或者bind。
这些值在调用时会被忽略,实际应用的是默认绑定规则。
function foo() {
console.log(this.a);
}
var a = 2;
foo.call(null); //
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()。
3)软绑定
如果可以给默认绑定指定一个全局对象和undefined以外的值。
那就可以实现和硬绑定相同的效果,同时保留隐式绑定或者显式绑定修改this的能力。
可以通过一种被称为软绑定的方法来实现我们想要的效果。
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 <---- 应用了软绑定
软绑定版本的foo()可以手动将this绑定到obj2或者obj3上。
但如果应用默认绑定,则会将this绑定到obj。
《你不知道的JavaScript》整理(二)——this的更多相关文章
- 你不知道的JavaScript(二)数组
作为一种线性数据结构,几乎每一种编程语言都支持数组类型.和c++.java这些强类型的语言相比,JavaScript数组有些不同,它可以存放任意类型的值.上节中有提到过JS中任意类型的值都可以赋值给任 ...
- 《你不知道的JavaScript》整理(一)——作用域、提升与闭包
最近在读一本进阶的JavaScript的书<你不知道的JavaScript(上卷)>,里面分析了很多基础性的概念. 可以更全面深入的理解JavaScript深层面的知识点. 一.函数作用域 ...
- 读《你不知道的JavaScript(上卷)》后感-作用域闭包(二)
github原文 一. 序言 最近我在读一本书:<你不知道的JavaScript>,这书分为上中卷,内容非常丰富,认真细读,能学到非常多JavaScript的知识点,希望广大的前端同胞们, ...
- JS闭包—你不知道的JavaScript上卷读书笔记(二)
关于闭包,初学者会被绕的晕头转向,在学习的路上也付出了很多精力来理解. 让我们一起来揭开闭包神秘的面纱. 闭包晦涩的定义 看过很多关于闭包的定义,很多讲的云里雾里,晦涩难懂.让不少人以为闭包是多么玄乎 ...
- 读《你不知道的JavaScript(上卷)》后感-浅谈JavaScript作用域(一)
原文 一. 序言 最近我在读一本书:<你不知道的JavaScript>,这书分为上中卷,内容非常丰富,认真细读,能学到非常多JavaScript的知识点,希望广大的前端同胞们,也入手看看这 ...
- JavaScript作用域闭包(你不知道的JavaScript)
JavaScript闭包.是JS开发project师必须深入了解的知识. 3月份自己曾撰写博客<JavaScript闭包>.博客中仅仅是简单阐述了闭包的工作过程和列举了几个演示样例,并没有 ...
- 读书笔记-你不知道的JavaScript(上)
本文首发在我的个人博客:http://muyunyun.cn/ <你不知道的JavaScript>系列丛书给出了很多颠覆以往对JavaScript认知的点, 读完上卷,受益匪浅,于是对其精 ...
- JavaScript中的this(你不知道的JavaScript)
JavaScript中的this,刚接触JavaScript时大家都在大肆渲染说其多么多么的灵巧重要,然而自己并不关心:随着自己对JavaScript一步步深入了解,突然恍然大悟,原来它真的很重要!所 ...
- 《你不知道的 JavaScript 上卷》 学习笔记
第一部分: 作用域和闭包 一.作用域 1. 作用域:存储变量并且查找变量的规则 2. 源代码在执行之前(编译)会经历三个步骤: 分词/此法分析:将代码字符串分解成有意义的代码块(词法单元) 解析/语法 ...
随机推荐
- .NET Core 系列5 :使用 Nuget打包类库
NuGet是个开源项目,项目包括 NuGet VS插件/NuGet Explorer/NuGetServer/NuGet命令行等项目,.NET Core项目完全使用Nuget 管理组件之间的依赖关系, ...
- 采用MiniProfiler监控EF与.NET MVC项目(Entity Framework 延伸系列1)
前言 Entity Framework 延伸系列目录 今天来说说EF与MVC项目的性能检测和监控 首先,先介绍一下今天我们使用的工具吧. MiniProfiler~ 这个东西的介绍如下: MVC Mi ...
- Android中手机录屏并转换GIF的两种方式
之前在博文中为了更好的给大家演示APP的实现效果,本人了解学习了几种给手机录屏的方法,今天就给大家介绍两种我个人用的比较舒服的两种方法: (1)配置adb环境后,使用cmd命令将手机界面操作演示存为视 ...
- iOS可视化动态绘制八种排序过程
前面几篇博客都是关于排序的,在之前陆陆续续发布的博客中,我们先后介绍了冒泡排序.选择排序.插入排序.希尔排序.堆排序.归并排序以及快速排序.俗话说的好,做事儿要善始善终,本篇博客就算是对之前那几篇博客 ...
- RestTemplate发送请求并携带header信息
1.使用restTemplate的postForObject方法 注:目前没有发现发送携带header信息的getForObject方法. HttpHeaders headers = new Http ...
- .NET平台开源项目速览(18)C#平台JSON实体类生成器JSON C# Class Generator
去年,我在一篇文章用原始方法解析复杂字符串,json一定要用JsonMapper么?中介绍了简单的JSON解析的问题,那种方法在当时的环境是非常方便的,因为不需要生成实体类,结构很容易解析.但随着业务 ...
- .Net 大型分布式基础服务架构横向演变概述
一. 业务背景 构建具备高可用,高扩展性,高性能,能承载高并发,大流量的分布式电子商务平台,支持用户,订单,采购,物流,配送,财务等多个项目的协作,便于后续运营报表,分析,便于运维及监控. 二. 基础 ...
- [转载]强制不使用“兼容性视图”的HTML代码
在IE8浏览器以后版本,都有一个"兼容性视图",让不少新技术无法使用.那么如何禁止浏览器自动选择"兼容性视图",强制IE以最高级别的可用模式显示内容呢?下面就介 ...
- psoc学习
第一是:项目的路径需要放在Documents and Settings\,也就是默认的文件夹的地方,不然会报错错误范例为:Question:CY8CKIT-023 kit example projec ...
- Tomcat 部署我的第一个程序
idea 生成war包.先双击clean,再双击package.生成成功之后就会产生war包. 第二步:将生成好的war文件复制到tomcat文件夹下. 第三步:配置tomcat的server.xml ...