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 的调用位置

this绑定规则

1. 默认绑定:
function foo() {
console.log( this.a );
}
var a = 2;
foo(); // 2

上述代码中,函数调用时应用了this 的默认绑定,因此this 指向全局对象(非严格模式下)。

那么我们怎么知道这里应用了默认绑定呢?可以通过分析调用位置来看看foo() 是如何调用的。在代码中,foo() 是直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定,无法应用其他规则。

如果使用严格模式(strict mode),那么全局对象将无法使用默认绑定,因此this 会绑定到undefined:

function foo() {
"use strict";
console.log( this.a );
}
var a = 2;
foo(); // TypeError: this is undefined

这里有一个微妙但是非常重要的细节,虽然this 的绑定规则完全取决于调用位置,但是只有foo() 运行在非strict mode 下时,默认绑定才能绑定到全局对象;严格模式下与foo()的调用位置无关:

function foo() {
console.log( this.a );
}
var a = 2; (function(){
"use strict";
foo(); // 2
})();
2. 隐式绑定:
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
}; obj.foo(); // 2

当foo() 被调用时,它的落脚点指向obj 对象。当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this 绑定到这个上下文对象。因为调用foo() 时this 被绑定到obj,因此this.a 和obj.a 是一样的。

对象属性引用链中只有最顶层或者说最后一层会影响调用位置

#######隐式丢失

一个最常见的this 绑定问题就是被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,从而把this 绑定到全局对象或者undefined 上,取决于是否是严格模式。

function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo; // 函数别名!
var a = "oops, global"; // a 是全局对象的属性
bar(); // "oops, global"

虽然bar 是obj.foo 的一个引用,但是实际上,它引用的是foo 函数本身,因此此时的bar() 其实是一个不带任何修饰的函数调用,因此应用了默认绑定。

3. 显式绑定:

就像我们刚才看到的那样,在分析隐式绑定时,我们必须在一个对象内部包含一个指向函数的属性,并通过这个属性间接引用函数,从而把this 间接(隐式)绑定到这个对象上。那么如果我们不想在对象内部包含函数引用,而想在某个对象上强制调用函数,该怎么做呢?

JavaScript 中的“所有”函数都有一些有用的特性(这和它们的[[ 原型]] 有关——之后我们会详细介绍原型),可以用来解决这个问题。具体点说,可以使用函数的call(..) 和apply(..) 方法。严格来说,JavaScript 的宿主环境有时会提供一些非常特殊的函数,它们并没有这两个方法。但是这样的函数非常罕见,JavaScript 提供的绝大多数函数以及你自己创建的所有函数都可以使用call(..) 和apply(..) 方法。

function foo() {
console.log( this.a );
}
var obj = {
a:2
};
foo.call( obj ); // 2

通过foo.call(..),我们可以在调用foo 时强制把它的this 绑定到obj 上。

显式绑定仍然无法解决我们之前提出的丢失绑定问题,通过以下方法可以解决:

硬绑定

	function foo() {
console.log( this.a );
}
var obj = {
a:2
};
var bar = function() {
foo.call( obj );
};
bar(); // 2
setTimeout( bar, 100 ); // 2
// 硬绑定的bar 不可能再修改它的this
bar.call( window ); // 2
4. new绑定:

使用new 来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。

  1. 创建(或者说构造)一个全新的对象。

  2. 这个新对象会被执行[[ 原型]] 连接。

  3. 这个新对象会绑定到函数调用的this。

  4. 如果函数没有返回其他对象,那么new 表达式中的函数调用会自动返回这个新对象。

     function foo(a) {
    this.a = a;
    }
    var bar = new foo(2);
    console.log( bar.a ); // 2

优先级: new绑定>显式绑定>隐式绑定

判断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
更安全的ths

一种“更安全”的做法是传入一个特殊的对象,把this 绑定到这个对象不会对你的程序产生任何副作用。。就像网络(以及军队)一样,我们可以创建一个“DMZ”(demilitarized zone,非军事区)对象——它就是一个空的非委托的对象。

function foo(a,b) {
console.log( "a:" + a + ", b:" + b );
}
// 我们的DMZ 空对象
var ø = Object.create( null ); //此处的∅不会创建Object.prototype 这个委托,所以它比{}“更空”
// 把数组展开成参数
foo.apply( ø, [2, 3] ); // a:2, b: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(); // 3
(p.foo = o.foo)(); // 2

3. 软绑定(此处不讨论)

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 !

foo() 内部创建的箭头函数会捕获调用时foo() 的this。由于foo() 的this 绑定到obj1,bar(引用箭头函数)的this 也会绑定到obj1,箭头函数的绑定无法被修改。(new 也不行!)

JavaScript中的this—你不知道的JavaScript上卷读书笔记(三)的更多相关文章

  1. JavaScript 中的事件类型5(读书笔记思维导图)

    Web 浏览器中可能发生的事件有很多类型.如前所述,不同的事件类型具有不同的信息,而“ DOM3级事件”规定了以下几类事件. UI(User Interface,用户界面)事件:当用户与页面上的元素交 ...

  2. JavaScript 中的事件类型4(读书笔记思维导图)

    Web 浏览器中可能发生的事件有很多类型.如前所述,不同的事件类型具有不同的信息,而“ DOM3级事件”规定了以下几类事件. UI(User Interface,用户界面)事件:当用户与页面上的元素交 ...

  3. JavaScript 中的事件类型3(读书笔记思维导图)

    Web 浏览器中可能发生的事件有很多类型.如前所述,不同的事件类型具有不同的信息,而“ DOM3级事件”规定了以下几类事件. UI(User Interface,用户界面)事件:当用户与页面上的元素交 ...

  4. JavaScript 中的事件类型2(读书笔记思维导图)

    Web 浏览器中可能发生的事件有很多类型.如前所述,不同的事件类型具有不同的信息,而“ DOM3级事件”规定了以下几类事件: UI(User Interface,用户界面)事件:当用户与页面上的元素交 ...

  5. JavaScript 中的事件类型1(读书笔记思维导图)

    Web 浏览器中可能发生的事件有很多类型.如前所述,不同的事件类型具有不同的信息,而“ DOM3级事件”规定了以下几类事件. UI(User Interface,用户界面)事件:当用户与页面上的元素交 ...

  6. 【转载】MDX Step by Step 读书笔记(三) - Understanding Tuples (理解元组)

    1. 在 Analysis Service 分析服务中,Cube (多维数据集) 是以一个多维数据空间来呈现的.在Cube 中,每一个纬度的属性层次结构都形成了一个轴.沿着这个轴,在属性层次结构上的每 ...

  7. JavaScript中的对象与原型—你不知道的JavaScript上卷读书笔记(四)

    一.对象 对象可以通过两种形式定义:声明(文字)形式和构造形式.即: var myObj = { key: value // ... }; 或: var myObj = new Object(); m ...

  8. JS闭包—你不知道的JavaScript上卷读书笔记(二)

    关于闭包,初学者会被绕的晕头转向,在学习的路上也付出了很多精力来理解. 让我们一起来揭开闭包神秘的面纱. 闭包晦涩的定义 看过很多关于闭包的定义,很多讲的云里雾里,晦涩难懂.让不少人以为闭包是多么玄乎 ...

  9. JavaScript词法作用域—你不知道的JavaScript上卷读书笔记(一)

    前段时间在每天往返的地铁上抽空将 <你不知道的JavaScript(上卷)>读了一遍,这本书很多部分写的很是精妙,对于接触前端时间不太久的人来说,就好像是叩开了JavaScript的另一扇 ...

随机推荐

  1. 【2019.7.20 NOIP模拟赛 T2】B(B)(数位DP)

    数位\(DP\) 首先考虑二进制数\(G(i)\)的一些性质: \(G(i)\)不可能有连续两位第\(x\)位和第\(x+1\)位都是\(1\).因为这样就可以进位到第\(x+2\)位.其余情况下,这 ...

  2. display Flex 盒子模型布局兼容Android UC

    <!DOCTYPE html><html><head><meta charset="utf-8"><meta content= ...

  3. 数据仓库005 - 复习Linux shell命令 - crontab调度 sh脚本 后台执行 软连接

    一.crontab调度 对于linux 自带crontab而言,      xxx.sh的一般编写格式以#!/bin/bash 解释器开头,可在脚本中加入: date 但是,shell脚本执行 需要 ...

  4. @Resource和@Autowire用谁?

    我选了@Resource 1.当注入的属性是接口 1.1在接口只有一个实现类的时候,@Resource和@Autowire 在功能上是没有区别的 1.2如果接口有多个实现类,在写法上,@Autowir ...

  5. NPOI 帮助类

    NPOI 帮助类 代码实现了来自于互联网 using System; using System.Data; using System.IO; using System.Text; using NPOI ...

  6. Logstash:多个配置文件(conf)

    Logstash:多个配置文件(conf) 对于多个配置的处理方法,有多个处理方法: 多个pipeline 一个pipleline处理多个配置文件 一个pipeline含有一个逻辑的数据流,它从inp ...

  7. Spring-AOP源码分析随手记(二)

    这次来分析下切面的执行过程. 1.怎么看? 怎么开始看源码呢?就直接从被增强的方法调用那里打断点,看看怎么执行的: 然后就来到了这: 2.初步分析 里面有段: if (this.advised.exp ...

  8. 3-美团 HTTP 服务治理实践

    参考: 美团 HTTP 服务治理实践 Oceanus:美团HTTP流量定制化路由的实践

  9. HDFS基本原理

    一.什么是HDFS HDFS即Hadoop分布式文件系统(Hadoop Distributed Filesystem),以流式数据访问模式来存储超大文件,它和现有的分布式文件系统有很多共同点.但同时, ...

  10. PHP MySQL数据分页

    SQL SELECT语句查询总是可能导致数千条记录.但是在一个页面上显示所有结果并不是一个好主意.因此,我们可以根据要求将此结果划分为多个页面.分页意味着在多个页面中显示您的查询结果,而不是仅将它们全 ...